From d4559eb70549782b15b8f4e925891edef5dfd077 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Fri, 30 Oct 2026 07:47:19 +0100 Subject: [PATCH 001/144] fix(runtime): instanceof and inherited getters on function objects Function objects keep a reset prototype in the closure side table, so `instanceof` ignored it and fell back to the builtin check; `has_recorded_prototype` now consults both tables. An inherited getter reached through a function receiver also now runs with that function as `this`. --- ...546-function-receiver-instanceof-getter.md | 4 +++ .../src/closure/dynamic_props.rs | 10 ++++++++ crates/perry-runtime/src/object/instanceof.rs | 2 +- .../src/object/instanceof/dynamic_dispatch.rs | 2 +- .../src/object/instanceof/static_dispatch.rs | 2 +- .../src/object/prototype_chain.rs | 7 ++++++ ..._gap_function_receiver_inherited_getter.ts | 25 +++++++++++++++++++ 7 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 changelog.d/13546-function-receiver-instanceof-getter.md create mode 100644 test-files/test_gap_function_receiver_inherited_getter.ts diff --git a/changelog.d/13546-function-receiver-instanceof-getter.md b/changelog.d/13546-function-receiver-instanceof-getter.md new file mode 100644 index 00000000000..cbcaf1dc70e --- /dev/null +++ b/changelog.d/13546-function-receiver-instanceof-getter.md @@ -0,0 +1,4 @@ +Function objects keep a reset prototype in the closure side table, so +`instanceof` ignored it and fell back to the builtin check; +`has_recorded_prototype` now consults both tables. An inherited getter reached +through a function receiver also now runs with that function as `this`. diff --git a/crates/perry-runtime/src/closure/dynamic_props.rs b/crates/perry-runtime/src/closure/dynamic_props.rs index a27559e607d..ce7cf9b7904 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -366,10 +366,20 @@ pub(crate) fn closure_get_dynamic_prop_keyed( // (and prime) the inherited-read cache instead of minting a fresh // key string that no cache entry can ever match again. let key_hdr = crate::string::canonical_key(prop.as_bytes()); + // An accessor further up the prototype's own chain runs with the + // closure as `this`, not the prototype the walk entered through. + let scope = crate::gc::RuntimeHandleScope::new(); + let prev_override = crate::object::field_get_set::accessor_receiver_override_begin( + crate::value::js_nanbox_pointer(ptr as i64), + ); + let prev_override_handle = prev_override.map(|value| scope.root_nanbox_f64(value)); let v = crate::object::js_object_get_field_by_name( proto_ptr as *const crate::object::ObjectHeader, key_hdr as *const crate::StringHeader, ); + crate::object::field_get_set::accessor_receiver_override_end( + prev_override_handle.map(crate::gc::RuntimeHandle::into_nanbox_f64), + ); if !v.is_undefined() && !v.is_null() { return f64::from_bits(v.bits()); } diff --git a/crates/perry-runtime/src/object/instanceof.rs b/crates/perry-runtime/src/object/instanceof.rs index 0e9399dd868..03770e1cebd 100644 --- a/crates/perry-runtime/src/object/instanceof.rs +++ b/crates/perry-runtime/src/object/instanceof.rs @@ -119,7 +119,7 @@ fn recorded_prototype_instanceof_builtin( constructor: Option<&crate::gc::RuntimeHandle<'_>>, ) -> Option { let addr = value_addr(value.get_nanbox_f64()); - if addr == 0 || super::prototype_chain::object_static_prototype(addr).is_none() { + if addr == 0 || !super::prototype_chain::has_recorded_prototype(addr) { return None; } prototype_instanceof_builtin(value, name, constructor) diff --git a/crates/perry-runtime/src/object/instanceof/dynamic_dispatch.rs b/crates/perry-runtime/src/object/instanceof/dynamic_dispatch.rs index be9dcd55f81..86ac07c4ab8 100644 --- a/crates/perry-runtime/src/object/instanceof/dynamic_dispatch.rs +++ b/crates/perry-runtime/src/object/instanceof/dynamic_dispatch.rs @@ -260,7 +260,7 @@ pub extern "C" fn js_instanceof_dynamic(value: f64, mut type_ref: f64) -> f64 { // with an individually recorded prototype chain (an instance of a // later evaluation) answers by its actual chain. if crate::object::class_value::class_value_is_first_evaluation(class_id) - && super::prototype_chain::object_static_prototype(value_addr(value)).is_some() + && super::prototype_chain::has_recorded_prototype(value_addr(value)) { return f64::from_bits(if ordinary_has_instance_prototype_walk(value, type_ref) { crate::value::TAG_TRUE diff --git a/crates/perry-runtime/src/object/instanceof/static_dispatch.rs b/crates/perry-runtime/src/object/instanceof/static_dispatch.rs index a461272c451..55af03a2ece 100644 --- a/crates/perry-runtime/src/object/instanceof/static_dispatch.rs +++ b/crates/perry-runtime/src/object/instanceof/static_dispatch.rs @@ -460,7 +460,7 @@ fn native_instanceof( // prototype side table instead. Honor that chain for user class ids. if is_class_id_registered(class_id) { let addr = value_addr(value); - if addr != 0 && super::prototype_chain::object_static_prototype(addr).is_some() { + if addr != 0 && super::prototype_chain::has_recorded_prototype(addr) { let constructor = super::class_constructor_ref_value(class_id); return if ordinary_has_instance_prototype_walk( rooted_value.get_nanbox_f64(), diff --git a/crates/perry-runtime/src/object/prototype_chain.rs b/crates/perry-runtime/src/object/prototype_chain.rs index 32808599c7e..c8d07f63760 100644 --- a/crates/perry-runtime/src/object/prototype_chain.rs +++ b/crates/perry-runtime/src/object/prototype_chain.rs @@ -641,6 +641,13 @@ pub fn object_static_prototype(obj_ptr: usize) -> Option { } } +/// Does the receiver carry an explicitly recorded prototype? Function objects +/// keep theirs in the closure side table rather than the object registry. +pub(crate) fn has_recorded_prototype(addr: usize) -> bool { + object_static_prototype(addr).is_some() + || crate::closure::closure_static_prototype(addr).is_some() +} + /// True when this receiver's recorded prototype diverges from its class /// default, regardless of whether runtime wiring or a user-facing operation /// selected it. Cache guards use this conservative signal. diff --git a/test-files/test_gap_function_receiver_inherited_getter.ts b/test-files/test_gap_function_receiver_inherited_getter.ts new file mode 100644 index 00000000000..5055bcd58fa --- /dev/null +++ b/test-files/test_gap_function_receiver_inherited_getter.ts @@ -0,0 +1,25 @@ +// A getter inherited two or more prototype levels up runs with the function +// the property was read from as `this`, not with the prototype the lookup +// entered through. +class Base { + get self() { + return this; + } + get kind() { + return typeof this; + } +} +class Mid extends Base {} +class Leaf extends Mid {} + +const fn = Object.setPrototypeOf(function () {}, Mid.prototype) as any; +console.log(fn.self === fn, fn.kind); + +const bound = Object.setPrototypeOf(function () {}.bind(null), Mid.prototype) as any; +console.log(bound.self === bound, bound.kind); + +const deep = Object.setPrototypeOf(() => 1, Leaf.prototype) as any; +console.log(deep.self === deep, deep.kind); + +const obj = Object.setPrototypeOf({}, Mid.prototype) as any; +console.log(obj.self === obj, obj.kind); From 817f9baafbd3e8bdf1c6170de8d72645a376cccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 17:14:25 +0000 Subject: [PATCH 002/144] wpfacts: a method called on a described parameter's reach is a read, not an escape A marker with its callers' values beside it (decision 13, DECISIONS 148): the call runs on those values, whose contracts carry it to their units (DECISIONS 143). method_on marked the marker ESCAPE before its beside check, so each caller reported what it passed and the census broke the passed class, its method and the method's result layout. It now records a read of the key's reach (marker_load: a reach marker is its own reach), as for a parameter's own marker (DECISIONS 144). Tests: closed and run-method fixtures (red before: 4 rounds, 3 layouts broken); the round-work pin moves by +4 per round. --- changelog.d/0-uea2-method-on-read.md | 9 +++ .../src/solve/call_and_construct.rs | 20 +++++ crates/perry-wpfacts/src/tests/unit_mode.rs | 3 + .../src/tests/unit_mode/contracts.rs | 6 +- .../src/tests/unit_mode/method_on_read.rs | 78 +++++++++++++++++++ 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 changelog.d/0-uea2-method-on-read.md create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs diff --git a/changelog.d/0-uea2-method-on-read.md b/changelog.d/0-uea2-method-on-read.md new file mode 100644 index 00000000000..d8bec6d5247 --- /dev/null +++ b/changelog.d/0-uea2-method-on-read.md @@ -0,0 +1,9 @@ +Per-unit facts: a method called on what a described parameter reaches +(`context.factory.createIdentifier(..)`, where the callers' values sit beside +the parameter's marker: a closed function, a run method's parameter) is a +read of the method's key, not an escape of the receiver. The call runs on the +values beside the marker, whose contracts carry it to their units. Before, +the escape made every caller report what it passed, and the census broke the +passed class, its method and the method's result layout. A closed-function +fixture now converges in 2 rounds instead of 4 with no broken layout; the +whole program is unchanged and per unit never proves more than whole. diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index 3ff1f9d34d0..86a75a9feb9 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -750,6 +750,26 @@ impl Model { return; } } + // A marker with its callers' values beside it (decision 13, + // DECISIONS 107, 148): the call is made on those values (below), + // whose contracts carry what it does to them to their units + // (DECISIONS 143). Its own use is a read of the method under the key, + // as for a parameter's own marker (DECISIONS 144), not an escape: + // nothing of the receiver reaches code the solve does not see. A + // reach marker is its own reach under every key (`marker_load`). + if bits == crate::model::MARK_ESCAPE + && t != TOP + && self.is_marker(t) + && self.beside_values(t) + { + self.mark_use(t, crate::model::MARK_READ); + let r = match key { + Key::Named(a) => self.marker_load(t, Some(a)), + Key::Any => self.reach_of(t), + }; + self.mark_use(r, crate::model::MARK_READ); + return; + } self.mark_use(t, bits); if t == TOP { self.call_tok(call, TOP, Some(TOP)); diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index f31fd92c096..31651b8f206 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -597,3 +597,6 @@ mod return_transfers; mod commonjs_helper; #[path = "unit_mode/pub_callable.rs"] mod pub_callable; + +#[path = "unit_mode/method_on_read.rs"] +mod method_on_read; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs index a87af22c1ae..ea7193b0b94 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs @@ -313,8 +313,10 @@ pub(super) fn a_broken_namespace_field_function_resolves_only_the_units_that_see // ([754, 879, 986, 543, 467]). SEH: a join of two descriptions keeps what // each says of a field (`ClassContract::join`) where it made the field ⊤: // round 2 builds what the readers held as ⊤, and the fixpoint ends one - // round earlier. - assert_eq!(pf.round_work, [754, 1090, 948, 42], "{:?}", pf.units); + // round earlier. A method called on a marker with values beside it + // reads its key's reach (`method_on`): [754, 1090, 948, 42] -> the pin + // below. + assert_eq!(pf.round_work, [758, 1094, 952, 42], "{:?}", pf.units); } #[test] diff --git a/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs b/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs new file mode 100644 index 00000000000..acc05d70b62 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs @@ -0,0 +1,78 @@ +//! A method called on what a described parameter reaches: the +//! parameter's marker has its callers' values beside it (decision 13, +//! DECISIONS 148), so the call runs on those values and their +//! contracts carry it to their units (DECISIONS 143). The marker records a +//! read of the method's key (`Model::method_on`), as for a parameter's own +//! marker (DECISIONS 144), not an escape: an escape there made each caller +//! report its value and broke its layouts. + +use super::*; + +/// The keys `transform` reads off its parameter. +const KEYS: [&str; 2] = ["factory", "createIdentifier"]; + +const N: &str = r#"export class Factory { + createIdentifier(name: string) { return { kind: 80, text: name, parent: undefined as any }; } +}"#; + +/// The sites of module `t.ts` reading `key`. +fn t_sites<'a>(pf: &'a ProgramFacts, key: &str) -> Vec<&'a crate::SiteFacts> { + let v: Vec<_> = pf + .sites + .iter() + .filter(|s| s.key == key && pf.module_paths[s.module as usize] == "t.ts") + .collect(); + assert!(!v.is_empty(), "no t.ts site {key}\n{}", pf.dump_text()); + v +} + +fn t_proven(pf: &ProgramFacts, key: &str) -> bool { + t_sites(pf, key) + .iter() + .all(|s| matches!(s.verdict, Verdict::T0Mono | Verdict::T0Set)) +} + +/// Per unit never proves a site the whole program does not, and breaks +/// no layout: the callee's calls on what it is passed are described. +fn reads_the_method(mods: &[(&str, &str)]) -> ProgramFacts { + let (u, w) = (units_of(mods), whole_of(mods)); + let stronger: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(stronger.is_empty(), "per unit stronger than whole: {stronger:?}\n{}", u.dump_text()); + for k in KEYS { + assert!(t_proven(&w, k), "whole {k}\n{}", w.dump_text()); + } + assert!(u.broken.is_empty(), "{:?}\n{}", u.broken, u.dump_text()); + u +} + +/// Decision 13: a closed function of a sibling unit. Before: 4 rounds, +/// `Factory`, `fn:…createIdentifier` and its result's layout broken. +#[test] +fn a_closed_functions_method_call_on_a_parameters_reach_is_a_read() { + let t = r#"import { Factory } from "./n"; +function transform(context: any) { + const node = context.factory.createIdentifier("x"); + return node.kind; +} +export function run() { return transform({ factory: new Factory() }); } +console.log(run());"#; + let u = reads_the_method(&[("n", N), ("t", t)]); + assert!(t_proven(&u, "factory"), "{}", u.dump_text()); + assert_eq!(u.round_work.len(), 2, "{:?}", u.round_work); +} + +/// DECISIONS 148: a published class's method run by another unit, its +/// parameter holding what that unit passes. +#[test] +fn a_published_methods_method_call_on_a_parameters_reach_is_a_read() { + let t = r#"export class T { + transform(context: any) { + const node = context.factory.createIdentifier("x"); + return node.kind; + } +}"#; + let b = r#"import { T } from "./t"; +import { Factory } from "./n"; +console.log(new T().transform({ factory: new Factory() }));"#; + reads_the_method(&[("n", N), ("t", t), ("b", b)]); +} From aa68ab0e4959dc5e3764b76c9fb4c43d3cf14876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:43:59 +0100 Subject: [PATCH 003/144] changelog: number the PR fragment --- .../{0-uea2-method-on-read.md => 13578-uea2-method-on-read.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-uea2-method-on-read.md => 13578-uea2-method-on-read.md} (100%) diff --git a/changelog.d/0-uea2-method-on-read.md b/changelog.d/13578-uea2-method-on-read.md similarity index 100% rename from changelog.d/0-uea2-method-on-read.md rename to changelog.d/13578-uea2-method-on-read.md From 3077cc89c36ffb28d2cec895c7251a69d0f3557e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:37:27 +0000 Subject: [PATCH 004/144] link: PERRY_LINK_PAD places pad objects between the compiled objects and the runtime archive; linkpad pads in front of the runtime --- changelog.d/0-lpad2-pad-before-runtime.md | 17 + .../perry/src/commands/compile/compile_env.rs | 1 + .../commands/compile/link/build_and_run.rs | 1 + .../src/commands/compile/link/link_cache.rs | 2 + crates/perry/src/commands/compile/link/mod.rs | 37 +- .../perry/src/commands/compile/link/wasi.rs | 1 + .../src/commands/compile/run_pipeline.rs | 1 + .../src/commands/compile/tiny_program/mod.rs | 4 +- scripts/a1/irdiff.sh | 39 +- scripts/a1/linkpad.py | 386 ++++++++++++++---- 10 files changed, 387 insertions(+), 102 deletions(-) create mode 100644 changelog.d/0-lpad2-pad-before-runtime.md diff --git a/changelog.d/0-lpad2-pad-before-runtime.md b/changelog.d/0-lpad2-pad-before-runtime.md new file mode 100644 index 00000000000..49b1d31007f --- /dev/null +++ b/changelog.d/0-lpad2-pad-before-runtime.md @@ -0,0 +1,17 @@ +`PERRY_LINK_PAD`, a measurement input for startup A/Bs: whitespace-separated objects placed on the link +line right after the compiled objects and before the runtime archive, on every final link perry runs +(executable, dylib, proven tiny program, WASI component), through one reader, `link::link_pad_args()`. +`PERRY_EXTRA_LINK_ARGS` keeps its place (appended last) and meaning. Unset, the link line is unchanged. + +The startup A/B harness's link-pad control (`scripts/a1/linkpad.py`, `scripts/a1/irdiff.sh`) now pads in +front of the runtime. The linker lays input sections out in command-line order, so a pad between the +compiled objects and the runtime archive keeps every runtime byte of `.text`, `.rodata` (including the +merged `.rodata.cst*`/`str1.1` blobs), `.eh_frame` (one filler FDE), `.gcc_except_table`, `.data.rel.ro` +(anchored at the end of the RELRO region), `.data` and `.bss` at the same address in both arms; the pad +that is linked last remains only to equalize section ends and the heap start when the runtime itself +differs. The planner relinks the cached objects until the markers coincide; the proof +(`/.layout.txt`) compares markers, section ends, segment starts, the image end and the +addresses of every runtime symbol both binaries define. Rows that were left by the previous control (ld.so +`check_match` over runtime `.rodata` names, libc compares over runtime literals) now disappear when the +runtime is unchanged. `perry_*` record sections and `.eh_frame_hdr` stay unpadded; both arms must be built +from a perry that has `PERRY_LINK_PAD`. diff --git a/crates/perry/src/commands/compile/compile_env.rs b/crates/perry/src/commands/compile/compile_env.rs index 32cc0f3dac8..bbda77ed13b 100644 --- a/crates/perry/src/commands/compile/compile_env.rs +++ b/crates/perry/src/commands/compile/compile_env.rs @@ -108,6 +108,7 @@ pub(super) const COMPILE_ENV_VARS: &[&str] = &[ "PERRY_LD", "PERRY_LD64.LLD", "PERRY_LIB_DIR", + "PERRY_LINK_PAD", "PERRY_LINUX_ARM64_CC", "PERRY_LINUX_ARM64_SYSROOT", "PERRY_LINUX_MUSL_AARCH64_CC", diff --git a/crates/perry/src/commands/compile/link/build_and_run.rs b/crates/perry/src/commands/compile/link/build_and_run.rs index f692bcc8c01..d81386f5536 100644 --- a/crates/perry/src/commands/compile/link/build_and_run.rs +++ b/crates/perry/src/commands/compile/link/build_and_run.rs @@ -158,6 +158,7 @@ pub(crate) fn build_and_run_link( for obj_path in obj_paths { cmd.arg(obj_path); } + cmd.args(super::link_pad_args()); // HarmonyOS: pick up native C objects that build.rs scripts emitted // alongside the Rust artifacts. Rust's staticlib normally bundles these diff --git a/crates/perry/src/commands/compile/link/link_cache.rs b/crates/perry/src/commands/compile/link/link_cache.rs index e20d5071a32..ae781f9b03f 100644 --- a/crates/perry/src/commands/compile/link/link_cache.rs +++ b/crates/perry/src/commands/compile/link/link_cache.rs @@ -34,6 +34,8 @@ const LINK_ENV_VARS: &[&str] = &[ // changed value must miss the cache or diagnostic flags (`-why_live`) get // a stale cached binary back with no linker run at all. "PERRY_EXTRA_LINK_ARGS", + // Same for the pad objects placed before the runtime archive. + "PERRY_LINK_PAD", ]; #[derive(Debug, Clone)] diff --git a/crates/perry/src/commands/compile/link/mod.rs b/crates/perry/src/commands/compile/link/mod.rs index e8826d6ac7b..177177bb2a5 100644 --- a/crates/perry/src/commands/compile/link/mod.rs +++ b/crates/perry/src/commands/compile/link/mod.rs @@ -510,8 +510,24 @@ fn resolve_optional_framework_dir(env_name: &str, args_input: &Path) -> Option

Vec { - std::env::var("PERRY_EXTRA_LINK_ARGS") - .map(|extra| extra.split_whitespace().map(str::to_string).collect()) + split_link_env("PERRY_EXTRA_LINK_ARGS") +} + +/// `PERRY_LINK_PAD`: whitespace-separated pad objects placed on the link line +/// right after the compiled objects and before the runtime archive (and any +/// other library), on every final link perry runs. A measurement input for +/// startup A/Bs: the linker lays input sections out in command-line order, so +/// a pad here moves every runtime byte of the section it pads, which an +/// input appended last (`PERRY_EXTRA_LINK_ARGS`) cannot do. Not part of any +/// documented stable surface; unset, the link line is unchanged. One reader, +/// so no link path silently drops it. +pub(super) fn link_pad_args() -> Vec { + split_link_env("PERRY_LINK_PAD") +} + +fn split_link_env(name: &str) -> Vec { + std::env::var(name) + .map(|value| value.split_whitespace().map(str::to_string).collect()) .unwrap_or_default() } @@ -797,6 +813,23 @@ mod elf_archive_group_tests { } } +#[cfg(test)] +mod link_input_env_tests { + use super::split_link_env; + + /// The pad and extra-argument inputs share one reader: whitespace-separated, empty when unset. + /// (A name no perry code reads, so parallel tests cannot see it.) + #[test] + fn link_inputs_split_on_whitespace_and_default_to_empty() { + const NAME: &str = "PERRY_TEST_LINK_INPUT_SPLIT_ONLY"; + std::env::remove_var(NAME); + assert!(split_link_env(NAME).is_empty()); + std::env::set_var(NAME, " a.o\t-Wl,-x b.o\n"); + assert_eq!(split_link_env(NAME), ["a.o", "-Wl,-x", "b.o"]); + std::env::remove_var(NAME); + } +} + #[cfg(test)] mod optional_framework_dir_tests; diff --git a/crates/perry/src/commands/compile/link/wasi.rs b/crates/perry/src/commands/compile/link/wasi.rs index ecdb4ea4927..ef11baf01df 100644 --- a/crates/perry/src/commands/compile/link/wasi.rs +++ b/crates/perry/src/commands/compile/link/wasi.rs @@ -86,6 +86,7 @@ pub(super) fn link_args( format!("--sysroot={}", sdk.join("share/wasi-sysroot").display()), ]; args.extend(objects.iter().map(|o| o.display().to_string())); + args.extend(super::link_pad_args()); args.push(runtime.display().to_string()); if !keep_symbols { // Compression changes instruction offsets and cannot preserve DWARF. diff --git a/crates/perry/src/commands/compile/run_pipeline.rs b/crates/perry/src/commands/compile/run_pipeline.rs index 1c2f9939abb..38dbf77b94e 100644 --- a/crates/perry/src/commands/compile/run_pipeline.rs +++ b/crates/perry/src/commands/compile/run_pipeline.rs @@ -8304,6 +8304,7 @@ pub fn run_with_parse_cache( for obj_path in &obj_paths { cmd.arg(obj_path); } + cmd.args(link::link_pad_args()); if is_dylib_windows { // MSVC link.exe takes the output path as `/OUT:`, not `-o`. diff --git a/crates/perry/src/commands/compile/tiny_program/mod.rs b/crates/perry/src/commands/compile/tiny_program/mod.rs index 25855fee5d5..a6ab61682a3 100644 --- a/crates/perry/src/commands/compile/tiny_program/mod.rs +++ b/crates/perry/src/commands/compile/tiny_program/mod.rs @@ -115,7 +115,9 @@ pub(super) fn try_compile( if args.target.as_deref() == Some("macos") { command.arg("-mmacosx-version-min=15.0"); } - command.arg(&object_path).arg("-o").arg(&absolute_output); + command.arg(&object_path); + command.args(super::link::link_pad_args()); + command.arg("-o").arg(&absolute_output); if cfg!(target_os = "macos") { command.arg("-Wl,-dead_strip"); } else { diff --git a/scripts/a1/irdiff.sh b/scripts/a1/irdiff.sh index 197edf7cd3c..82b8fde877f 100755 --- a/scripts/a1/irdiff.sh +++ b/scripts/a1/irdiff.sh @@ -42,12 +42,15 @@ for f in "$FIXED" "$MAPS_SHIM" "$STR_PLAIN"; do [ -f "$f" ] || { echo "missing m PRELOAD="$FIXED:$MAPS_SHIM:$STR_PLAIN" # Link-pad control (on unless IRDIFF_LINKPAD=0): rows that follow addresses with equal call counts (mimalloc # page map, address-sorted ranges, libc alignment) move when one arm's sections are smaller. linkpad.py pads -# each arm with unreferenced retained sections linked last (PERRY_EXTRA_LINK_ARGS) until every paddable -# section ends at the same address in both arms, so later sections, segments and the heap start coincide; -# the layout proof is $OUT/.layout.txt and the pads are $OUT/pads.tsv. Nothing executes a pad. +# each arm with unreferenced retained sections placed by perry right after the compiled objects, before the +# runtime archive (PERRY_LINK_PAD), until the runtime archive's bytes start at the same address in every +# padded section, plus an end pad linked last (PERRY_EXTRA_LINK_ARGS) that equalizes section ends and the +# heap start when the runtime itself differs in size. The layout proof (markers, section ends, segment +# starts, image end, runtime symbols compared by address) is $OUT/.layout.txt and the pads are +# $OUT/pads.tsv. Nothing executes a pad. # Self-test of the control (A/A): IRDIFF_HEAD_LINK_ARGS links extra inputs (e.g. a perturbing pad object) # into the head arm only; with the same perry on both arms the unpadded run shows the address rows, the -# padded run must show 0. +# padded run must show 0. IRDIFF_HEAD_LINK_PAD does the same for inputs placed before the runtime archive. LINKPAD=${IRDIFF_LINKPAD:-1}; LINKPAD_PY=$(dirname "$(readlink -f "$0")")/linkpad.py # minimal environment of both the compile and the run (same for both arms) ENVV=(PATH=/usr/lib/llvm-22/bin:/root/.cargo/bin:/usr/bin:/bin HOME="$HOME" LANG=C LC_ALL=C TZ=UTC LD_PRELOAD="$PRELOAD" PERRY_ALLOW_PERRY_FEATURES=1 TMPDIR="$OUT/tmp" NO_COLOR=1 PKG_BENCH_N=0 PKG_BENCH_WARM=0) @@ -66,20 +69,26 @@ if [ $full = 1 ]; then "nextjs_hello|${IRDIFF_NEXT:-/root/secret-tests/nextjs-hello}|${IRDIFF_NEXT_SRC:-main.ts}|") fi -compile() { # arm perry ws name dir src [pad-object] -> $OUT/w/$n.$arm - local arm=$1 perry=$2 ws=$3 n=$4 d=$5 s=$6 pad=${7:--} extra=() link="" +compile() { # arm perry ws name dir src [mid-pad,end-pad] -> $OUT/w/$n.$arm + local arm=$1 perry=$2 ws=$3 n=$4 d=$5 s=$6 pad=${7:--,-} extra=() link="" mid=${7%%,*} end=${7##*,} + [ -d "$OUT/tmp/cache.$arm" ] && mv "$OUT/tmp/cache.$arm" "$OUT/tmp/cache" [ "$arm" = head ] && link=${IRDIFF_HEAD_LINK_ARGS:-} - [ "$pad" != - ] && link="$link $pad" + [ "$end" != - ] && link="$link $end" [ -n "${link// }" ] && extra=(PERRY_EXTRA_LINK_ARGS="${link# }") + [ "$mid" = - ] && mid="" + [ "$arm" = head ] && mid="${IRDIFF_HEAD_LINK_PAD:-} $mid" # a perturbation stands for compiled code: before the marker + mid=${mid# }; [ -n "${mid// }" ] && extra+=(PERRY_LINK_PAD="${mid% }") ( cd "$d" && env -i "${ENVV[@]}" "${extra[@]}" PERRY_RUNTIME_DIR=$(dirname "$perry") PERRY_WORKSPACE_ROOT=$ws PERRY_TARGET_CPU=x86-64-v3 PERRY_KEEP_SYMBOLS=1 \ "$perry" compile --cache-dir "$OUT/tmp/cache" "$s" -o "$OUT/w/$n" > "$OUT/cg/$n.$arm.compile.log" 2>&1 ) - local rc=$?; rm -rf "$OUT/tmp/cache"; [ $rc = 0 ] && mv "$OUT/w/$n" "$OUT/w/$n.$arm"; return $rc + # The cache path is part of generated symbol names, so both arms compile through the same path. The link-pad + # rounds relink the same objects: park the arm's object cache under its own name between compiles. + local rc=$?; if [ "$LINKPAD" = 1 ] && [ $rc = 0 ]; then mv "$OUT/tmp/cache" "$OUT/tmp/cache.$arm"; else rm -rf "$OUT/tmp/cache"; fi; [ $rc = 0 ] && mv "$OUT/w/$n" "$OUT/w/$n.$arm"; return $rc } # pads both arms of witness $n to the same layout (see LINKPAD); exit 1 if it does not converge linkpad() { # name dir src local n=$1 d=$2 s=$3 r res pm=- ph=- cm=- ch=- rm -f "$OUT/pad/$n.json" - for r in 1 2 3 4 5; do + for r in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do res=$(python3 "$LINKPAD_PY" plan "$OUT/w/$n.main" "$OUT/w/$n.head" "$OUT/pad/$n.json" "$OUT/pad") || return 1 [ "$res" = CONVERGED ] && break pm=$(sed 's/.*main=\([^ ]*\).*/\1/' <<< "$res"); ph=$(sed 's/.*head=\([^ ]*\).*/\1/' <<< "$res") @@ -87,7 +96,8 @@ linkpad() { # name dir src [ "$ph" != "$ch" ] && { compile head "$HEAD" "$WS_HEAD" "$n" "$d" "$s" "$ph" || return 1; ch=$ph; } done printf "%s\t%s\t%s\t%s\t%s\n" "$n" "$res" "$(basename "$cm")" "$(basename "$ch")" "$(tr -d ' \n' < "$OUT/pad/$n.json")" >> "$OUT/pads.tsv" - python3 "$LINKPAD_PY" check "$OUT/w/$n.main" "$OUT/w/$n.head" > "$OUT/$n.layout.txt" + rm -rf "$OUT/tmp/cache.main" "$OUT/tmp/cache.head" + python3 "$LINKPAD_PY" check "$OUT/w/$n.main" "$OUT/w/$n.head" "$(dirname "$MAIN")/libperry_runtime.a" > "$OUT/$n.layout.txt" } # per-function Ir of one callgrind run: fnir fn_ir() { # outfile @@ -127,14 +137,15 @@ for spec in "${W[@]}"; do IFS='|' read -r n d s a <<< "$spec" if [ -n "${IRDIFF_ONLY:-}" ] && [[ " $IRDIFF_ONLY " != *" $n "* ]]; then continue; fi if [ ! -f "$d/$s" ]; then echo "MISSING $n: no harness at $d/$s" | tee -a "$OUT/missing.txt"; printf "%s\tMISSING\tMISSING\t-\t-\n" "$n" >> "$OUT/summary.tsv"; continue; fi - compile main "$MAIN" "$WS_MAIN" "$n" "$d" "$s" || { echo "COMPILE-FAIL $n main"; fail=1; continue; } - compile head "$HEAD" "$WS_HEAD" "$n" "$d" "$s" || { echo "COMPILE-FAIL $n head"; fail=1; continue; } + zero=-,-; [ "$LINKPAD" = 1 ] && zero=$(python3 "$LINKPAD_PY" zero "$OUT/pad"),- + compile main "$MAIN" "$WS_MAIN" "$n" "$d" "$s" "$zero" || { echo "COMPILE-FAIL $n main"; fail=1; continue; } + compile head "$HEAD" "$WS_HEAD" "$n" "$d" "$s" "$zero" || { echo "COMPILE-FAIL $n head"; fail=1; continue; } if [ "$LINKPAD" = 1 ]; then - linkpad "$n" "$d" "$s" || { echo "LINKPAD-FAIL $n: layouts differ (see $OUT/$n.layout.txt, $OUT/pads.tsv)"; fail=1; continue; } + linkpad "$n" "$d" "$s" || { rm -rf "$OUT/tmp/cache.main" "$OUT/tmp/cache.head"; echo "LINKPAD-FAIL $n: layouts differ (see $OUT/$n.layout.txt, $OUT/pads.tsv)"; fail=1; continue; } fi tm=$(measure main "$n" "$d" "$a") || { fail=1; continue; } th=$(measure head "$n" "$d" "$a") || { fail=1; continue; } - rm -f "$OUT/w/$n" "$OUT/w/$n.main" "$OUT/w/$n.head" + [ -n "${IRDIFF_KEEP:-}" ] || rm -f "$OUT/w/$n" "$OUT/w/$n.main" "$OUT/w/$n.head" # IRDIFF_KEEP=1 keeps the linked arms awk -F'\t' 'NR==FNR { m[$1]=$2; next } { h[$1]=$2 } END { for (f in m) u[f]; for (f in h) u[f]; for (f in u) printf "%s\t%d\t%d\t%d\n", f, m[f]+0, h[f]+0, h[f]-m[f] }' \ "$OUT/cg/$n.main.fn" "$OUT/cg/$n.head.fn" | awk -F'\t' '$4!=0' | sort -t$'\t' -k4,4nr > "$OUT/$n.tsv" grep -v '^#' "$ALLOW" 2>/dev/null | sed '/^$/d' | sort -u > "$OUT/tmp/allow.$n" diff --git a/scripts/a1/linkpad.py b/scripts/a1/linkpad.py index 4448d1e97d6..6e9f5f9e108 100755 --- a/scripts/a1/linkpad.py +++ b/scripts/a1/linkpad.py @@ -1,43 +1,81 @@ #!/usr/bin/env python3 """scripts/a1/linkpad.py: link-pad control for startup A/Bs (nightly/CW/STARTUP_RULE_2026-10-28.md). -Some startup rows follow addresses, not code: when one arm's .text/.rodata/... is smaller, every later -section and the heap start lower (mimalloc page map, address-sorted ranges, libc alignment). The control -pads each arm so both get the same end address for every paddable output section, which makes every -later section, the segment starts and the heap start coincide. A pad is an object of unreferenced -SHF_GNU_RETAIN input sections (so --gc-sections keeps it) linked last through PERRY_EXTRA_LINK_ARGS; it -lands at the end of its output section and nothing executes or reads it. +Some startup rows follow addresses, not code: when one arm's compiled code or data is smaller, every +runtime byte after it (and the heap) starts lower (mimalloc page map, address-sorted ranges, libc +alignment, ld.so symbol lookup order). The control pads each arm so the runtime archive's bytes sit at +the same address in both arms. +Two pad objects per arm, both made of unreferenced SHF_GNU_RETAIN input sections nothing executes or reads: + + mid pad (PERRY_LINK_PAD) placed by perry right after the compiled objects and before the runtime + archive on every final link. The linker lays input sections out in + command-line order, so it moves the runtime's part of each output section. + It defines a marker symbol at the end of each padded section + (__lpad_mid_); the marker is where the runtime's bytes start. + end pad (PERRY_EXTRA_LINK_ARGS) linked last. Only matters when the runtime itself differs in size between + the arms: it makes every paddable section end, and the image end (heap + start), coincide anyway. + + linkpad.py zero + Write the mid pad with no extra bytes (markers and the .eh_frame filler only) and print its path. + Both arms are linked with it first, so every marker exists. linkpad.py plan Read both binaries (already carrying the pads in pads.json, if any), update pads.json so every - paddable section ends at the same address in both arms, write /-.o for each arm - with a nonzero pad and print "CONVERGED" when no pad changed, else "PADS main= head=". - linkpad.py check - Print the per-section layout comparison; exit 1 unless every paddable section ends at the same - address, all PT_LOAD segments start at the same address and the image ends at the same address. - -Only sections whose bytes are reached through pointers are padded. The runtime walks perry_* sections -(__start_/__stop_), .eh_frame and stack maps record by record, so those are never padded: a size -difference there is taken up by the next paddable section of the same segment. + marker and every paddable section end coincide, write /--.o for each + arm and print "CONVERGED" when no pad changed, else "PADS main=, head=," + ("-" for no end pad). + linkpad.py check [] + Print the layout comparison and, per output section, how many symbols defined in both binaries (and + in the runtime archive, when given) sit at the same address; exit 1 unless all markers and paddable + section ends coincide, all PT_LOAD segments start at the same address and the image ends at the same + address. + +.eh_frame is padded with one FDE (a retained function in .text.lpad); its marker is the end of that FDE, found +through the .eh_frame_hdr search table. Sections the runtime walks record by record (perry_*, stack maps) +and the linker-generated .eh_frame_hdr are never padded: their size differences are taken up by the next +padded section, so the runtime bytes after them still coincide, but the runtime records inside them move +with the compiled records in front of them. """ -import hashlib, json, os, subprocess, sys +import hashlib, json, os, struct, subprocess, sys -# input section name and assembler flags/type for each paddable output section +# output section -> (mid input section, flags, fill, marker) and (end input section) PADDABLE = { - ".text": ('.text.lpad', '"axR",@progbits', '0xcc'), - ".rodata": ('.rodata.lpad', '"aR",@progbits', '0'), - ".gcc_except_table": ('.gcc_except_table.lpad', '"aR",@progbits', '0'), - ".data.rel.ro": ('.data.rel.ro.lpad', '"awR",@progbits', '0'), - ".data": ('.data.lpad', '"awR",@progbits', '0'), - ".bss": ('.bss.lpad', '"awR",@nobits', None), + ".text": ('.text.lpad', '"axR",@progbits', '0xcc', '__lpad_mid_text', '.text.lpade'), + ".rodata": ('.rodata.lpad', '"aR",@progbits', '0', '__lpad_mid_rodata', '.rodata.lpade'), + ".gcc_except_table": ('.gcc_except_table.lpad', '"aR",@progbits', '0', '__lpad_mid_gcc_except_table', + '.gcc_except_table.lpade'), + ".data.rel.ro": ('.data.rel.ro.lpad', '"awR",@progbits', '0', '__lpad_mid_data_rel_ro', '.data.rel.ro.lpade'), + ".data": ('.data.lpad', '"awR",@progbits', '0', '__lpad_mid_data', '.data.lpade'), + ".bss": ('.bss.lpad', '"awR",@nobits', None, '__lpad_mid_bss', '.bss.lpade'), } +# SHF_MERGE blobs inside .rodata: the linker merges every input of one (flags, entsize) class into the blob of +# the first, compiled, input, so the runtime's constants and strings move whenever the compiled ones differ. +# A mid pad of unique entries between the compiled and the runtime inputs holds them in place. name -> (input +# section, flags, entsize); the marker is the last entry (a symbol past the end of a merge section is not mapped). +# The flags must equal the compiled inputs' for the linker to merge, so these sections cannot carry SHF_GNU_RETAIN; +# a PC-relative word to each marker in the retained .rodata.lpad keeps them through --gc-sections. +MERGES = { + "rodata.cst4": (".rodata.cst4", '"aM",@progbits,4', 4), + "rodata.cst8": (".rodata.cst8", '"aM",@progbits,8', 8), + "rodata.cst16": (".rodata.cst16", '"aM",@progbits,16', 16), + "rodata.cst32": (".rodata.cst32", '"aM",@progbits,32', 32), + "rodata.str1.1": (".rodata.str1.1", '"aMS",@progbits,1', 1), +} +EH = ".eh_frame" # mid pad only: one FDE of 24 + extra bytes, extra a multiple of 8, made of DW_CFA_GNU_args_size +# ops (the linker strips trailing DW_CFA_nop padding, so nops would not count) +EH_FN = "__lpad_eh_fn" +EH_MIN = 24 + + +def run(*cmd): + return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout def sections(path): - """Allocated sections in address order: [(name, addr, size)].""" - out = subprocess.run(["readelf", "-SW", path], check=True, capture_output=True, text=True).stdout + """Allocated sections in address order: [(name, addr, size, file offset)].""" secs = [] - for line in out.splitlines(): + for line in run("readelf", "-SW", path).splitlines(): line = line.strip() if not line.startswith("[") or "]" not in line: continue @@ -45,17 +83,16 @@ def sections(path): if len(f) < 7: continue try: - name, addr, size, flags = f[0], int(f[2], 16), int(f[4], 16), f[6] + name, addr, off, size, flags = f[0], int(f[2], 16), int(f[3], 16), int(f[4], 16), f[6] except ValueError: continue if "A" in flags and addr: - secs.append((name, addr, size)) + secs.append((name, addr, size, off)) return sorted(secs, key=lambda s: (s[1], s[0])) def loads(path): - out = subprocess.run(["readelf", "-lW", path], check=True, capture_output=True, text=True).stdout - return [(int(f[2], 16), int(f[5], 16), f[6]) for f in (l.split() for l in out.splitlines()) + return [(int(f[2], 16), int(f[5], 16), f[6]) for f in (l.split() for l in run("readelf", "-lW", path).splitlines()) if f and f[0] == "LOAD"] @@ -66,24 +103,65 @@ def notes(path): return [l.split()[1:5] for l in r.stdout.splitlines() if l.strip().startswith("0x")] -def paired(m, h): - names_m, names_h = [s[0] for s in m], [s[0] for s in h] - if names_m != names_h: - sys.exit(f"section order differs:\n main {names_m}\n head {names_h}") - return list(zip(m, h)) +def symbols(path): + """Defined symbols: {name: addr} (first definition wins).""" + out = {} + for line in run("nm", "--defined-only", path).splitlines(): + f = line.split(None, 2) + if len(f) == 3 and f[1] not in "UuwWvV": + out.setdefault(f[2], int(f[0], 16)) + return out -def write_pad(pads, arm, pad_dir): - want = {k: v for k, v in pads.get(arm, {}).items() if v > 0} - if not want: - return "-" - src = "" - for out_sec in PADDABLE: - if out_sec in want: - name, flags, fill = PADDABLE[out_sec] - src += f".section {name},{flags}\n" + (f".space {want[out_sec]},{fill}\n" if fill else f".zero {want[out_sec]}\n") - src += '.section .note.GNU-stack,"",@progbits\n' - obj = os.path.join(pad_dir, f"{arm}-{hashlib.sha256(src.encode()).hexdigest()[:16]}.o") +def eh_marker(path, secs, syms): + """End address of the pad FDE in .eh_frame: look its start up in the .eh_frame_hdr search table.""" + by = {s[0]: s for s in secs} + if EH_FN not in syms or ".eh_frame_hdr" not in by or EH not in by: + return None, 0 + hn, ha, hs, ho = by[".eh_frame_hdr"] + en, ea, es, eo = by[EH] + with open(path, "rb") as fh: + fh.seek(ho) + hdr = fh.read(hs) + ver, ptr_enc, cnt_enc, tab_enc = hdr[0], hdr[1], hdr[2], hdr[3] + if ver != 1 or cnt_enc != 0x03 or tab_enc != 0x3b or ptr_enc != 0x1b: + sys.exit(f"{path}: unexpected .eh_frame_hdr encoding {hdr[:4].hex()}") + count = struct.unpack_from(" 0}, pads[arm].get("fde", 0))) + esrc = end_source({k: v for k, v in pads[arm]["end"].items() if v > 0}) + return mid, (write_obj(f"{arm}-end", pad_dir, esrc) if esrc else "-") + + +# .data.rel.ro lies in the RELRO region, which the linker anchors at its END (page aligned): a pad in it moves +# the section's start, not its end. So the runtime bytes before the compiled ones need the sections' sizes equal +# (mid pad) and the bytes after them need the markers equal (end pad, which lowers the marker). +ANCHORED = ".data.rel.ro" + + +def level(pads, kind, n, am, ah): + r = ah - am + if r == 0: + return False + gran = 8 if n == EH else MERGES[n][2] if n in MERGES else 1 + if r % gran: + sys.exit(f"{n}: runtime start differs by {r} bytes: not a multiple of the {gran}-byte entry size") + big, small = ("head", "main") if r > 0 else ("main", "head") + r = abs(r) + # keep pads minimal: first take back the longer arm's own pad, then pad the shorter arm + back = min(r, pads[big][kind].get(n, 0)) + pads[big][kind][n] = pads[big][kind].get(n, 0) - back + pads[small][kind][n] = pads[small][kind].get(n, 0) + r - back + return True + + +def anchored(pads, mk_m, mk_h, size_m, size_h): + n = ANCHORED + old = {a: (pads[a]["end"].get(n, 0), pads[a]["mid"].get(n, 0)) for a in pads} + # marker = anchor - (runtime bytes after it + end pad): the arm with the higher marker gets the end pad + d = old["head"][0] - old["main"][0] + (mk_h - mk_m) + end = {"head": max(d, 0), "main": max(-d, 0)} + # start = anchor - size: the arm with the smaller section gets the mid pad + diff = (size_h + end["head"] - old["head"][0]) - (size_m + end["main"] - old["main"][0]) + d = old["main"][1] - old["head"][1] + diff + mid = {"main": max(d, 0), "head": max(-d, 0)} + changed = False + for a in pads: + changed |= (end[a], mid[a]) != old[a] + pads[a]["end"][n], pads[a]["mid"][n] = end[a], mid[a] + return changed + + def plan(main_bin, head_bin, pads_json, pad_dir): - pads = json.load(open(pads_json)) if os.path.exists(pads_json) else {"main": {}, "head": {}} + empty = lambda: {"mid": {}, "end": {}} + pads = json.load(open(pads_json)) if os.path.exists(pads_json) else {"main": empty(), "head": empty()} + secs_m, mk_m, cnt_m = marks(main_bin) + secs_h, mk_h, cnt_h = marks(head_bin) + names_m, names_h = [s[0] for s in secs_m], [s[0] for s in secs_h] + if names_m != names_h: + sys.exit(f"section order differs:\n main {names_m}\n head {names_h}") + missing = [n for n in list(PADDABLE) + [EH] if n in names_m and n not in mk_m or n in names_h and n not in mk_h] + missing += [n for n in MERGES if (n in mk_m) != (n in mk_h)] + if missing: + sys.exit(f"no mid-pad marker for {missing}: link both arms with the mid pad first (linkpad.py zero) and keep symbols") changed = False - # Walk the sections in address order. Until this round changes a pad, the observed end difference of a - # paddable section is exact. After that the binaries no longer show the new layout, so the difference - # is modelled: a padded section ends equal, and every later section starts equal plus the size - # differences of the non-paddable sections in between (alignment can absorb some of those; the next - # round sees what the linker did and corrects it). - carry = 0 - for (n, am, sm), (_, ah, sh) in paired(sections(main_bin), sections(head_bin)): - if n not in PADDABLE: - carry += sh - sm - continue - r = (ah + sh) - (am + sm) if not changed else carry + sh - sm - carry = 0 - if r == 0: - continue - big, small = ("head", "main") if r > 0 else ("main", "head") - r = abs(r) - # keep pads minimal: first take back the longer arm's own pad, then pad the shorter arm - back = min(r, pads[big].get(n, 0)) - pads[big][n] = pads[big].get(n, 0) - back - pads[small][n] = pads[small].get(n, 0) + r - back + # The unwinder binary-searches the .eh_frame_hdr table: both arms need the same number of FDEs, so the + # arm with fewer gets filler FDEs (each also adds 24 bytes of .eh_frame, which the section's pad absorbs). + fde = {a: pads[a].setdefault("fde", 0) for a in ("main", "head")} + d = fde["head"] - fde["main"] - (cnt_h - cnt_m) + new = {"head": max(d, 0), "main": max(-d, 0)} + if new != fde: + pads["main"]["fde"], pads["head"]["fde"] = new["main"], new["head"] changed = True + # Walk the sections in address order and fix the first one that differs; the sections after it are + # re-read from the relinked binaries in the next round (a pad moves everything after it, and alignment + # absorbs part of that, so modelling it makes the plan oscillate). + for n in sorted(mk_m, key=lambda k: mk_m[k][0]) if not changed else (): + (mm, em, sm), (mh, eh, sh) = mk_m[n], mk_h[n] + if n == ANCHORED: + changed = anchored(pads, mm, mh, em - sm if em else 0, eh - sh if eh else 0) + else: + changed = level(pads, "mid", n, mm, mh) + if not changed and em is not None: + changed = level(pads, "end", n, em, eh) + if changed: + break json.dump(pads, open(pads_json, "w"), indent=1, sort_keys=True) if not changed: print("CONVERGED") - else: - print(f"PADS main={write_pad(pads, 'main', pad_dir)} head={write_pad(pads, 'head', pad_dir)}") + return + (mm, em), (mh, eh) = write_arm(pads, "main", pad_dir), write_arm(pads, "head", pad_dir) + print(f"PADS main={mm},{em} head={mh},{eh}") -def check(main_bin, head_bin): +def check(main_bin, head_bin, rt_lib=None): ok = True - print(f"{'section':<24}{'addr main':>12}{'addr head':>12}{'size main':>11}{'size head':>11}") - for (n, am, sm), (_, ah, sh) in paired(sections(main_bin), sections(head_bin)): - mark = "" - if n in PADDABLE and am + sm != ah + sh: - mark, ok = " END DIFFERS", False - elif am != ah: - mark = " start moved (a non-paddable section before it differs in size)" - if am != ah or sm != sh or mark: - print(f"{n:<24}{am:>12x}{ah:>12x}{sm:>11x}{sh:>11x}{mark}") + secs_m, mk_m, cnt_m = marks(main_bin) + secs_h, mk_h, cnt_h = marks(head_bin) + names_m, names_h = [s[0] for s in secs_m], [s[0] for s in secs_h] + if names_m != names_h: + print(f"section order differs:\n main {names_m}\n head {names_h}") + print("LAYOUT-DIFFERS") + return 1 + print(f"{'section':<20}{'marker main':>13}{'marker head':>13}{'end main':>12}{'end head':>12}") + for n in sorted(mk_m, key=lambda k: mk_m[k][0]): + (mm, em, sm), (mh, eh, sh) = mk_m[n], mk_h[n] + # the RELRO region is anchored at its end: there the section start must coincide, not the end + bad = mm != mh or (n == ANCHORED and sm != sh) or (n != ANCHORED and em is not None and em != eh) + ok &= not bad + print(f"{n:<20}{mm:>13x}{mh:>13x}{(em or 0):>12x}{(eh or 0):>12x}" + (" DIFFERS" if bad else "")) + if cnt_m != cnt_h: + print(f"FDE count differs: main {cnt_m} head {cnt_h}") + ok = False lm, lh = loads(main_bin), loads(head_bin) - for (vm, mm, fm), (vh, mh, fh) in zip(lm, lh): + for (vm, mm_, fm), (vh, mh_, fh) in zip(lm, lh): if vm != vh or fm != fh: print(f"LOAD {fm} {vm:x} vs {fh} {vh:x} START DIFFERS") ok = False - em, eh = lm[-1][0] + lm[-1][1], lh[-1][0] + lh[-1][1] - print(f"image end (heap start side): main {em:x} head {eh:x}" + ("" if em == eh else " DIFFERS")) - if len(lm) != len(lh) or em != eh: + em_, eh_ = lm[-1][0] + lm[-1][1], lh[-1][0] + lh[-1][1] + print(f"image end (heap start side): main {em_:x} head {eh_:x}" + ("" if em_ == eh_ else " DIFFERS")) + if len(lm) != len(lh) or em_ != eh_: ok = False if notes(main_bin) != notes(head_bin): print(".note.gnu.property DIFFERS") ok = False + # runtime symbols: defined in both binaries (and in the runtime archive), same address? + sm, sh = symbols(main_bin), symbols(head_bin) + common = set(sm) & set(sh) + if rt_lib: + common &= {l.split(None, 2)[2] for l in run("nm", "--defined-only", rt_lib).splitlines() if len(l.split(None, 2)) == 3} + ranges = [(s[0], s[1], s[1] + s[2]) for s in secs_m] + + def sec_of(a): + return next((r[0] for r in ranges if r[1] <= a < r[2]), "?") + + per = {} + for name in common: + sec = sec_of(sm[name]) + t = per.setdefault(sec, [0, 0, []]) + t[0] += 1 + if sm[name] != sh[name]: + t[1] += 1 + t[2].append(name) + print(f"runtime symbols in both binaries{' and the runtime archive' if rt_lib else ''}: same address / compared, per section") + for sec, (cnt, moved, ex) in sorted(per.items(), key=lambda kv: -kv[1][0]): + print(f" {sec:<22}{cnt - moved:>8}/{cnt:<8}" + (f" moved e.g. {ex[0][:60]}" if moved else "")) print("LAYOUT-EQUAL" if ok else "LAYOUT-DIFFERS") return 0 if ok else 1 if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "" - if cmd == "plan" and len(sys.argv) == 6: + if cmd == "zero" and len(sys.argv) == 3: + print(write_obj("zero-mid", sys.argv[2], zero_text())) + elif cmd == "plan" and len(sys.argv) == 6: plan(*sys.argv[2:]) - elif cmd == "check" and len(sys.argv) == 4: + elif cmd == "check" and len(sys.argv) in (4, 5): sys.exit(check(*sys.argv[2:])) else: sys.exit(__doc__) From 7ead7bff0f6ca2ada95961e597d3681a7af262f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:46:19 +0100 Subject: [PATCH 005/144] changelog: number the PR fragment --- ...d2-pad-before-runtime.md => 13581-lpad2-pad-before-runtime.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-lpad2-pad-before-runtime.md => 13581-lpad2-pad-before-runtime.md} (100%) diff --git a/changelog.d/0-lpad2-pad-before-runtime.md b/changelog.d/13581-lpad2-pad-before-runtime.md similarity index 100% rename from changelog.d/0-lpad2-pad-before-runtime.md rename to changelog.d/13581-lpad2-pad-before-runtime.md From 698f3fc68c21380b73efe6779e4771286c55cd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:50:47 +0000 Subject: [PATCH 006/144] compile env: register PERRY_FACTS_CONSUMED as a diagnostics-only variable --- changelog.d/0-facts-consumed-env-registered.md | 5 +++++ crates/perry/src/commands/compile/compile_env.rs | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 changelog.d/0-facts-consumed-env-registered.md diff --git a/changelog.d/0-facts-consumed-env-registered.md b/changelog.d/0-facts-consumed-env-registered.md new file mode 100644 index 00000000000..16039564650 --- /dev/null +++ b/changelog.d/0-facts-consumed-env-registered.md @@ -0,0 +1,5 @@ +Register `PERRY_FACTS_CONSUMED` (the codegen-consumed marks of the facts +dump) with the compile environment as a diagnostics-only variable, like +`PERRY_FACTS_DUMP`: it records which sites read a program fact and changes +no emitted code, so it is not a build-cache input. Fixes the environment +audit test on main. diff --git a/crates/perry/src/commands/compile/compile_env.rs b/crates/perry/src/commands/compile/compile_env.rs index bbda77ed13b..91fe45de27b 100644 --- a/crates/perry/src/commands/compile/compile_env.rs +++ b/crates/perry/src/commands/compile/compile_env.rs @@ -391,6 +391,10 @@ const COMPILE_ENV_EXCLUSIONS: &[(&str, &str)] = &[ "PERRY_EVAL_DIAG", "Prints lowering diagnostics; no emitted instructions depend on the report.", ), + ( + "PERRY_FACTS_CONSUMED", + "Diagnostics/analysis-only output of the program-facts pass; emitted code does not depend on it.", + ), ( "PERRY_FACTS_DUMP", "Diagnostics/analysis-only output of the program-facts pass; emitted code does not depend on it.", From 330a5fa0109ee4def7839ec14384a07fad073253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:52:51 +0100 Subject: [PATCH 007/144] changelog: number the PR fragment --- ...d-env-registered.md => 13583-facts-consumed-env-registered.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-facts-consumed-env-registered.md => 13583-facts-consumed-env-registered.md} (100%) diff --git a/changelog.d/0-facts-consumed-env-registered.md b/changelog.d/13583-facts-consumed-env-registered.md similarity index 100% rename from changelog.d/0-facts-consumed-env-registered.md rename to changelog.d/13583-facts-consumed-env-registered.md From d408a4a73802cf9077a7ad1be960d38a441679fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:31:48 +0000 Subject: [PATCH 008/144] fix(runtime): stack-overflow guard under ulimit -s unlimited An unlimited stack rlimit gave the main thread a bound as wide as the gap to the next mapping, so the guard sat terabytes down and deep recursion hit SIGSEGV instead of throwing RangeError. The main-thread bound now falls back to the kernel default stack size when the rlimit is not a usable size, and the derivation covers every Linux libc. --- changelog.d/usg-unlimited-stack.md | 1 + crates/perry-runtime/src/native_stack.rs | 120 +++++++++++++++++------ scripts/check_unlimited_stack.sh | 24 +++++ 3 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 changelog.d/usg-unlimited-stack.md create mode 100755 scripts/check_unlimited_stack.sh diff --git a/changelog.d/usg-unlimited-stack.md b/changelog.d/usg-unlimited-stack.md new file mode 100644 index 00000000000..35d054a1af9 --- /dev/null +++ b/changelog.d/usg-unlimited-stack.md @@ -0,0 +1 @@ +The stack-overflow guard now works when the stack limit is unlimited (`ulimit -s unlimited`): runaway recursion throws a catchable `RangeError: Maximum call stack size exceeded` instead of dying with SIGSEGV. The main-thread stack bound is derived from the kernel's own stack mapping, and an unlimited (or address-space-wide) limit uses the kernel's default 8 MiB stack size. diff --git a/crates/perry-runtime/src/native_stack.rs b/crates/perry-runtime/src/native_stack.rs index b0e8131bd2d..34cbdef3937 100644 --- a/crates/perry-runtime/src/native_stack.rs +++ b/crates/perry-runtime/src/native_stack.rs @@ -16,25 +16,36 @@ /// string (`AT_EXECFN`), the first thing the kernel copies onto the new stack /// (`bprm->p` starts at the mapping end minus one word); /// * the stack may grow to `RLIMIT_STACK` below that end, the kernel's own -/// check, which is the limit glibc's maps lookup reports as well. +/// check, which is the limit glibc's maps lookup reports as well; +/// * when the rlimit is not a size the mapping can have (`RLIM_INFINITY`, or +/// one reaching the address-space bottom) the kernel lays the address space +/// out bottom-up and the stack may grow into whatever is free, down to a +/// neighbouring mapping that can be terabytes away. glibc and musl then +/// report that gap as the stack size, which puts the overflow guard so far +/// down that the process exhausts memory or hits a mapping first (SIGSEGV). +/// The bound is then the kernel's own default stack limit, +/// [`UNLIMITED_MAIN_STACK`], the size a process has when nobody set one. /// -/// The low bound this yields equals glibc's on every rlimit and environment size -/// tried (the top differs from glibc's by at most one page). The derivation is -/// checked against the one fact the process knows independently -/// (`__libc_stack_end`, the entry stack pointer, lies below the mapping end -/// by less than the stack size); anything that fails the check, a missing -/// `AT_EXECFN`, and an unlimited (or address-space-wide) rlimit, whose bound is -/// whatever mapping happens to sit below the stack and which only the maps -/// lookup knows, keep asking pthread, exactly as before. +/// The low bound this yields equals glibc's on every finite rlimit and +/// environment size tried (the top differs from glibc's by at most one page). +/// The derivation is checked against the one fact the process knows +/// independently (a live address of this very stack lies below the mapping end +/// by less than the stack size); anything that fails the check, or a missing +/// `AT_EXECFN`, keeps asking pthread, exactly as before. #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) fn stack_bounds() -> Option<(usize, usize)> { - #[cfg(all(target_os = "linux", target_env = "gnu"))] + #[cfg(target_os = "linux")] if let Some(bounds) = main_thread_bounds() { return Some(bounds); } pthread_bounds() } +/// The kernel's default `RLIMIT_STACK` (`_STK_LIM`, 8 MiB): the main-thread +/// stack size when the rlimit gives no usable bound. +#[cfg(target_os = "linux")] +const UNLIMITED_MAIN_STACK: usize = 8 << 20; + /// Return the exclusive upper bound of this thread's native stack, or zero /// when it cannot be supplied. Callers use zero to abandon the stack walk. #[cfg(target_os = "linux")] @@ -46,15 +57,10 @@ pub(crate) fn stack_top() -> usize { } /// The process main thread's bounds from `AT_EXECFN` and the stack rlimit; -/// `None` on any other thread, when the rlimit is not a size, or when the -/// derived mapping end fails its consistency check. -#[cfg(all(target_os = "linux", target_env = "gnu"))] +/// `None` on any other thread or when the derived mapping end fails its +/// consistency check. +#[cfg(target_os = "linux")] fn main_thread_bounds() -> Option<(usize, usize)> { - extern "C" { - /// glibc: the stack pointer at process entry (set by the loader; also - /// present in a static glibc). - static __libc_stack_end: *mut libc::c_void; - } // The main thread is the one whose tid is the pid. Every other thread // (the runtime's own, or an embedder's) takes the pthread path, whose // answer is read from its descriptor. @@ -71,10 +77,6 @@ fn main_thread_bounds() -> Option<(usize, usize)> { } rl.assume_init() }; - if rl.rlim_cur == libc::RLIM_INFINITY { - return None; - } - let size = usize::try_from(rl.rlim_cur).ok()?; // SAFETY: AT_EXECFN is a NUL-terminated string the kernel placed on the // stack; 0 means the entry is absent. let execfn = unsafe { libc::getauxval(libc::AT_EXECFN) } as usize; @@ -87,14 +89,45 @@ fn main_thread_bounds() -> Option<(usize, usize)> { let end = execfn .checked_add(len + 1)? .checked_next_multiple_of(page)?; + derive_main_bounds(rl.rlim_cur, end, live_stack_address()) +} + +/// An address inside the calling (main) thread's stack, for the consistency +/// check: glibc records the stack pointer at process entry (set by the loader; +/// also present in a static glibc, and no lookup is needed to read it). +#[cfg(all(target_os = "linux", target_env = "gnu"))] +fn live_stack_address() -> usize { + extern "C" { + static __libc_stack_end: *mut libc::c_void; + } // SAFETY: written once by the loader before any user code runs. - let entry_sp = unsafe { __libc_stack_end } as usize; - // The entry stack pointer lies inside the mapping, below its end by the - // argv/envp/auxv blocks, which are smaller than the stack itself. - if size == 0 || end < entry_sp || end - entry_sp >= size || end <= size { + unsafe { __libc_stack_end as usize } +} + +/// An address inside the calling thread's stack (libcs without +/// `__libc_stack_end`). +#[cfg(all(target_os = "linux", not(target_env = "gnu")))] +fn live_stack_address() -> usize { + let here = 0u8; + std::hint::black_box(&here) as *const u8 as usize +} + +/// The main thread's `(low, size)` from the rlimit, the mapping end and a live +/// address `here` of the stack; `None` when they disagree. +#[cfg(target_os = "linux")] +fn derive_main_bounds(rlim_cur: libc::rlim_t, end: usize, here: usize) -> Option<(usize, usize)> { + // RLIM_INFINITY is above every mapping end, so one comparison covers it. + let size = match usize::try_from(rlim_cur) { + Ok(size) if size < end => size, + _ => UNLIMITED_MAIN_STACK, + }; + // The live address lies inside the mapping, strictly above its low end + // (which also rules out a zero size) and below its end. + let low = end.checked_sub(size)?; + if here <= low || here >= end { return None; } - Some((end - size, size)) + Some((low, size)) } #[cfg(any(target_os = "linux", target_os = "android"))] @@ -121,7 +154,38 @@ fn pthread_bounds() -> Option<(usize, usize)> { #[cfg(all(test, target_os = "linux"))] mod tests { - use super::stack_top; + use super::{derive_main_bounds, stack_top, UNLIMITED_MAIN_STACK}; + + const END: usize = 0x7ffc_0000_0000; + + #[test] + fn a_finite_rlimit_is_the_stack_size() { + let size = 64 << 20; + assert_eq!( + derive_main_bounds(size as libc::rlim_t, END, END - 4096), + Some((END - size, size)) + ); + } + + /// `ulimit -s unlimited` (and a limit reaching the address-space bottom) + /// must still give a finite bound the overflow guard can sit above. + #[test] + fn an_unusable_rlimit_gets_the_kernel_default_stack() { + for rlim in [libc::RLIM_INFINITY, END as libc::rlim_t, !0 >> 1] { + assert_eq!( + derive_main_bounds(rlim, END, END - 4096), + Some((END - UNLIMITED_MAIN_STACK, UNLIMITED_MAIN_STACK)), + "rlimit {rlim:#x}" + ); + } + } + + #[test] + fn a_live_address_outside_the_derived_stack_is_refused() { + let far = END - UNLIMITED_MAIN_STACK - 4096; + assert_eq!(derive_main_bounds(libc::RLIM_INFINITY, END, far), None); + assert_eq!(derive_main_bounds(libc::RLIM_INFINITY, END, END), None); + } #[test] fn stack_top_encloses_a_current_thread_local() { diff --git a/scripts/check_unlimited_stack.sh b/scripts/check_unlimited_stack.sh new file mode 100755 index 00000000000..7dfd1844cf2 --- /dev/null +++ b/scripts/check_unlimited_stack.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# USG: the stack-overflow guard must work under every stack limit. Compiles the +# #10812 recursion gap test, runs it under the default limit and under +# `ulimit -s unlimited`, and requires the same output (RangeError caught, the +# program keeps running) and exit 0 from both. +# usage: scripts/check_unlimited_stack.sh (PERRY_RUNTIME_DIR as for any compile) +set -u +perry=${1:?usage: check_unlimited_stack.sh } +root=$(cd "$(dirname "$0")/.." && pwd) +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +"$perry" compile "$root/test-files/test_gap_10812_stack_overflow_range_error.ts" -o "$work/rec" >/dev/null 2>&1 || { echo "FAIL: compile"; exit 1; } +status=0 +for limit in 8192 unlimited; do + (ulimit -v 8000000; ulimit -s "$limit"; timeout 120 "$work/rec" >"$work/out-$limit" 2>&1) + rc=$? + if [ "$rc" != 0 ] || ! grep -q '^function: true Maximum call stack size exceeded$' "$work/out-$limit"; then + echo "FAIL: ulimit -s $limit rc=$rc"; status=1 + else + echo "ok: ulimit -s $limit" + fi +done +cmp -s "$work/out-8192" "$work/out-unlimited" || { echo "FAIL: output differs between limits"; status=1; } +exit $status From 59f8ebca2826ff4fc423bb9fb18d5dce2f26e64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:32:05 +0000 Subject: [PATCH 009/144] changelog: name the fragment for numbering --- changelog.d/{usg-unlimited-stack.md => 0-usg-unlimited-stack.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{usg-unlimited-stack.md => 0-usg-unlimited-stack.md} (100%) diff --git a/changelog.d/usg-unlimited-stack.md b/changelog.d/0-usg-unlimited-stack.md similarity index 100% rename from changelog.d/usg-unlimited-stack.md rename to changelog.d/0-usg-unlimited-stack.md From 19a8e4697ec78f7e2cb5a2177daa24a78f538fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:33:58 +0100 Subject: [PATCH 010/144] changelog: number the PR fragment --- .../{0-usg-unlimited-stack.md => 13580-usg-unlimited-stack.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-usg-unlimited-stack.md => 13580-usg-unlimited-stack.md} (100%) diff --git a/changelog.d/0-usg-unlimited-stack.md b/changelog.d/13580-usg-unlimited-stack.md similarity index 100% rename from changelog.d/0-usg-unlimited-stack.md rename to changelog.d/13580-usg-unlimited-stack.md From 6d545a2d89f545896cb779e4d695aa86fb02d06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 15:46:48 +0000 Subject: [PATCH 011/144] fix(runtime): CreateDataProperty defines from a descriptor record, not a descriptor object A public class field is CreateDataPropertyOrThrow. Its generic route built a four-key JS descriptor object (four key strings, four named stores) and handed it to Reflect.defineProperty, which decoded it back. A callable class instance (ArkType's Callable: a bound function with the class prototype) declines every shape-owned define arm, so each of its fields paid both: 24k Ir per field. The decoded view already has the record form (DescView::create_data, used by the receiver tail of [[Set]]): define from it. --- .../src/object/property_ops/set.rs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/perry-runtime/src/object/property_ops/set.rs b/crates/perry-runtime/src/object/property_ops/set.rs index bc739360aa7..8e5adb44c37 100644 --- a/crates/perry-runtime/src/object/property_ops/set.rs +++ b/crates/perry-runtime/src/object/property_ops/set.rs @@ -798,18 +798,24 @@ pub(crate) unsafe fn build_create_data_descriptor(value: f64) -> f64 { /// receiver's `[[DefineOwnProperty]]`. This is the operation used by public /// class fields: it bypasses inherited setters on ordinary objects and still /// drives a Proxy's `defineProperty` trap. +/// +/// The descriptor is a Property Descriptor record, not a JS object: no +/// descriptor object is allocated, and nothing is read back through +/// `%Object.prototype%` (an inherited `get` cannot turn it into an accessor). pub(crate) fn create_data_property(receiver: f64, key: f64, value: f64) -> bool { - let scope = crate::gc::RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(receiver); - let key = scope.root_nanbox_f64(key); - let value = scope.root_nanbox_f64(value); - let descriptor = unsafe { build_create_data_descriptor(value.get_nanbox_f64()) }; - let descriptor = scope.root_nanbox_f64(descriptor); - crate::value::js_is_truthy(js_reflect_define_property( - receiver.get_nanbox_f64(), - key.get_nanbox_f64(), - descriptor.get_nanbox_f64(), - )) != 0 + unsafe { + if !crate::object::object_ops::definition_target_is_object(receiver) { + reflect_non_object_typeerror("defineProperty"); + } + let scope = crate::gc::RuntimeHandleScope::new(); + let receiver = + scope.root_nanbox_f64(crate::object::object_ops::normalize_descriptor_operand(receiver)); + let value = scope.root_nanbox_f64(value); + let key = scope.root_nanbox_f64(crate::object::js_to_property_key(key)); + let descriptor = + crate::object::object_ops::DescView::create_data(&scope, value.get_nanbox_f64()); + crate::object::object_ops::define_own_property_decoded(&scope, &receiver, &key, &descriptor) + } } /// #5129: build a `{ value }`-only property descriptor — the `valueDesc` of From eb927c2c8c874fd70319acb67ddfbf0a56339ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 17:43:04 +0000 Subject: [PATCH 012/144] test(gap): class fields define from a record on callable and plain receivers --- changelog.d/0-create-data-property-record.md | 11 ++++ .../test_gap_class_field_define_record.ts | 54 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 changelog.d/0-create-data-property-record.md create mode 100644 test-files/test_gap_class_field_define_record.ts diff --git a/changelog.d/0-create-data-property-record.md b/changelog.d/0-create-data-property-record.md new file mode 100644 index 00000000000..d1c34709dd2 --- /dev/null +++ b/changelog.d/0-create-data-property-record.md @@ -0,0 +1,11 @@ +`CreateDataProperty` (public class fields, and the other runtime callers of +the operation) defines from a Property Descriptor record instead of building a +four-key JS descriptor object and decoding it back through +`Reflect.defineProperty`. A class field on a callable class instance (a bound +function given the class prototype, as ArkType's `Callable` builds) declines +every shape-owned define and paid both steps: 24k instructions per field, +1.57 G instructions of the Supabase CLI's `--version`. It is now about 10k. +Because nothing is read back through `%Object.prototype%`, an inherited `get` +or `set` there no longer turns a class field into an invalid accessor +descriptor; `test_gap_class_field_define_record` pins that and the field +attributes on callable and plain receivers. diff --git a/test-files/test_gap_class_field_define_record.ts b/test-files/test_gap_class_field_define_record.ts new file mode 100644 index 00000000000..5a719049a50 --- /dev/null +++ b/test-files/test_gap_class_field_define_record.ts @@ -0,0 +1,54 @@ +// A public class field is CreateDataPropertyOrThrow with a Property Descriptor +// record: nothing is read through %Object.prototype%, on any receiver kind. +class Callable { + constructor(fn: (...a: any[]) => any) { + return Object.setPrototypeOf(fn.bind(this), new.target.prototype); + } +} +class Node extends Callable { + kind = "node"; + count = 0; + constructor() { + super((x: any) => x); + } +} +class Plain { + a = 1; + b = "two"; +} +function show(label: string, o: any, key: string) { + const d = Object.getOwnPropertyDescriptor(o, key)!; + console.log(label, key, JSON.stringify(o[key]), d.writable, d.enumerable, d.configurable, "get" in d); +} +const before = new Node() as any; +show("callable", before, "kind"); +show("callable", before, "count"); +(Object.prototype as any).get = function () { + return "polluted"; +}; +(Object.prototype as any).set = function () {}; +try { + const n = new Node() as any; + show("callable+pollution", n, "kind"); + console.log(typeof n, n(5), Object.keys(n).join(",")); + const p = new Plain() as any; + show("plain+pollution", p, "a"); + show("plain+pollution", p, "b"); +} catch (e: any) { + console.log("threw", e.constructor.name); +} +delete (Object.prototype as any).get; +delete (Object.prototype as any).set; +const frozen = Object.freeze(() => 0); +class OnFrozen extends Callable { + x = 1; + constructor() { + super(frozen); + } +} +try { + new OnFrozen(); + console.log("no throw"); +} catch (e: any) { + console.log("frozen target", e.constructor.name); +} From 21dd89d0cb4829c9581f58245671bf1b619e0d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 22:01:35 +0100 Subject: [PATCH 013/144] changelog: number the PR fragment --- ...ta-property-record.md => 13585-create-data-property-record.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-create-data-property-record.md => 13585-create-data-property-record.md} (100%) diff --git a/changelog.d/0-create-data-property-record.md b/changelog.d/13585-create-data-property-record.md similarity index 100% rename from changelog.d/0-create-data-property-record.md rename to changelog.d/13585-create-data-property-record.md From 842490baca4a3789bbf5b2e8ebe7ba029a99aaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:38:39 +0000 Subject: [PATCH 014/144] runtime: state the PerryLayoutKey.off offset per pointer width (wasm32 build) The const assert on PerryLayoutKey.off compared against one 64-bit value (14), so perry-runtime failed to compile for wasm32, where the key is { ptr4, u32, u8, u8, u16 } and off sits at byte 10. State the offset and the key size for LP64 (14/16) and ILP32 (10/12) like the descriptor constants, and check the emitted %PerryLayoutKey LLVM type against both. --- changelog.d/0-wasi-layout-key-assert.md | 8 +++++ crates/perry-abi/src/layouts.rs | 7 ++++- crates/perry-codegen/src/layout_desc.rs | 2 +- crates/perry-codegen/src/target_layout.rs | 29 +++++++++++++++++++ .../perry-runtime/src/object/layout_desc.rs | 18 ++++++++++-- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 changelog.d/0-wasi-layout-key-assert.md diff --git a/changelog.d/0-wasi-layout-key-assert.md b/changelog.d/0-wasi-layout-key-assert.md new file mode 100644 index 00000000000..e559495c227 --- /dev/null +++ b/changelog.d/0-wasi-layout-key-assert.md @@ -0,0 +1,8 @@ +Fix the wasm32 (WASI) runtime build: the compile-time check of the +`PerryLayoutKey.off` offset used the 64-bit value (14) on every target, but +with 32-bit pointers the field sits at byte 10, so `perry-runtime` failed to +compile for wasm32-wasip1/wasip2 and the release build-wasi leg went red. The +offset and the key size are now stated per pointer width (14/16 on LP64, +10/12 on ILP32), matching the descriptor constants beside them, and a codegen +test checks the emitted `%PerryLayoutKey` LLVM type has the same offset and +size under both data layouts. Native targets are unchanged. diff --git a/crates/perry-abi/src/layouts.rs b/crates/perry-abi/src/layouts.rs index b5920d1fb8d..d962d75dfe6 100644 --- a/crates/perry-abi/src/layouts.rs +++ b/crates/perry-abi/src/layouts.rs @@ -236,7 +236,12 @@ pub const LAYOUT_DESC_ID_OFFSET_ILP32: usize = 56; pub const LAYOUT_DESC_SIZE_LP64: usize = 80; pub const LAYOUT_DESC_SIZE_ILP32: usize = 64; /// `PerryLayoutKey.off`: the key's absolute slot offset from the object (u16). -pub const LAYOUT_KEY_OFF_OFFSET: usize = 14; +/// It follows `atom` (a pointer), `slot` (u32), `attrs` and `_pad` (u8 each). +pub const LAYOUT_KEY_OFF_OFFSET_LP64: usize = 14; +pub const LAYOUT_KEY_OFF_OFFSET_ILP32: usize = 10; +/// `PerryLayoutKey` is 16 bytes on LP64 (12 on ILP32). +pub const LAYOUT_KEY_SIZE_LP64: usize = 16; +pub const LAYOUT_KEY_SIZE_ILP32: usize = 12; /// Offsets of the A1 precomputes (`gc_header_word`) appended after `image_keys` /// (`i64` aligned to 8 on every target). pub const LAYOUT_DESC_GC_HEADER_WORD_OFFSET_LP64: usize = 88; diff --git a/crates/perry-codegen/src/layout_desc.rs b/crates/perry-codegen/src/layout_desc.rs index e1541707049..df794216be4 100644 --- a/crates/perry-codegen/src/layout_desc.rs +++ b/crates/perry-codegen/src/layout_desc.rs @@ -32,7 +32,7 @@ use crate::strings::StringPool; pub const DESC_TY: &str = "{ i32, i16, i8, i8, i32, i32, i64, ptr, ptr, i32, i32, ptr, i32, i32, ptr, i32, i32, ptr, i64, [32 x i8] }"; pub(crate) mod methods; -/// `%PerryLayoutKey`: atom, slot, attrs, pad, off (16 bytes). +/// `%PerryLayoutKey`: atom, slot, attrs, pad, off (16 bytes on LP64, 12 on ILP32). pub const KEY_TY: &str = "{ ptr, i32, i8, i8, i16 }"; /// The descriptor tail after `image_keys`: `gc_header_word` for a layout of `slot_count` slots. diff --git a/crates/perry-codegen/src/target_layout.rs b/crates/perry-codegen/src/target_layout.rs index 3bbbe3c9729..4f25259172e 100644 --- a/crates/perry-codegen/src/target_layout.rs +++ b/crates/perry-codegen/src/target_layout.rs @@ -744,6 +744,35 @@ mod dense_layout_tests { } } +#[cfg(all(test, feature = "llvm-inprocess"))] +#[test] +fn emitted_key_type_has_exact_llvm_target_layout() { + use inkwell::{context::Context, targets::TargetData}; + for (layout, size, off) in [ + ( + "e-p:64:64-i64:64", + perry_abi::LAYOUT_KEY_SIZE_LP64, + perry_abi::LAYOUT_KEY_OFF_OFFSET_LP64, + ), + ( + "e-p:32:32-i64:64", + perry_abi::LAYOUT_KEY_SIZE_ILP32, + perry_abi::LAYOUT_KEY_OFF_OFFSET_ILP32, + ), + ] { + let context = Context::create(); + let ir = format!( + "%Key = type {}\n@key = global %Key zeroinitializer\n", + crate::layout_desc::KEY_TY + ); + let module = crate::inprocess::parse_ir_text(&context, &ir, "key_offsets").unwrap(); + let ty = module.get_struct_type("Key").unwrap(); + let data = TargetData::create(layout); + assert_eq!(data.get_abi_size(&ty), size as u64); + assert_eq!(data.offset_of_element(&ty, 4), Some(off as u64)); + } +} + #[cfg(all(test, feature = "llvm-inprocess"))] #[test] fn emitted_descriptor_type_has_exact_llvm_target_layout() { diff --git a/crates/perry-runtime/src/object/layout_desc.rs b/crates/perry-runtime/src/object/layout_desc.rs index f974fca8149..b00916dd932 100644 --- a/crates/perry-runtime/src/object/layout_desc.rs +++ b/crates/perry-runtime/src/object/layout_desc.rs @@ -345,8 +345,22 @@ const _: () = assert!( crate::codegen_abi::LAYOUT_DESC_GC_HEADER_WORD_OFFSET_ILP32 } ); -const _: () = - assert!(std::mem::offset_of!(PerryLayoutKey, off) == crate::codegen_abi::LAYOUT_KEY_OFF_OFFSET); +const _: () = assert!( + std::mem::offset_of!(PerryLayoutKey, off) + == if cfg!(target_pointer_width = "64") { + crate::codegen_abi::LAYOUT_KEY_OFF_OFFSET_LP64 + } else { + crate::codegen_abi::LAYOUT_KEY_OFF_OFFSET_ILP32 + } +); +const _: () = assert!( + std::mem::size_of::() + == if cfg!(target_pointer_width = "64") { + crate::codegen_abi::LAYOUT_KEY_SIZE_LP64 + } else { + crate::codegen_abi::LAYOUT_KEY_SIZE_ILP32 + } +); const _: () = assert!( std::mem::offset_of!(PerryLayoutDesc, image_keys) From b3d3507aced55e0a2b26d4f532cd5e7617383a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:47:46 +0000 Subject: [PATCH 015/144] ci(release-wasi): admit perry_host.discard_pages in the wasip1 import check The wasip1 archive carries the opt-in wasm-discard member, which imports perry_host.discard_pages; the release step checked it with --allow-perry-net only and so rejected the archive it ships. Pass --allow-perry-host as test.yml already does. --- .github/workflows/release-wasi.yml | 7 ++++--- changelog.d/0-wasi-layout-key-assert.md | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-wasi.yml b/.github/workflows/release-wasi.yml index 179d87be701..d6a8cbfe505 100644 --- a/.github/workflows/release-wasi.yml +++ b/.github/workflows/release-wasi.yml @@ -108,9 +108,10 @@ jobs: run: | set -euo pipefail archive="$(python3 scripts/wasi_build_runtime.py wasm32-wasip1 | sed -n 's/^WASI archive: //p' | tail -n 1)" - # The optional networking member ships in the archive; default - # linked modules still omit it unless PERRY_WASM_NET=1 is selected. - python3 scripts/wasi_imports_check.py "$archive" --allow-perry-net + # The optional networking and page-discard members ship in the + # archive; default linked modules still omit them unless + # PERRY_WASM_NET=1 / PERRY_WASM_DISCARD=1 is selected. + python3 scripts/wasi_imports_check.py "$archive" --allow-perry-net --allow-perry-host python3 scripts/wasi_release.py package --target wasm32-wasip1 --revision "$EXPECTED_SHA" \ --runtime "$archive" --output wasip1-release - name: Upload the wasip1 runtime bundle diff --git a/changelog.d/0-wasi-layout-key-assert.md b/changelog.d/0-wasi-layout-key-assert.md index e559495c227..246a32f2622 100644 --- a/changelog.d/0-wasi-layout-key-assert.md +++ b/changelog.d/0-wasi-layout-key-assert.md @@ -6,3 +6,8 @@ offset and the key size are now stated per pointer width (14/16 on LP64, 10/12 on ILP32), matching the descriptor constants beside them, and a codegen test checks the emitted `%PerryLayoutKey` LLVM type has the same offset and size under both data layouts. Native targets are unchanged. + +The release wasip1 step now admits `perry_host.discard_pages` in its import +check (`--allow-perry-host`, as the test workflow already does): the one +wasip1 archive has carried the opt-in page-discard member since it landed, +so the check rejected the archive it was meant to ship. From ab685afddeacf4399c450f98ccfa13001c929732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 22:56:01 +0100 Subject: [PATCH 016/144] changelog: number the PR fragment --- ...-wasi-layout-key-assert.md => 13586-wasi-layout-key-assert.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wasi-layout-key-assert.md => 13586-wasi-layout-key-assert.md} (100%) diff --git a/changelog.d/0-wasi-layout-key-assert.md b/changelog.d/13586-wasi-layout-key-assert.md similarity index 100% rename from changelog.d/0-wasi-layout-key-assert.md rename to changelog.d/13586-wasi-layout-key-assert.md From 8a6b0b1ad238762ed8ce9a36171ba84479c0d7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 22:38:10 +0000 Subject: [PATCH 017/144] linkpad: equalize the RELRO size and the aligned TLS size between the arms; layout check compares RELRO/TLS/RW starts --- changelog.d/0-lpad3-relro.md | 1 + scripts/a1/irdiff.sh | 2 +- scripts/a1/linkpad.py | 121 ++++++++++++++++++++++++++++------- 3 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 changelog.d/0-lpad3-relro.md diff --git a/changelog.d/0-lpad3-relro.md b/changelog.d/0-lpad3-relro.md new file mode 100644 index 00000000000..fb356f1dd09 --- /dev/null +++ b/changelog.d/0-lpad3-relro.md @@ -0,0 +1 @@ +startup A/B link pad (scripts/a1/linkpad.py): equalizes the RELRO region size (end pad then mid pad in .data.rel.ro) and the aligned TLS size (.tbss end pad), so the RW LOAD start, .tdata, .tbss, .init_array, .fini_array and .data.rel.ro starts coincide when one arm has a larger .got; the layout check compares them; the planner runs up to 30 rounds. Measurement script only, no change to shipping links. diff --git a/scripts/a1/irdiff.sh b/scripts/a1/irdiff.sh index 82b8fde877f..72886c1b6f3 100755 --- a/scripts/a1/irdiff.sh +++ b/scripts/a1/irdiff.sh @@ -88,7 +88,7 @@ compile() { # arm perry ws name dir src [mid-pad,end-pad] -> $OUT/w/$n.$arm linkpad() { # name dir src local n=$1 d=$2 s=$3 r res pm=- ph=- cm=- ch=- rm -f "$OUT/pad/$n.json" - for r in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do + for r in $(seq 1 30); do res=$(python3 "$LINKPAD_PY" plan "$OUT/w/$n.main" "$OUT/w/$n.head" "$OUT/pad/$n.json" "$OUT/pad") || return 1 [ "$res" = CONVERGED ] && break pm=$(sed 's/.*main=\([^ ]*\).*/\1/' <<< "$res"); ph=$(sed 's/.*head=\([^ ]*\).*/\1/' <<< "$res") diff --git a/scripts/a1/linkpad.py b/scripts/a1/linkpad.py index 6e9f5f9e108..2e85181ba59 100755 --- a/scripts/a1/linkpad.py +++ b/scripts/a1/linkpad.py @@ -28,8 +28,10 @@ linkpad.py check [] Print the layout comparison and, per output section, how many symbols defined in both binaries (and in the runtime archive, when given) sit at the same address; exit 1 unless all markers and paddable - section ends coincide, all PT_LOAD segments start at the same address and the image ends at the same - address. + section ends coincide, all PT_LOAD segments start at the same address, the GNU_RELRO region and the + TLS segment start at the same address with the same (TLS: aligned) size, .tdata/.tbss/.init_array/ + .fini_array/.data.rel.ro start at the same address, the thread-pointer-relative address of every TLS symbol both + binaries and the runtime archive define is reported, and the image ends at the same address. .eh_frame is padded with one FDE (a retained function in .text.lpad); its marker is the end of that FDE, found through the .eh_frame_hdr search table. Sections the runtime walks record by record (perry_*, stack maps) @@ -62,6 +64,12 @@ "rodata.cst32": (".rodata.cst32", '"aM",@progbits,32', 32), "rodata.str1.1": (".rodata.str1.1", '"aMS",@progbits,1', 1), } +# The TLS block is addressed from the thread pointer by the segment's size rounded up to its alignment, so a +# thread-local added to one arm's runtime shifts EVERY thread-local's address when it crosses a 64-byte boundary. +# An end pad (nobits, TLS) in .tbss raises the smaller arm's TLS size to the larger one's. It takes no address +# space and is linked last, so only the (unavoidable) offsets after the added variable differ. +TLS_PAD = ".tbss" +TLS_END = ".tbss.lpade" EH = ".eh_frame" # mid pad only: one FDE of 24 + extra bytes, extra a multiple of 8, made of DW_CFA_GNU_args_size # ops (the linker strips trailing DW_CFA_nop padding, so nops would not count) EH_FN = "__lpad_eh_fn" @@ -96,6 +104,30 @@ def loads(path): if f and f[0] == "LOAD"] +def segments(path): + """{type: (vaddr, memsz, align)} of the GNU_RELRO and TLS program headers.""" + out = {} + for l in run("readelf", "-lW", path).splitlines(): + f = l.split() + if f and f[0] in ("GNU_RELRO", "TLS"): + out[f[0]] = (int(f[2], 16), int(f[5], 16), int(f[-1], 16)) + return out + + +def tls_symbols(path): + """{name: offset in the TLS segment} of every defined TLS symbol (nm prints these offsets as addresses).""" + out = {} + for line in run("readelf", "-sW", path).splitlines(): + f = line.split() + if len(f) >= 8 and f[3] == "TLS" and f[6] != "UND": + out.setdefault(f[7], int(f[1], 16)) + return out + + +# sections that follow the RELRO start: their starts move with the RELRO size +RW_SECTIONS = (".tdata", ".tbss", ".init_array", ".fini_array", ".data.rel.ro") + + def notes(path): """The GNU property note: a pad object must not change the linked features (IBT/SHSTK, ISA).""" r = subprocess.run(["readelf", "-x", ".note.gnu.property", path], capture_output=True, text=True) @@ -218,6 +250,8 @@ def end_source(want): for n, (_, flags, fill, _, name) in PADDABLE.items(): if want.get(n): src += f".section {name},{flags}\n" + (f".space {want[n]},{fill}\n" if fill else f".zero {want[n]}\n") + if want.get(TLS_PAD): + src += f'.section {TLS_END},"awRT",@nobits\n.zero {want[TLS_PAD]}\n' return src + '.section .note.GNU-stack,"",@progbits\n' if src else "" @@ -227,12 +261,6 @@ def write_arm(pads, arm, pad_dir): return mid, (write_obj(f"{arm}-end", pad_dir, esrc) if esrc else "-") -# .data.rel.ro lies in the RELRO region, which the linker anchors at its END (page aligned): a pad in it moves -# the section's start, not its end. So the runtime bytes before the compiled ones need the sections' sizes equal -# (mid pad) and the bytes after them need the markers equal (end pad, which lowers the marker). -ANCHORED = ".data.rel.ro" - - def level(pads, kind, n, am, ah): r = ah - am if r == 0: @@ -249,21 +277,26 @@ def level(pads, kind, n, am, ah): return True -def anchored(pads, mk_m, mk_h, size_m, size_h): +# .data.rel.ro lies in the RELRO region, which the linker anchors at its END (page aligned): a pad in it moves +# the section start, not its end. The region also holds what follows the section (.dynamic, .got, the first +# .got.plt slots), whose size differs between arms (link-time-resolved GOT slots; the 64-byte section alignment +# turns 0x18 more .got into a 0x40 lower RW LOAD start, .tdata, .tbss, .init_array, .fini_array and .data.rel.ro). +# So the section END differs between arms and equal section sizes do not give equal starts. Two knobs: +# end pad lowers the marker (the runtime's bytes start at: section end - runtime tail - end pad) +# mid pad lowers the section start and the RW start, and leaves the marker where it is +# Equal markers first (end pad), then equal section starts (mid pad); the RELRO size then coincides as well. +ANCHORED = ".data.rel.ro" + + +def anchored(pads, mk_m, mk_h, st_m, st_h): n = ANCHORED old = {a: (pads[a]["end"].get(n, 0), pads[a]["mid"].get(n, 0)) for a in pads} - # marker = anchor - (runtime bytes after it + end pad): the arm with the higher marker gets the end pad - d = old["head"][0] - old["main"][0] + (mk_h - mk_m) - end = {"head": max(d, 0), "main": max(-d, 0)} - # start = anchor - size: the arm with the smaller section gets the mid pad - diff = (size_h + end["head"] - old["head"][0]) - (size_m + end["main"] - old["main"][0]) - d = old["main"][1] - old["head"][1] + diff - mid = {"main": max(d, 0), "head": max(-d, 0)} - changed = False - for a in pads: - changed |= (end[a], mid[a]) != old[a] - pads[a]["end"][n], pads[a]["mid"][n] = end[a], mid[a] - return changed + for kind, i, diff in (("end", 0, mk_h - mk_m), ("mid", 1, st_h - st_m)): + if diff: + d = old["head"][i] - old["main"][i] + diff + pads["head"][kind][n], pads["main"][kind][n] = max(d, 0), max(-d, 0) + return True + return False def plan(main_bin, head_bin, pads_json, pad_dir): @@ -293,13 +326,26 @@ def plan(main_bin, head_bin, pads_json, pad_dir): for n in sorted(mk_m, key=lambda k: mk_m[k][0]) if not changed else (): (mm, em, sm), (mh, eh, sh) = mk_m[n], mk_h[n] if n == ANCHORED: - changed = anchored(pads, mm, mh, em - sm if em else 0, eh - sh if eh else 0) + changed = anchored(pads, mm, mh, sm, sh) else: changed = level(pads, "mid", n, mm, mh) if not changed and em is not None: changed = level(pads, "end", n, em, eh) if changed: break + if not changed: + gm, gh = segments(main_bin).get("TLS"), segments(head_bin).get("TLS") + if gm and gh: + base = {a: g[1] - pads[a]["end"].get(TLS_PAD, 0) for a, g in (("main", gm), ("head", gh))} + up = lambda z, al: -(-z // al) * al + if up(base["main"], gm[2]) != up(base["head"], gh[2]): + big, small = ("head", "main") if base["head"] > base["main"] else ("main", "head") + want = {small: -(-(base[big] - base[small]) // 8) * 8, big: 0} + else: + want = {"main": 0, "head": 0} + for a in pads: + changed |= pads[a]["end"].get(TLS_PAD, 0) != want[a] + pads[a]["end"][TLS_PAD] = want[a] json.dump(pads, open(pads_json, "w"), indent=1, sort_keys=True) if not changed: print("CONVERGED") @@ -332,6 +378,27 @@ def check(main_bin, head_bin, rt_lib=None): if vm != vh or fm != fh: print(f"LOAD {fm} {vm:x} vs {fh} {vh:x} START DIFFERS") ok = False + # the RELRO region (anchored at its page-aligned end: its size sets the RW start), the TLS segment and the + # start of every section placed at the RW start + gm, gh = segments(main_bin), segments(head_bin) + for k in ("GNU_RELRO", "TLS"): + if k not in gm or k not in gh: + if (k in gm) != (k in gh): + print(f"{k}: present in one binary only") + ok = False + continue + (vm, zm, am), (vh, zh, ah) = gm[k], gh[k] + # the TLS block is addressed from the thread pointer by the aligned size: only that must coincide + if k == "TLS": + zm, zh = -(-zm // am) * am, -(-zh // ah) * ah + bad = vm != vh or zm != zh or am != ah + ok &= not bad + print(f"{k:<10} start main {vm:x} head {vh:x} size{' (aligned)' if k == 'TLS' else ''} main {zm:x} head {zh:x}" + (" DIFFERS" if bad else "")) + sm_, sh_ = {s[0]: s[1] for s in secs_m}, {s[0]: s[1] for s in secs_h} + for n in RW_SECTIONS: + if n in sm_ and sm_[n] != sh_.get(n): + print(f"{n} starts main {sm_[n]:x} head {sh_.get(n, 0):x} DIFFERS") + ok = False em_, eh_ = lm[-1][0] + lm[-1][1], lh[-1][0] + lh[-1][1] print(f"image end (heap start side): main {em_:x} head {eh_:x}" + ("" if em_ == eh_ else " DIFFERS")) if len(lm) != len(lh) or em_ != eh_: @@ -360,6 +427,16 @@ def sec_of(a): print(f"runtime symbols in both binaries{' and the runtime archive' if rt_lib else ''}: same address / compared, per section") for sec, (cnt, moved, ex) in sorted(per.items(), key=lambda kv: -kv[1][0]): print(f" {sec:<22}{cnt - moved:>8}/{cnt:<8}" + (f" moved e.g. {ex[0][:60]}" if moved else "")) + # TLS symbols (.tdata/.tbss): same offset in the TLS segment + tm, th = tls_symbols(main_bin), tls_symbols(head_bin) + tcommon = set(tm) & set(th) + if rt_lib: + tcommon &= {l.split(None, 2)[2] for l in run("nm", "--defined-only", rt_lib).splitlines() if len(l.split(None, 2)) == 3} + # thread-pointer-relative address: offset - TLS size (aligned). A thread-local the runtimes differ by shifts + # the ones after it by its size: reported, not gated (the TLS start and aligned size above are) + zt = lambda g: -(-g["TLS"][1] // g["TLS"][2]) * g["TLS"][2] if "TLS" in g else 0 + tmoved = sorted(n for n in tcommon if tm[n] - zt(gm) != th[n] - zt(gh)) + print(f"TLS symbols (.tdata/.tbss), thread-pointer-relative: same {len(tcommon) - len(tmoved)}/{len(tcommon)}" + (f" moved e.g. {tmoved[0][:60]}" if tmoved else "")) print("LAYOUT-EQUAL" if ok else "LAYOUT-DIFFERS") return 0 if ok else 1 From d0abc905771117fbe45d00c4c611a8693def6555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 23:40:36 +0100 Subject: [PATCH 018/144] changelog: number the PR fragment --- changelog.d/{0-lpad3-relro.md => 13587-lpad3-relro.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-lpad3-relro.md => 13587-lpad3-relro.md} (100%) diff --git a/changelog.d/0-lpad3-relro.md b/changelog.d/13587-lpad3-relro.md similarity index 100% rename from changelog.d/0-lpad3-relro.md rename to changelog.d/13587-lpad3-relro.md From a2f13983b14a52c7a7170d05deebad084ac78724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 22:26:16 +0000 Subject: [PATCH 019/144] perry-runtime lib tests: pass in release and perry-dev at default threads GC liveness counters count in every lib-test build; tests that read process-wide state run in their own process; TestOpsGuard serializes component host-ops installs; CI full tier runs release lib tests. --- .github/workflows/test.yml | 6 + changelog.d/0-rlt-release-lib-tests.md | 1 + .../component/guest/resource_scope_tests.rs | 42 ++--- .../src/component/guest/tests.rs | 150 +++++++++--------- crates/perry-runtime/src/component/host.rs | 28 +++- crates/perry-runtime/src/event_pump.rs | 86 +++++----- .../src/event_pump/agent_loop_tests.rs | 70 ++++---- crates/perry-runtime/src/gc/instruments.rs | 55 +++---- .../src/iterator_helpers/tests.rs | 4 +- crates/perry-runtime/src/lib.rs | 138 ++++++++-------- .../src/object/shapes_store/dense_tests.rs | 20 +-- crates/perry-runtime/src/test_isolation.rs | 37 +++++ crates/perry-runtime/src/tls_hot.rs | 104 ++++++------ .../perry-runtime/src/typed_feedback/tests.rs | 26 +-- 14 files changed, 426 insertions(+), 341 deletions(-) create mode 100644 changelog.d/0-rlt-release-lib-tests.md create mode 100644 crates/perry-runtime/src/test_isolation.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f4a2b9d101..f1b481f3402 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1632,6 +1632,12 @@ jobs: # Match the PR lane: Blaze cannot unwind through the naked ABI # trampoline. Keep the full unit/integration/doc test inventory. MBX_BLAZE=0 RUST_TEST_THREADS=1 python3 scripts/runtime_test_batches.py + # The batches above are the dev profile. Release builds drop the + # debug-only GC instruments and change inlining, so a unit test + # can pass in dev and fail in release (59 did, unseen for weeks). + # Default thread count on purpose: a test that needs exclusive + # process state must serialize itself (`test_isolation`). + MBX_BLAZE=0 cargo test --locked --release -p perry-runtime --lib fi # `cargo test` only builds lib/bin/test targets — NOT the `staticlib` # crate-type — so libperry_runtime.a / libperry_stdlib.a are never diff --git a/changelog.d/0-rlt-release-lib-tests.md b/changelog.d/0-rlt-release-lib-tests.md new file mode 100644 index 00000000000..db8c82f2ecb --- /dev/null +++ b/changelog.d/0-rlt-release-lib-tests.md @@ -0,0 +1 @@ +perry-runtime's lib tests pass in the release and perry-dev profiles with the default thread count. The GC liveness counters (`copying_minor_cycles` and friends) now count in every lib-test build, not only debug ones, which is what 59 moving-GC tests read; shipping archives are unchanged and a compile-time assert keeps their counters zero-sized. Tests that assert on process-wide state (the event-pump wake flag, the has-active registries, typed-feedback counters, thread-slot claims, the guest host tables) run in their own process through `test_isolation::in_own_process`, and `TestOpsGuard` serializes component host-ops installs, which stops an uncaught throw in one `http::pump` test from aborting the whole test binary. CI's full-tier run now also runs the runtime lib tests in release. diff --git a/crates/perry-runtime/src/component/guest/resource_scope_tests.rs b/crates/perry-runtime/src/component/guest/resource_scope_tests.rs index 373840a077a..12b7db68108 100644 --- a/crates/perry-runtime/src/component/guest/resource_scope_tests.rs +++ b/crates/perry-runtime/src/component/guest/resource_scope_tests.rs @@ -49,25 +49,27 @@ fn canonical_return_expires_only_its_borrows_while_task_values_remain_rooted() { #[test] fn canonical_borrow_cleanup_preserves_an_earlier_error_and_drops_each_handle_once() { - crate::gc::gc_init(); - let scope = RuntimeHandleScope::new(); - let _registry = Registry::new(&scope); - let _ops = TestOps::new(record_borrow_drop); - unsafe { - let task = abi::perry_component_frame_task_new(); - for _ in 0..2 { - perry_component_frame_resource_lift(task, 4, 1, 0, 0); + crate::test_isolation::in_own_process(|| { + crate::gc::gc_init(); + let scope = RuntimeHandleScope::new(); + let _registry = Registry::new(&scope); + let _ops = TestOps::new(record_borrow_drop); + unsafe { + let task = abi::perry_component_frame_task_new(); + for _ in 0..2 { + perry_component_frame_resource_lift(task, 4, 1, 0, 0); + } + let error = scope.root_nanbox_f64(crate::value::js_nanbox_string( + crate::string::js_string_from_bytes(b"original".as_ptr(), 8) as i64, + )); + abi::frame(task).guard::<()>(|_| crate::exception::js_throw(error.get_nanbox_f64())); + assert_eq!(abi::perry_component_frame_resource_scope_end(task), -1); + assert_eq!( + abi::frame(task).error().unwrap().to_bits(), + error.get_nanbox_f64().to_bits() + ); + abi::perry_component_frame_drop(task); + assert_eq!(COLLECTED.load(Ordering::SeqCst), 1 << 8); } - let error = scope.root_nanbox_f64(crate::value::js_nanbox_string( - crate::string::js_string_from_bytes(b"original".as_ptr(), 8) as i64, - )); - abi::frame(task).guard::<()>(|_| crate::exception::js_throw(error.get_nanbox_f64())); - assert_eq!(abi::perry_component_frame_resource_scope_end(task), -1); - assert_eq!( - abi::frame(task).error().unwrap().to_bits(), - error.get_nanbox_f64().to_bits() - ); - abi::perry_component_frame_drop(task); - assert_eq!(COLLECTED.load(Ordering::SeqCst), 1 << 8); - } + }); } diff --git a/crates/perry-runtime/src/component/guest/tests.rs b/crates/perry-runtime/src/component/guest/tests.rs index 1b85b0f30f9..7159f622c43 100644 --- a/crates/perry-runtime/src/component/guest/tests.rs +++ b/crates/perry-runtime/src/component/guest/tests.rs @@ -17,85 +17,87 @@ unsafe extern "C" fn echo( #[test] fn imports_work_during_initialization_before_exports_are_registered() { - let scope = RuntimeHandleScope::new(); - let global = scope.root_nanbox_f64(crate::object::js_get_global_this()); - let previous = scope.root_nanbox_f64(maybe_registry()); - let ops = ComponentGuestOps { - abi_version: GUEST_ABI_VERSION, - size: std::mem::size_of::(), - contract: CONTRACT.as_ptr(), - contract_len: CONTRACT.len(), - import: echo, - resource: unused_resource, - }; - struct Restore<'a> { - global: crate::gc::RuntimeHandle<'a>, - registry: crate::gc::RuntimeHandle<'a>, - ops: *mut ComponentGuestOps, - } - impl Drop for Restore<'_> { - fn drop(&mut self) { - GUEST_OPS.store(self.ops, Ordering::Release); - unsafe { - bag::store( - object_pointer(self.global.get_nanbox_f64()).unwrap() as usize, - COMPONENT_GUEST, - self.registry.get_nanbox_f64(), - ); + crate::test_isolation::in_own_process(|| { + let scope = RuntimeHandleScope::new(); + let global = scope.root_nanbox_f64(crate::object::js_get_global_this()); + let previous = scope.root_nanbox_f64(maybe_registry()); + let ops = ComponentGuestOps { + abi_version: GUEST_ABI_VERSION, + size: std::mem::size_of::(), + contract: CONTRACT.as_ptr(), + contract_len: CONTRACT.len(), + import: echo, + resource: unused_resource, + }; + struct Restore<'a> { + global: crate::gc::RuntimeHandle<'a>, + registry: crate::gc::RuntimeHandle<'a>, + ops: *mut ComponentGuestOps, + } + impl Drop for Restore<'_> { + fn drop(&mut self) { + GUEST_OPS.store(self.ops, Ordering::Release); + unsafe { + bag::store( + object_pointer(self.global.get_nanbox_f64()).unwrap() as usize, + COMPONENT_GUEST, + self.registry.get_nanbox_f64(), + ); + } } } - } - let _restore = Restore { - global, - registry: previous, - ops: GUEST_OPS.swap(std::ptr::null_mut(), Ordering::AcqRel), - }; - unsafe { - bag::store( - object_pointer(_restore.global.get_nanbox_f64()).unwrap() as usize, - COMPONENT_GUEST, - undefined(), - ); - assert_eq!(js_component_set_guest_ops(&ops), 1); - } - let outcome = crate::exception::catch_js_throw(|| { - let text = |text: &[u8]| { - scope.root_nanbox_f64(crate::value::js_nanbox_string( - crate::string::js_string_from_bytes(text.as_ptr(), text.len() as u32) as i64, - )) + let _restore = Restore { + global, + registry: previous, + ops: GUEST_OPS.swap(std::ptr::null_mut(), Ordering::AcqRel), }; - let interface = text(b""); - let function = text(b"echo"); - let value = text("called before register 🌍".as_bytes()); - let args = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::array::js_array_alloc_with_length(1) as i64, - )); - crate::array::js_array_set_f64( - array_pointer(args.get_nanbox_f64()).unwrap(), - 0, - value.get_nanbox_f64(), - ); - let output = js_component_import( - interface.get_nanbox_f64(), - function.get_nanbox_f64(), - args.get_nanbox_f64(), + unsafe { + bag::store( + object_pointer(_restore.global.get_nanbox_f64()).unwrap() as usize, + COMPONENT_GUEST, + undefined(), + ); + assert_eq!(js_component_set_guest_ops(&ops), 1); + } + let outcome = crate::exception::catch_js_throw(|| { + let text = |text: &[u8]| { + scope.root_nanbox_f64(crate::value::js_nanbox_string( + crate::string::js_string_from_bytes(text.as_ptr(), text.len() as u32) as i64, + )) + }; + let interface = text(b""); + let function = text(b"echo"); + let value = text("called before register 🌍".as_bytes()); + let args = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( + crate::array::js_array_alloc_with_length(1) as i64, + )); + crate::array::js_array_set_f64( + array_pointer(args.get_nanbox_f64()).unwrap(), + 0, + value.get_nanbox_f64(), + ); + let output = js_component_import( + interface.get_nanbox_f64(), + function.get_nanbox_f64(), + args.get_nanbox_f64(), + ); + assert_eq!(string(output), "called before register 🌍"); + let contract = text(CONTRACT); + let exports = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( + crate::object::js_object_alloc(0, 0) as i64, + )); + js_component_register(contract.get_nanbox_f64(), exports.get_nanbox_f64()); + assert!(crate::exception::catch_js_throw(|| js_component_register( + contract.get_nanbox_f64(), + exports.get_nanbox_f64() + )) + .is_err()); + }); + assert!( + outcome.is_ok(), + "initialization import or registration threw" ); - assert_eq!(string(output), "called before register 🌍"); - let contract = text(CONTRACT); - let exports = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::object::js_object_alloc(0, 0) as i64, - )); - js_component_register(contract.get_nanbox_f64(), exports.get_nanbox_f64()); - assert!(crate::exception::catch_js_throw(|| js_component_register( - contract.get_nanbox_f64(), - exports.get_nanbox_f64() - )) - .is_err()); }); - assert!( - outcome.is_ok(), - "initialization import or registration threw" - ); } unsafe extern "C" fn unused_resource(_: *mut std::ffi::c_void, _: u32, _: u32, _: u32) -> u32 { diff --git a/crates/perry-runtime/src/component/host.rs b/crates/perry-runtime/src/component/host.rs index 4679cc21bdc..8d15acb4884 100644 --- a/crates/perry-runtime/src/component/host.rs +++ b/crates/perry-runtime/src/component/host.rs @@ -5,21 +5,37 @@ use std::sync::atomic::{AtomicPtr, Ordering}; static OPS: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +/// Installs a test table into the process-global `OPS` and holds +/// `TEST_OPS_LOCK` until dropped: two tests with different tables would +/// otherwise swap and restore each other's pointer, and a test whose `ops()` +/// read sees null or a foreign table throws uncaught (the process exits and +/// the thread-local destructors abort the whole lib-test binary). #[cfg(test)] -pub(super) struct TestOpsGuard(*mut ComponentHostOps); +pub(super) struct TestOpsGuard { + previous: *mut ComponentHostOps, + _serial: std::sync::MutexGuard<'static, ()>, +} +#[cfg(test)] +static TEST_OPS_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); #[cfg(test)] impl TestOpsGuard { pub fn install(ops: &'static ComponentHostOps) -> Self { - Self(OPS.swap( - (ops as *const ComponentHostOps).cast_mut(), - Ordering::AcqRel, - )) + let serial = TEST_OPS_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + Self { + previous: OPS.swap( + (ops as *const ComponentHostOps).cast_mut(), + Ordering::AcqRel, + ), + _serial: serial, + } } } #[cfg(test)] impl Drop for TestOpsGuard { fn drop(&mut self) { - OPS.store(self.0, Ordering::Release); + OPS.store(self.previous, Ordering::Release); } } diff --git a/crates/perry-runtime/src/event_pump.rs b/crates/perry-runtime/src/event_pump.rs index b5ed37ddc31..d49e8521eb4 100644 --- a/crates/perry-runtime/src/event_pump.rs +++ b/crates/perry-runtime/src/event_pump.rs @@ -1093,49 +1093,51 @@ pub(crate) mod tests { /// timers polluting the budget, and reaps its own timer afterward. #[test] fn wait_returns_when_timer_due() { - let _g = serial(); - let mut last_elapsed = Duration::ZERO; - let mut blocked_for_timer = false; - for _ in 0..40 { - // Drain any already-expired timer (left by a parallel test or a - // prior attempt) so it can't pin the budget at 0, and consume any - // stale notify so the wait blocks on our timer rather than a - // leftover flag. - crate::timer::js_timer_tick(); - NOTIFIED.store(false, Ordering::Release); - // Schedule a timer 50ms out so wait_for_event uses 50ms as budget. - crate::timer::js_set_timeout(50.0); - // js_set_timeout / the drain above can flip NOTIFIED via promise - // resolution; clear it once more immediately before the wait. - NOTIFIED.store(false, Ordering::Release); - let start = Instant::now(); - js_wait_for_event(); - last_elapsed = start.elapsed(); - // Reap our 50ms timer so it can't leak a due deadline into the - // next attempt or a later serialized test. - std::thread::sleep(Duration::from_millis(60)); - crate::timer::js_timer_tick(); - // #1444: the lower bound (≥40ms) is the real spec — the wait - // *blocked* for the ~50ms timer budget rather than spinning or - // returning instantly. The upper bound only guards against a wait - // that never returns; the old 500ms cap false-failed on - // overloaded runners where a 50ms `wait_timeout` oversleeps well - // past 500ms (the 1016s-vs-451s slow-runner signature in #1444), - // so every attempt landed "too late" and the retry loop exhausted. - // 5s tolerates that oversleep while still catching a truly stuck - // wait. - if (Duration::from_millis(40)..Duration::from_secs(5)).contains(&last_elapsed) { - blocked_for_timer = true; - break; + crate::test_isolation::in_own_process(|| { + let _g = serial(); + let mut last_elapsed = Duration::ZERO; + let mut blocked_for_timer = false; + for _ in 0..40 { + // Drain any already-expired timer (left by a parallel test or a + // prior attempt) so it can't pin the budget at 0, and consume any + // stale notify so the wait blocks on our timer rather than a + // leftover flag. + crate::timer::js_timer_tick(); + NOTIFIED.store(false, Ordering::Release); + // Schedule a timer 50ms out so wait_for_event uses 50ms as budget. + crate::timer::js_set_timeout(50.0); + // js_set_timeout / the drain above can flip NOTIFIED via promise + // resolution; clear it once more immediately before the wait. + NOTIFIED.store(false, Ordering::Release); + let start = Instant::now(); + js_wait_for_event(); + last_elapsed = start.elapsed(); + // Reap our 50ms timer so it can't leak a due deadline into the + // next attempt or a later serialized test. + std::thread::sleep(Duration::from_millis(60)); + crate::timer::js_timer_tick(); + // #1444: the lower bound (≥40ms) is the real spec — the wait + // *blocked* for the ~50ms timer budget rather than spinning or + // returning instantly. The upper bound only guards against a wait + // that never returns; the old 500ms cap false-failed on + // overloaded runners where a 50ms `wait_timeout` oversleeps well + // past 500ms (the 1016s-vs-451s slow-runner signature in #1444), + // so every attempt landed "too late" and the retry loop exhausted. + // 5s tolerates that oversleep while still catching a truly stuck + // wait. + if (Duration::from_millis(40)..Duration::from_secs(5)).contains(&last_elapsed) { + blocked_for_timer = true; + break; + } + // Woken early (concurrent notify / sooner parallel timer) or + // absurdly late (>5s stall) — retry for a clean window. } - // Woken early (concurrent notify / sooner parallel timer) or - // absurdly late (>5s stall) — retry for a clean window. - } - assert!( - blocked_for_timer, - "wait never blocked for the ~50ms timer budget across retries; last: {:?}", - last_elapsed - ); + assert!( + blocked_for_timer, + "wait never blocked for the ~50ms timer budget across retries; last: {:?}", + last_elapsed + ); + }); } /// #1114 spec: a *transient* budget-0 return stays zero-latency, but diff --git a/crates/perry-runtime/src/event_pump/agent_loop_tests.rs b/crates/perry-runtime/src/event_pump/agent_loop_tests.rs index 4020a2514c3..495b5d3ab3e 100644 --- a/crates/perry-runtime/src/event_pump/agent_loop_tests.rs +++ b/crates/perry-runtime/src/event_pump/agent_loop_tests.rs @@ -361,40 +361,44 @@ fn a_worker_agent_gets_its_own_loop() { /// primary agent's route. #[test] fn sibling_worker_agents_do_not_share_a_loop() { - let _g = serial(); - let before = routed_agents(); - let (a_tx, a_rx) = mpsc::channel(); - let (go_tx, go_rx) = mpsc::channel::<()>(); - let a = std::thread::spawn(move || { - let agent = crate::agent::enter_worker_agent(); - assert!(ensure_loop()); - let loop_id = route_loop_id(agent); - assert_ne!(loop_id, 0, "no endpoint published for this agent"); - a_tx.send((agent, loop_id)).unwrap(); - // Hold the loop until the sibling has built its own, so both exist at - // once — a sequential pair would pass even with one shared route. - go_rx.recv().unwrap(); - crate::agent::retire_agent(agent); - }); - let (a_agent, a_loop) = a_rx.recv().unwrap(); - let b = std::thread::spawn(move || { - let agent = crate::agent::enter_worker_agent(); - assert!(ensure_loop()); - let loop_id = route_loop_id(agent); - assert_ne!(loop_id, 0, "no endpoint published for this agent"); - crate::agent::retire_agent(agent); - (agent, loop_id) + // `routed_agents()` counts every agent in the process; a worker agent of + // any other module's test moves it between the two reads. + crate::test_isolation::in_own_process(|| { + let _g = serial(); + let before = routed_agents(); + let (a_tx, a_rx) = mpsc::channel(); + let (go_tx, go_rx) = mpsc::channel::<()>(); + let a = std::thread::spawn(move || { + let agent = crate::agent::enter_worker_agent(); + assert!(ensure_loop()); + let loop_id = route_loop_id(agent); + assert_ne!(loop_id, 0, "no endpoint published for this agent"); + a_tx.send((agent, loop_id)).unwrap(); + // Hold the loop until the sibling has built its own, so both exist at + // once — a sequential pair would pass even with one shared route. + go_rx.recv().unwrap(); + crate::agent::retire_agent(agent); + }); + let (a_agent, a_loop) = a_rx.recv().unwrap(); + let b = std::thread::spawn(move || { + let agent = crate::agent::enter_worker_agent(); + assert!(ensure_loop()); + let loop_id = route_loop_id(agent); + assert_ne!(loop_id, 0, "no endpoint published for this agent"); + crate::agent::retire_agent(agent); + (agent, loop_id) + }); + let (b_agent, b_loop) = b.join().unwrap(); + go_tx.send(()).unwrap(); + a.join().unwrap(); + assert_ne!(a_agent, b_agent); + assert_ne!(a_loop, b_loop, "two agents shared one loop"); + assert_eq!( + routed_agents(), + before, + "retired worker agents leaked route slots" + ); }); - let (b_agent, b_loop) = b.join().unwrap(); - go_tx.send(()).unwrap(); - a.join().unwrap(); - assert_ne!(a_agent, b_agent); - assert_ne!(a_loop, b_loop, "two agents shared one loop"); - assert_eq!( - routed_agents(), - before, - "retired worker agents leaked route slots" - ); } /// A second thread acting for an agent that already has an owner keeps the diff --git a/crates/perry-runtime/src/gc/instruments.rs b/crates/perry-runtime/src/gc/instruments.rs index c7cc9486b6f..a2e8670d8e7 100644 --- a/crates/perry-runtime/src/gc/instruments.rs +++ b/crates/perry-runtime/src/gc/instruments.rs @@ -10,81 +10,91 @@ //! Process-global rather than thread-local: the report is about the run. use std::cell::Cell; -#[cfg(perry_gc_instruments)] +#[cfg(any(test, perry_gc_instruments))] use std::sync::atomic::{AtomicU64, Ordering}; /// A liveness counter. Only the instrumented runtime build (the /// `perry_gc_instruments` cfg, set by `scripts/build_instr_runtime.sh` and by -/// debug-profile test builds) stores and counts. In the default release +/// debug-profile builds) and lib-test builds of any profile (the unit tests +/// assert on these counters, so a release-profile test run must not read 0) +/// store and count. In the default release /// archives it is zero-sized, `add` compiles to nothing and `get` is 0, so the /// polls and GC events that note an event pay no locked read-modify-write for a /// number nothing reads (a `lock incq` on the "nothing due" budgeted-step /// poll was 1% of the instructions and 7% of the cycles of a regex loop). pub(crate) struct Counter { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] cell: AtomicU64, } impl Counter { pub(crate) const fn new() -> Self { Self { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] cell: AtomicU64::new(0), } } #[inline(always)] pub(crate) fn add(&self, n: u64) { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] self.cell.fetch_add(n, Ordering::Relaxed); - #[cfg(not(perry_gc_instruments))] + #[cfg(not(any(test, perry_gc_instruments)))] let _ = n; } #[inline(always)] pub(crate) fn get(&self) -> u64 { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] return self.cell.load(Ordering::Relaxed); - #[cfg(not(perry_gc_instruments))] + #[cfg(not(any(test, perry_gc_instruments)))] 0 } /// Raise the stored value to `value` if it is larger. #[inline(always)] pub(crate) fn raise_to(&self, value: u64) { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] self.cell.fetch_max(value, Ordering::Relaxed); - #[cfg(not(perry_gc_instruments))] + #[cfg(not(any(test, perry_gc_instruments)))] let _ = value; } /// Store `value` when the counter is 0. #[inline(always)] pub(crate) fn set_if_zero(&self, value: u64) { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] let _ = self .cell .compare_exchange(0, value, Ordering::Relaxed, Ordering::Relaxed); - #[cfg(not(perry_gc_instruments))] + #[cfg(not(any(test, perry_gc_instruments)))] let _ = value; } /// Return the stored value and reset it to 0. #[inline(always)] pub(crate) fn take(&self) -> u64 { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] return self.cell.swap(0, Ordering::Relaxed); - #[cfg(not(perry_gc_instruments))] + #[cfg(not(any(test, perry_gc_instruments)))] 0 } #[cfg(test)] pub(crate) fn reset(&self) { - #[cfg(perry_gc_instruments)] + #[cfg(any(test, perry_gc_instruments))] self.cell.store(0, Ordering::Relaxed); } } +// The default release archives carry no counter state at all: the cfg is the +// single switch, and a counter that stores in a build that does not instrument +// would put the locked read-modify-write back on the polls. A lib-test build +// always counts (see `Counter`), so the shipping shape is checked here, at +// compile time, in every build that is not a test or instrumented one. +#[cfg(not(any(test, perry_gc_instruments)))] +const _: () = assert!(std::mem::size_of::() == 0); + static COPYING_MINORS: Counter = Counter::new(); static MOVED_OBJECTS: Counter = Counter::new(); @@ -611,21 +621,6 @@ fn instruments_unavailable(knob: &str) -> ! { mod counter_tests { use super::Counter; - /// The default release archives carry no counter state at all: the cfg is - /// the single switch, and a counter that stores in a build that does not - /// instrument would put the locked read-modify-write back on the polls. - #[cfg(not(perry_gc_instruments))] - #[test] - fn counter_is_zero_sized_and_inert_without_instruments() { - assert_eq!(std::mem::size_of::(), 0); - let c = Counter::new(); - c.add(5); - c.raise_to(9); - c.set_if_zero(3); - assert_eq!(c.get(), 0); - assert_eq!(c.take(), 0); - } - /// The instrumented build (and every debug-profile test build) must really /// count: tests that read `copying_minor_cycles`, `loop_polls_reached`, /// `budgeted_step_skips`, ... depend on it. diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index aed3733dbce..681fe9e4cff 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -706,7 +706,9 @@ fn thrown(f: impl FnOnce() -> f64) -> String { } } -static RETURNS: AtomicUsize = AtomicUsize::new(0); +per_test_global! { + static RETURNS: AtomicUsize = AtomicUsize::new(0); +} extern "C" fn endless_next( _c: *const crate::closure::ClosureHeader, diff --git a/crates/perry-runtime/src/lib.rs b/crates/perry-runtime/src/lib.rs index d2977dbb295..0958fb04b24 100644 --- a/crates/perry-runtime/src/lib.rs +++ b/crates/perry-runtime/src/lib.rs @@ -94,6 +94,8 @@ static GLOBAL: alloc_census::CensusAlloc = // scope for every module below it. See its module docs for #7672. #[macro_use] pub mod per_test_global; +#[cfg(test)] +pub(crate) mod test_isolation; #[macro_use] mod wasm_image; @@ -1181,25 +1183,27 @@ pub(crate) mod stdlib_pump { /// exited with the pipe still open. #[test] fn stdin_object_listener_keeps_the_loop_alive_without_stdlib() { - crate::os::test_set_stdin_data_listener(None); - assert_eq!( - js_stdlib_has_active_handles(), - 0, - "no stdin listener and no other source: the loop must be free to exit" - ); - crate::os::test_set_stdin_data_listener(Some(0x1234)); - assert_eq!( - js_stdlib_has_active_handles(), - 1, - "a live runtime-local stdin listener must keep the event loop alive \ - even when perry-stdlib is not linked (#9416)" - ); - crate::os::test_set_stdin_data_listener(None); - assert_eq!( - js_stdlib_has_active_handles(), - 0, - "removing the listener must release the loop again" - ); + crate::test_isolation::in_own_process(|| { + crate::os::test_set_stdin_data_listener(None); + assert_eq!( + js_stdlib_has_active_handles(), + 0, + "no stdin listener and no other source: the loop must be free to exit" + ); + crate::os::test_set_stdin_data_listener(Some(0x1234)); + assert_eq!( + js_stdlib_has_active_handles(), + 1, + "a live runtime-local stdin listener must keep the event loop alive \ + even when perry-stdlib is not linked (#9416)" + ); + crate::os::test_set_stdin_data_listener(None); + assert_eq!( + js_stdlib_has_active_handles(), + 0, + "removing the listener must release the loop again" + ); + }); } static HAS_ACTIVE_FLAG: AtomicI32 = AtomicI32::new(0); @@ -1207,29 +1211,35 @@ pub(crate) mod stdlib_pump { #[cfg(not(target_arch = "wasm32"))] #[test] fn agent_driver_reference_keeps_the_generated_loop_alive_without_an_extension_map() { - std::thread::spawn(|| { - let agent = crate::agent::enter_worker_agent(); - assert_eq!(js_stdlib_has_active_handles(), 0); - let handle = crate::event_pump::with_net_driver(|driver| { - driver - .tcp_listen("127.0.0.1:0".parse().unwrap(), &Default::default()) - .unwrap() + crate::test_isolation::in_own_process(|| { + std::thread::spawn(|| { + let agent = crate::agent::enter_worker_agent(); + assert_eq!(js_stdlib_has_active_handles(), 0); + let handle = crate::event_pump::with_net_driver(|driver| { + driver + .tcp_listen("127.0.0.1:0".parse().unwrap(), &Default::default()) + .unwrap() + }) + .expect("a JS agent must own its driver"); + assert_eq!( + js_stdlib_has_active_handles(), + 1, + "a referenced driver handle must keep the generated loop alive" + ); + crate::event_pump::with_net_driver(|driver| { + driver.set_ref(handle, false).unwrap() + }); + assert_eq!(js_stdlib_has_active_handles(), 0); + crate::event_pump::with_net_driver(|driver| { + driver.set_ref(handle, true).unwrap() + }); + assert_eq!(js_stdlib_has_active_handles(), 1); + crate::agent::retire_agent(agent); + assert_eq!(js_stdlib_has_active_handles(), 0); }) - .expect("a JS agent must own its driver"); - assert_eq!( - js_stdlib_has_active_handles(), - 1, - "a referenced driver handle must keep the generated loop alive" - ); - crate::event_pump::with_net_driver(|driver| driver.set_ref(handle, false).unwrap()); - assert_eq!(js_stdlib_has_active_handles(), 0); - crate::event_pump::with_net_driver(|driver| driver.set_ref(handle, true).unwrap()); - assert_eq!(js_stdlib_has_active_handles(), 1); - crate::agent::retire_agent(agent); - assert_eq!(js_stdlib_has_active_handles(), 0); - }) - .join() - .unwrap(); + .join() + .unwrap(); + }); } extern "C" fn flag_has_active() -> i32 { @@ -1242,28 +1252,30 @@ pub(crate) mod stdlib_pump { /// this slot, not through a timer. #[test] fn runtime_has_active_slot_gates_the_loop() { - crate::os::test_set_stdin_data_listener(None); - register_runtime_has_active(RUNTIME_HAS_ACTIVE_SLOTS - 1, flag_has_active); - HAS_ACTIVE_FLAG.store(0, AtomicOrdering::SeqCst); - assert_eq!( - js_stdlib_has_active_handles(), - 0, - "an armed slot reporting 0 must not pin the loop" - ); - HAS_ACTIVE_FLAG.store(1, AtomicOrdering::SeqCst); - assert_eq!( - js_stdlib_has_active_handles(), - 1, - "an armed slot reporting live work must keep the loop alive" - ); - HAS_ACTIVE_FLAG.store(0, AtomicOrdering::SeqCst); - assert_eq!( - js_stdlib_has_active_handles(), - 0, - "the loop is released again once the work is gone" - ); - // Out-of-range slots are ignored, never a panic. - register_runtime_has_active(RUNTIME_HAS_ACTIVE_SLOTS, flag_has_active); + crate::test_isolation::in_own_process(|| { + crate::os::test_set_stdin_data_listener(None); + register_runtime_has_active(RUNTIME_HAS_ACTIVE_SLOTS - 1, flag_has_active); + HAS_ACTIVE_FLAG.store(0, AtomicOrdering::SeqCst); + assert_eq!( + js_stdlib_has_active_handles(), + 0, + "an armed slot reporting 0 must not pin the loop" + ); + HAS_ACTIVE_FLAG.store(1, AtomicOrdering::SeqCst); + assert_eq!( + js_stdlib_has_active_handles(), + 1, + "an armed slot reporting live work must keep the loop alive" + ); + HAS_ACTIVE_FLAG.store(0, AtomicOrdering::SeqCst); + assert_eq!( + js_stdlib_has_active_handles(), + 0, + "the loop is released again once the work is gone" + ); + // Out-of-range slots are ignored, never a panic. + register_runtime_has_active(RUNTIME_HAS_ACTIVE_SLOTS, flag_has_active); + }); } #[test] diff --git a/crates/perry-runtime/src/object/shapes_store/dense_tests.rs b/crates/perry-runtime/src/object/shapes_store/dense_tests.rs index d78764435a7..7b5fc6cfc54 100644 --- a/crates/perry-runtime/src/object/shapes_store/dense_tests.rs +++ b/crates/perry-runtime/src/object/shapes_store/dense_tests.rs @@ -186,15 +186,17 @@ fn dead_agents_chunks_are_never_handed_out_again() { #[test] fn a_dropped_slab_returns_its_chunks_to_the_process() { - let before = super::CHUNKS.free_len(); - let mut slab = ShapeSlab::new(); - for i in 0..(super::DYNAMIC_CHUNK_ROWS + 1) { - slab.publish_dynamic(ShapeId(SHAPE_ID_BASE + 9000 + i), record()) - .unwrap(); - } - assert_eq!(slab.chunks.len(), 2); - drop(slab); - assert_eq!(super::CHUNKS.free_len(), before + 2); + crate::test_isolation::in_own_process(|| { + let before = super::CHUNKS.free_len(); + let mut slab = ShapeSlab::new(); + for i in 0..(super::DYNAMIC_CHUNK_ROWS + 1) { + slab.publish_dynamic(ShapeId(SHAPE_ID_BASE + 9000 + i), record()) + .unwrap(); + } + assert_eq!(slab.chunks.len(), 2); + drop(slab); + assert_eq!(super::CHUNKS.free_len(), before + 2); + }); } #[test] diff --git a/crates/perry-runtime/src/test_isolation.rs b/crates/perry-runtime/src/test_isolation.rs new file mode 100644 index 00000000000..10b6cd410ce --- /dev/null +++ b/crates/perry-runtime/src/test_isolation.rs @@ -0,0 +1,37 @@ +//! Tests that assert on process-wide state no lock can quiesce. +//! +//! `NOTIFIED`, the pending-microtask probes, the has-active registries and the +//! stdin listener are single process-wide cells that any of the other ~6000 +//! tests may touch (every promise settlement notifies). A test that needs them +//! idle cannot get that from a mutex only its own module takes, so it runs in a +//! child process holding nothing but itself. + +/// Run `body` in a fresh process running only the calling test. +/// +/// In the parent (the normal test run) this re-executes the test binary with +/// the calling test's exact name and returns once the child passed; in the +/// child (`PERRY_TEST_OWN_PROCESS` set) it runs `body` inline. Call it first +/// in the test, from the test fn itself: the test's name is read off the +/// current thread, which libtest names after the test. +pub(crate) fn in_own_process(body: impl FnOnce()) { + const MARKER: &str = "PERRY_TEST_OWN_PROCESS"; + if std::env::var_os(MARKER).is_some() { + body(); + return; + } + let thread = std::thread::current(); + let name = thread + .name() + .expect("in_own_process must be called from a named libtest thread"); + let output = std::process::Command::new(std::env::current_exe().expect("test binary")) + .args([name, "--exact", "--nocapture"]) + .env(MARKER, "1") + .output() + .expect("spawn the isolated test process"); + assert!( + output.status.success() && String::from_utf8_lossy(&output.stdout).contains("1 passed"), + "isolated run of {name} failed:\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} diff --git a/crates/perry-runtime/src/tls_hot.rs b/crates/perry-runtime/src/tls_hot.rs index 4c2fb0d52ac..90dbe8977bf 100644 --- a/crates/perry-runtime/src/tls_hot.rs +++ b/crates/perry-runtime/src/tls_hot.rs @@ -1485,62 +1485,64 @@ mod tests { /// claimed per *declaration*, not per thread. #[test] fn converted_declarations_survive_thread_turnover() { - fn touch_the_converted_registries() -> usize { - let mut seen = 0; - for probe in [0usize, 1, 0x1000, usize::MAX / 2] { - seen += usize::from(crate::map::is_registered_map(probe)); - seen += usize::from(crate::set::is_registered_set(probe)); - seen += usize::from(crate::buffer::is_registered_buffer(probe)); - seen += usize::from(crate::symbol::is_registered_symbol(probe)); - seen += usize::from( - crate::regex::regexp_data_of(crate::value::js_nanbox_pointer( - (probe as *const u8) as i64, - )) - .is_some(), - ); + crate::test_isolation::in_own_process(|| { + fn touch_the_converted_registries() -> usize { + let mut seen = 0; + for probe in [0usize, 1, 0x1000, usize::MAX / 2] { + seen += usize::from(crate::map::is_registered_map(probe)); + seen += usize::from(crate::set::is_registered_set(probe)); + seen += usize::from(crate::buffer::is_registered_buffer(probe)); + seen += usize::from(crate::symbol::is_registered_symbol(probe)); + seen += usize::from( + crate::regex::regexp_data_of(crate::value::js_nanbox_pointer( + (probe as *const u8) as i64, + )) + .is_some(), + ); + } + seen } - seen - } - touch_the_converted_registries(); - let after_main = super::claimed_slots(); + touch_the_converted_registries(); + let after_main = super::claimed_slots(); - for _ in 0..8 { - let batch: Vec<_> = (0..8) - .map(|_| std::thread::spawn(touch_the_converted_registries)) - .collect(); - for worker in batch { - worker.join().expect("worker thread panicked"); + for _ in 0..8 { + let batch: Vec<_> = (0..8) + .map(|_| std::thread::spawn(touch_the_converted_registries)) + .collect(); + for worker in batch { + worker.join().expect("worker thread panicked"); + } } - } - let after_workers = super::claimed_slots(); - // A TOLERANCE, not equality, and the reason is the measurement rather - // than the mechanism: `claimed_slots()` is PROCESS-global, so any other - // test in this binary that touches a converted declaration for the - // first time lands a claim inside this window. Under `--test-threads=1` - // the delta is exactly 0; in parallel it is 0 or a stray 1-2 from a - // neighbour. Asserting equality made this fail 6/6 under load while - // passing 3/3 in isolation and 1/1 single-threaded. - // - // The tolerance still separates the two outcomes by two orders of - // magnitude: per-THREAD claiming — the bug this test exists to catch — - // would add 5 declarations x 64 workers = 320, not 1. - const TURNOVER_CLAIM_TOLERANCE: u32 = 8; - let extra = after_workers.saturating_sub(after_main); - assert!( - extra <= TURNOVER_CLAIM_TOLERANCE, - "64 worker threads claimed {extra} extra slots (tolerance \ - {TURNOVER_CLAIM_TOLERANCE}); indices must be per declaration, not \ - per thread — per-thread claiming would add 5 x 64 = 320", - ); - assert!( - (after_workers as usize) < super::HOT_SLOT_CAPACITY, - "capacity {} exhausted at {after_workers}", - super::HOT_SLOT_CAPACITY, - ); - // And the parent thread's own cache is still serving after all that. - assert_eq!(touch_the_converted_registries(), 0); + let after_workers = super::claimed_slots(); + // A TOLERANCE, not equality, and the reason is the measurement rather + // than the mechanism: `claimed_slots()` is PROCESS-global, so any other + // test in this binary that touches a converted declaration for the + // first time lands a claim inside this window. Under `--test-threads=1` + // the delta is exactly 0; in parallel it is 0 or a stray 1-2 from a + // neighbour. Asserting equality made this fail 6/6 under load while + // passing 3/3 in isolation and 1/1 single-threaded. + // + // The tolerance still separates the two outcomes by two orders of + // magnitude: per-THREAD claiming — the bug this test exists to catch — + // would add 5 declarations x 64 workers = 320, not 1. + const TURNOVER_CLAIM_TOLERANCE: u32 = 8; + let extra = after_workers.saturating_sub(after_main); + assert!( + extra <= TURNOVER_CLAIM_TOLERANCE, + "64 worker threads claimed {extra} extra slots (tolerance \ + {TURNOVER_CLAIM_TOLERANCE}); indices must be per declaration, not \ + per thread — per-thread claiming would add 5 x 64 = 320", + ); + assert!( + (after_workers as usize) < super::HOT_SLOT_CAPACITY, + "capacity {} exhausted at {after_workers}", + super::HOT_SLOT_CAPACITY, + ); + // And the parent thread's own cache is still serving after all that. + assert_eq!(touch_the_converted_registries(), 0); + }); } /// The guard exists exactly when the value has something to destroy — that diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index f140cec5f99..9e6bf966f57 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -2646,16 +2646,18 @@ fn function_source_array_literal_keeps_the_array_index_fast_path_armed() { /// registry lock; the control arm proves the counter this test reads moves. #[test] fn representation_change_takes_no_registry_lock_with_feedback_off() { - let _guard = typed_feedback_test_lock(); - reset_typed_feedback_for_tests(); - let addr = 0x7000_0000usize; - invalidate_representation_change_when(addr, false); - assert_eq!( - typed_feedback_snapshot().representation_invalidations, - 0, - "feedback off: the registry must not be touched" - ); - invalidate_representation_change_when(addr, true); - assert_eq!(typed_feedback_snapshot().representation_invalidations, 1); - reset_typed_feedback_for_tests(); + crate::test_isolation::in_own_process(|| { + let _guard = typed_feedback_test_lock(); + reset_typed_feedback_for_tests(); + let addr = 0x7000_0000usize; + invalidate_representation_change_when(addr, false); + assert_eq!( + typed_feedback_snapshot().representation_invalidations, + 0, + "feedback off: the registry must not be touched" + ); + invalidate_representation_change_when(addr, true); + assert_eq!(typed_feedback_snapshot().representation_invalidations, 1); + reset_typed_feedback_for_tests(); + }); } From f94a1235f7fabcfac822f489c89769748c698727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 23:40:36 +0100 Subject: [PATCH 020/144] changelog: number the PR fragment --- ...{0-rlt-release-lib-tests.md => 13588-rlt-release-lib-tests.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-rlt-release-lib-tests.md => 13588-rlt-release-lib-tests.md} (100%) diff --git a/changelog.d/0-rlt-release-lib-tests.md b/changelog.d/13588-rlt-release-lib-tests.md similarity index 100% rename from changelog.d/0-rlt-release-lib-tests.md rename to changelog.d/13588-rlt-release-lib-tests.md From 44ee7cab8de68d3bcbe7a07f9a369a2b3c322dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 00:26:55 +0000 Subject: [PATCH 021/144] codegen: native IR reader builds inline metadata tuples (noalias scope decls) The indexed-owner loop region (stmt/indexed_owner_loop.rs) declares one noalias scope per typed-array owner with `llvm.experimental.noalias.scope.decl(metadata !{!{!"owner.N", !{!"DOMAIN"}}})` and tags the region's loads and stores with inline `!alias.scope` / `!noalias` tuples. The native reader, the default for split modules, accepted only `llvm.read_register`'s `!{!"NAME"}` operand, so a split module holding such a region failed construction with "unsupported metadata operand" and the build refused to link (opencode: @aws-sdk/core dist-cjs/submodules/protocols/index.js on 0.5.1663). It also dropped the inline scope attachments on loads and stores without any error. The reader now parses any inline metadata tuple of `!"string"` leaves (LLVM `\XX` escapes decoded) and nested tuples, and builds it through the uniquing MDNode API, as LLVM's parser does. The line call path and the typed Call/CallIndirect paths share one argument builder. Inline `!kind !{...}` attachments on loads and stores are set by kind name; `!invariant.load` keeps its meaning, and numbered `!N` references to module metadata stay unattached as before. split_top_level and rmatch_paren skip quoted strings, because a scope domain embeds the function name. Tests: a reader fixture whose print and metadata trees must equal LLVM's own parse (escaped domain with commas and brackets, read_register operand included), and the indexed-owner emitter test now builds its live output through the reader. Both fail without this change with the production error. --- changelog.d/0-native-reader-noalias-scopes.md | 12 ++ crates/perry-codegen/src/dialect/mod.rs | 63 +++++---- crates/perry-codegen/src/dialect/tests.rs | 82 ++++++++++++ crates/perry-codegen/src/dialect/text.rs | 124 +++++++++++++++++- .../src/stmt/indexed_owner_loop_tests.rs | 17 +++ 5 files changed, 262 insertions(+), 36 deletions(-) create mode 100644 changelog.d/0-native-reader-noalias-scopes.md diff --git a/changelog.d/0-native-reader-noalias-scopes.md b/changelog.d/0-native-reader-noalias-scopes.md new file mode 100644 index 00000000000..191553b1cdc --- /dev/null +++ b/changelog.d/0-native-reader-noalias-scopes.md @@ -0,0 +1,12 @@ +Fix split-module native construction of indexed-owner loop regions. The +native IR reader accepted only the `!{!"NAME"}` metadata operand of +`llvm.read_register`, so a function whose typed-array owner region declares +its noalias scopes (`llvm.experimental.noalias.scope.decl`) failed with +"unsupported metadata operand" and the whole build refused to link +(opencode's `@aws-sdk/core` protocols module). The reader now builds any +inline metadata tuple of strings and tuples through the uniquing MDNode API, +on both the line and typed call paths, and attaches inline `!alias.scope` / +`!noalias` tuples on loads and stores, which it used to drop. Comma and +bracket splitting skips quoted strings, since scope domains embed function +names. A reader test holds the result equal to LLVM's own parse, and the +indexed-owner emitter test builds its live output through the reader. diff --git a/crates/perry-codegen/src/dialect/mod.rs b/crates/perry-codegen/src/dialect/mod.rs index 05160e6f065..1186b4dcbd3 100644 --- a/crates/perry-codegen/src/dialect/mod.rs +++ b/crates/perry-codegen/src/dialect/mod.rs @@ -42,12 +42,13 @@ pub(crate) const LLVM_CC_PRESERVE_NONE: u32 = 21; pub(crate) const LLVM_CC_PRESERVE_MOST: u32 = 14; use text::{ - add_enum_attr, apply_flags, be, float_pred, int_pred, rauw, rmatch_paren, set_alignment_if_any, - set_invariant_load, split_top_level, take_quoted, unquote, + add_enum_attr, apply_flags, attach_trailing_metadata, be, float_pred, inline_metadata, + int_pred, rauw, rmatch_paren, set_alignment_if_any, set_invariant_load, split_top_level, + take_quoted, unquote, }; use types::{basic_type, constant, fn_type_of, indirect_fn_type, strip_label, ty_and_val}; #[cfg(test)] -mod tests; +pub(crate) mod tests; /// Create (only) the function declaration for `fn_text`'s define header, so /// later-defined functions are callable while earlier bodies are read. The @@ -664,6 +665,7 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { let _ = inst.set_volatile(true); } set_alignment_if_any(Some(inst), rest); + attach_trailing_metadata(self.ctx, Some(inst), &parts[2..])?; Ok(()) } "br" => { @@ -735,11 +737,7 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { if volatile { let _ = v.as_instruction_value().map(|i| i.set_volatile(true)); } - if rest.contains("!invariant.load") { - if let Some(i) = v.as_instruction_value() { - set_invariant_load(self.ctx, i); - } - } + attach_trailing_metadata(self.ctx, v.as_instruction_value(), &parts[2..])?; if atomic { let ord = match ordering { Some("seq_cst") => { @@ -857,6 +855,22 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { /// Calls: direct `@f(...)`, indirect `%fp(...)` (with or without a /// pre-opaque `(sig)*` type), and Perry-emitted inline asm. + /// One call argument. A `metadata` operand is an inline metadata tree + /// (`llvm.read_register`'s register name, a noalias scope declaration); + /// every other operand is a basic value of its type. + fn call_arg( + &mut self, + ty: &str, + tok: &str, + ) -> Result<(BasicMetadataValueEnum<'ctx>, inkwell::types::BasicMetadataTypeEnum<'ctx>)> { + if ty == "metadata" { + let node = inline_metadata(self.ctx, tok)?; + return Ok((node.into(), self.ctx.metadata_type().into())); + } + let bt = basic_type(self.ctx, ty)?; + Ok((self.val(bt, tok)?.into(), bt.into())) + } + fn call(&mut self, dst: Option<&str>, rest: &str) -> Result>> { // Optional fast-math prefix tokens on float-returning calls. let rest = rest @@ -906,24 +920,9 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { let mut arg_types: Vec = Vec::new(); for a in split_top_level(args_str) { let (aty, atok) = ty_and_val(&a)?; - if aty == "metadata" { - // `metadata !{!"NAME"}`: the one metadata operand Perry - // emits, `llvm.read_register`'s register name - // (`expr/stack_guard.rs`). - let name = atok - .strip_prefix("!{!\"") - .and_then(|t| t.strip_suffix("\"}")) - .ok_or_else(|| anyhow!("unsupported metadata operand `{atok}`"))?; - let node = self - .ctx - .metadata_node(&[self.ctx.metadata_string(name).into()]); - args.push(node.into()); - arg_types.push(self.ctx.metadata_type().into()); - continue; - } - let ty = basic_type(self.ctx, aty)?; - args.push(self.val(ty, atok)?.into()); - arg_types.push(ty.into()); + let (value, ty) = self.call_arg(aty, atok)?; + args.push(value); + arg_types.push(ty); } let name = dst.map(|d| d.trim_start_matches('%')).unwrap_or(""); @@ -1770,9 +1769,9 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { let mut argtys: Vec = Vec::with_capacity(args.len()); for (t, v) in args { - let bt = basic_type(self.ctx, t)?; - argv.push(self.val(bt, v)?.into()); - argtys.push(bt.into()); + let (value, ty) = self.call_arg(t, v)?; + argv.push(value); + argtys.push(ty); } let fn_ty = fn_type_of(self.ctx, ret, &argtys)?; let f = match self.module.get_function(callee) { @@ -1835,9 +1834,9 @@ impl<'ctx, 'm> FnReader<'ctx, 'm> { let mut argtys: Vec = Vec::with_capacity(args.len()); for (t, v) in args { - let bt = basic_type(self.ctx, t)?; - argv.push(self.val(bt, v)?.into()); - argtys.push(bt.into()); + let (value, ty) = self.call_arg(t, v)?; + argv.push(value); + argtys.push(ty); } let fn_ty = fn_type_of(self.ctx, ret, &argtys)?; let fp = self diff --git a/crates/perry-codegen/src/dialect/tests.rs b/crates/perry-codegen/src/dialect/tests.rs index b5bfcb3b638..b9c7b98446a 100644 --- a/crates/perry-codegen/src/dialect/tests.rs +++ b/crates/perry-codegen/src/dialect/tests.rs @@ -299,6 +299,88 @@ entry: assert_eq!(roundtrip_ir(ir, "freeze_text"), 3); } +/// Every function `text` defines, built by the reader over the skeleton and +/// verified; returns the reader module's print. Lets emitter tests outside +/// this module hold a LIVE emission to the reader (#8228). +pub(crate) fn reader_module_print(text: &str) -> String { + let (skeleton, fns) = split_corpus(text); + let ctx = Context::create(); + let module = + crate::inprocess::parse_ir_text(&ctx, &skeleton, "reader_print").expect("skeleton parses"); + for f in &fns { + predeclare_function_from_text(&ctx, &module, f) + .unwrap_or_else(|e| panic!("predeclare: {e:#}")); + } + for f in &fns { + add_function_from_text(&ctx, &module, f).unwrap_or_else(|e| panic!("{e:#}")); + } + module + .verify() + .unwrap_or_else(|e| panic!("verifier rejected native module:\n{}", e.to_string())); + module.print_to_string().to_string() +} + +/// `stmt/indexed_owner_loop.rs` declares one noalias scope per typed-array +/// owner (`llvm.experimental.noalias.scope.decl`, an inline `metadata` +/// operand) and tags the region's loads and stores with inline +/// `!alias.scope`/`!noalias` tuples. The reader accepted only +/// `llvm.read_register`'s `!{!"NAME"}` operand, so a split module holding +/// such a region failed native construction ("unsupported metadata +/// operand", @aws-sdk/core protocols in opencode, 0.5.1663), and it dropped +/// the attachments silently. The scope domain embeds the function name, so +/// the strings here carry escapes and structural characters. The reader's +/// build must print exactly as LLVM's own parse of the same text. +#[test] +fn noalias_scope_declarations_and_inline_scope_tuples_match_llvms_parse() { + let domain = r#"!{!"indexed.owner.f\5C\22,(x){y}.3"}"#; + let a = format!(r#"!{{!"owner.1", {domain}}}"#); + let b = format!(r#"!{{!"owner.2", {domain}}}"#); + let ir = format!( + r#"declare void @llvm.experimental.noalias.scope.decl(metadata) +declare i64 @llvm.read_register.i64(metadata) + +define double @owner_region(ptr %a, ptr %b) {{ +entry: + call void @llvm.experimental.noalias.scope.decl(metadata !{{{a}}}) + call void @llvm.experimental.noalias.scope.decl(metadata !{{{b}}}) + %sp = call i64 @llvm.read_register.i64(metadata !{{!"rsp"}}) + %x = load double, ptr %a, align 8, !alias.scope !{{{a}}}, !noalias !{{{b}}} + store double %x, ptr %b, align 8, !alias.scope !{{{b}}}, !noalias !{{{a}}} + ret double %x +}} +"# + ); + let llvm_ctx = Context::create(); + let llvm_module = crate::inprocess::parse_ir_text(&llvm_ctx, &ir, "scopes_llvm") + .expect("LLVM parses the fixture"); + let llvm = print_function(&llvm_module, "owner_region"); + assert_eq!( + llvm.matches("!alias.scope !").count() + llvm.matches("!noalias !").count(), + 4, + "fixture lost its scope attachments:\n{llvm}" + ); + let (skeleton, fns) = split_corpus(&ir); + let ctx = Context::create(); + let module = + crate::inprocess::parse_ir_text(&ctx, &skeleton, "scopes_reader").expect("skeleton parses"); + for f in &fns { + predeclare_function_from_text(&ctx, &module, f).expect("predeclare"); + add_function_from_text(&ctx, &module, f).unwrap_or_else(|e| panic!("{e:#}")); + } + module + .verify() + .unwrap_or_else(|e| panic!("verifier rejected native module:\n{}", e.to_string())); + let reader = print_function(&module, "owner_region"); + assert_same_function_print("noalias scope region", &llvm, &reader); + // Function prints show attachment slots only; the scope trees behind + // them must be the same nodes too. + let tail = |m: &inkwell::module::Module<'_>| { + let text = m.print_to_string().to_string(); + text.lines().filter(|l| l.starts_with('!')).map(str::to_string).collect::>() + }; + assert_eq!(tail(&llvm_module), tail(&module), "scope metadata trees differ"); +} + /// The `format!` templates `expr/channel.rs` emits for its `<4 x i32>` SIMD /// byte-channel reduction, in emission order. The fixture below is BUILT from /// these strings rather than duplicating them, and each is asserted to still diff --git a/crates/perry-codegen/src/dialect/text.rs b/crates/perry-codegen/src/dialect/text.rs index e7ef9b2e061..aa84e4d8ffe 100644 --- a/crates/perry-codegen/src/dialect/text.rs +++ b/crates/perry-codegen/src/dialect/text.rs @@ -3,22 +3,126 @@ //! Split out of `dialect/mod.rs` to keep it under the 2,000-line cap (#10750). use super::*; +use inkwell::values::MetadataValue; /// Tag a load with `!invariant.load !0` (issue #52 semantics). Kind id via /// llvm-sys; the node is the empty MDNode, same as the textual `!0 = !{}`. pub(super) fn set_invariant_load(ctx: &Context, inst: InstructionValue<'_>) { + set_metadata_kind(ctx, inst, "invariant.load", ctx.metadata_node(&[])); +} + +fn set_metadata_kind<'ctx>( + ctx: &'ctx Context, + inst: InstructionValue<'ctx>, + kind: &str, + node: MetadataValue<'ctx>, +) { use inkwell::context::AsContextRef; let kind = unsafe { llvm_sys::core::LLVMGetMDKindIDInContext( ctx.as_ctx_ref(), - b"invariant.load".as_ptr() as *const std::os::raw::c_char, - 14, + kind.as_ptr() as *const std::os::raw::c_char, + kind.len() as u32, ) }; - let node = ctx.metadata_node(&[]); let _ = inst.set_metadata(node, kind); } +/// An inline metadata operand as the emitters write it: a tree of `!{...}` +/// tuples whose leaves are `!"string"` (LLVM `\XX` byte escapes). Covers +/// `llvm.read_register`'s `!{!"rsp"}` and the indexed-owner region's +/// `llvm.experimental.noalias.scope.decl` scope lists, whose scopes are +/// `!{!"owner.N", !{!"DOMAIN"}}`. Built through the uniquing MDNode API, so +/// equal text yields the identical node, exactly as LLVM's own parser +/// uniques inline tuples. Numbered `!N` references name module metadata the +/// reader cannot see and stay a loud error here. +pub(super) fn inline_metadata<'ctx>(ctx: &'ctx Context, tok: &str) -> Result> { + let (node, rest) = + metadata_tuple(ctx, tok.trim()).with_context(|| format!("metadata operand `{tok}`"))?; + if !rest.trim().is_empty() { + bail!("trailing text after metadata operand `{tok}`"); + } + Ok(node) +} + +fn metadata_tuple<'ctx, 's>(ctx: &'ctx Context, s: &'s str) -> Result<(MetadataValue<'ctx>, &'s str)> { + let mut s = s + .strip_prefix("!{") + .ok_or_else(|| anyhow!("unsupported metadata form `{s}`"))?; + let mut elems: Vec> = Vec::new(); + loop { + s = s.trim_start(); + if let Some(rest) = s.strip_prefix('}') { + return Ok((ctx.metadata_node(&elems), rest)); + } + let (elem, rest) = if let Some(body) = s.strip_prefix("!\"") { + let end = body + .find('"') + .ok_or_else(|| anyhow!("unterminated metadata string"))?; + let text = unescape_llvm_bytes(&body[..end])?; + (ctx.metadata_string(&text), &body[end + 1..]) + } else { + metadata_tuple(ctx, s)? + }; + elems.push(elem.into()); + s = rest.trim_start(); + if let Some(rest) = s.strip_prefix(',') { + s = rest; + } else if !s.starts_with('}') { + bail!("expected `,` or `}}` in metadata tuple at `{s}`"); + } + } +} + +/// Decode LLVM's `\XX` hex byte escapes (`\5C` is a backslash). +fn unescape_llvm_bytes(s: &str) -> Result { + let bytes = s.as_bytes(); + let mut out = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'\\' { + let hex = s + .get(i + 1..i + 3) + .and_then(|h| u8::from_str_radix(h, 16).ok()) + .ok_or_else(|| anyhow!("bad `\\XX` escape in metadata string `{s}`"))?; + out.push(hex); + i += 3; + } else { + out.push(bytes[i]); + i += 1; + } + } + String::from_utf8(out).map_err(|_| anyhow!("metadata string `{s}` is not UTF-8")) +} + +/// Attach the trailing `!kind !{...}` metadata of a load/store line. Inline +/// tuples (the indexed-owner region's `!alias.scope`/`!noalias`) are built +/// like LLVM's own parse; `!invariant.load` keeps its empty-node meaning. +/// Numbered `!N` references to module metadata are not visible to the reader +/// and are left off, which only drops an optimization hint. +pub(super) fn attach_trailing_metadata<'ctx>( + ctx: &'ctx Context, + inst: Option>, + parts: &[String], +) -> Result<()> { + let Some(inst) = inst else { return Ok(()) }; + for part in parts { + let Some(attachment) = part.strip_prefix('!') else { + continue; + }; + let Some((kind, node)) = attachment.split_once(char::is_whitespace) else { + continue; + }; + let node = node.trim(); + if kind == "invariant.load" { + set_invariant_load(ctx, inst); + } else if node.starts_with("!{") { + set_metadata_kind(ctx, inst, kind, inline_metadata(ctx, node)?); + } + } + Ok(()) +} + /// Replace every use of a placeholder with the real definition. A type /// mismatch means the operand-context type at the USE disagreed with the /// defining instruction — always a reader bug, and silently leaving the @@ -76,8 +180,13 @@ pub(super) fn take_quoted<'a>(s: &'a str, what: &str) -> Result<(String, &'a str /// Find the matching `)` for the `(` at `open` in `s`. pub(super) fn rmatch_paren(s: &str, open: usize) -> Result { let mut depth = 0usize; + let mut quoted = false; for (i, c) in s.char_indices().skip(open) { match c { + // LLVM strings carry `\XX` byte escapes, never a bare quote, so + // each `"` toggles; brackets inside a string are bytes. + '"' => quoted = !quoted, + _ if quoted => {} '(' => depth += 1, ')' => { depth -= 1; @@ -91,13 +200,20 @@ pub(super) fn rmatch_paren(s: &str, open: usize) -> Result { bail!("unbalanced parens") } -/// Split on top-level commas (not inside (), [], <>, {}). +/// Split on top-level commas (not inside (), [], <>, {}, or a quoted +/// string: metadata strings carry arbitrary function names). pub(super) fn split_top_level(s: &str) -> Vec { let mut out = Vec::new(); let mut depth = 0i32; + let mut quoted = false; let mut cur = String::new(); for c in s.chars() { match c { + '"' => { + quoted = !quoted; + cur.push(c); + } + _ if quoted => cur.push(c), '(' | '[' | '{' | '<' => { depth += 1; cur.push(c); diff --git a/crates/perry-codegen/src/stmt/indexed_owner_loop_tests.rs b/crates/perry-codegen/src/stmt/indexed_owner_loop_tests.rs index 091c77a3166..52d99a0d6e4 100644 --- a/crates/perry-codegen/src/stmt/indexed_owner_loop_tests.rs +++ b/crates/perry-codegen/src/stmt/indexed_owner_loop_tests.rs @@ -248,6 +248,23 @@ fn disjoint_owner_scopes_have_dynamic_single_scope_declarations_and_verify() { .create_module_from_ir(buffer) .expect("owner-region LLVM parses"); module.verify().expect("dynamic owner-region scopes verify"); + // Split modules build bodies through the native reader: the same + // emission must construct there with every scope declaration and + // attachment LLVM's parse keeps. + let llvm = module.print_to_string().to_string(); + let reader = crate::dialect::tests::reader_module_print(&ir); + for form in [ + "call void @llvm.experimental.noalias.scope.decl(metadata", + "!alias.scope !", + "!noalias !", + ] { + assert!(llvm.contains(form), "LLVM parse lost `{form}`"); + assert_eq!( + llvm.matches(form).count(), + reader.matches(form).count(), + "reader and LLVM disagree on `{form}`" + ); + } } #[test] From 7c18fa9e789415321c8b130249883ea3e6986399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 02:04:23 +0100 Subject: [PATCH 022/144] changelog: number the PR fragment --- ...er-noalias-scopes.md => 13598-native-reader-noalias-scopes.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-native-reader-noalias-scopes.md => 13598-native-reader-noalias-scopes.md} (100%) diff --git a/changelog.d/0-native-reader-noalias-scopes.md b/changelog.d/13598-native-reader-noalias-scopes.md similarity index 100% rename from changelog.d/0-native-reader-noalias-scopes.md rename to changelog.d/13598-native-reader-noalias-scopes.md From e22a12a2e22341f8268f6efddc8bd82176eb4076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:36:57 +0000 Subject: [PATCH 023/144] test: a relinked parent class constructor runs on the subclass instance per unit --- crates/perry-wpfacts/src/tests.rs | 1 + .../src/tests/relinked_parent.rs | 170 ++++++++++++++++++ crates/perry/tests/relinked_parent_ctor.rs | 84 +++++++++ 3 files changed, 255 insertions(+) create mode 100644 crates/perry-wpfacts/src/tests/relinked_parent.rs create mode 100644 crates/perry/tests/relinked_parent_ctor.rs diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index 9545f1c3326..af883e64cb1 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -44,6 +44,7 @@ mod protocol_calls; mod protocol_keys; mod receiver_slots; mod receiver_this; +mod relinked_parent; mod rest_element; mod run_once_closed; mod runtime_helpers; diff --git a/crates/perry-wpfacts/src/tests/relinked_parent.rs b/crates/perry-wpfacts/src/tests/relinked_parent.rs new file mode 100644 index 00000000000..f40991ad547 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/relinked_parent.rs @@ -0,0 +1,170 @@ +//! Per unit, a class whose class object is relinked as another class's +//! parent (`Object.setPrototypeOf(B, A)`, `Reflect.setPrototypeOf`, +//! `B.__proto__ = A`): `B`'s `super()` runs `A`'s constructor on a `B` +//! instance. The whole program sees that receiver; per unit, `A`'s unit +//! must not prove `this` of `A`'s constructor any stronger. + +use super::*; +use crate::bindings::BindingFacts; + +fn hirs_of(mods: &[(&str, &str)]) -> Vec<(String, perry_hir::Module)> { + mods.iter() + .map(|(name, src)| { + let path = format!("{name}.ts"); + let module = perry_parser::parse_typescript(src, &path).expect("parses"); + let mut hir = perry_hir::lower_module(&module, name, &path).expect("lowers"); + for imp in &mut hir.imports { + if let Some(n) = imp.source.strip_prefix("./") { + imp.resolved_path = Some(format!("{n}.ts")); + } + } + (path, hir) + }) + .collect() +} + +/// The facts of `this` of class `class`'s constructor (module `module`), +/// per unit and whole program. +fn ctor_this( + mods: &[(&str, &str)], + module: &str, + class: &str, +) -> [(ProgramFacts, Option); 2] { + let hirs = hirs_of(mods); + let list: Vec<(String, &perry_hir::Module)> = + hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let path = format!("{module}.ts"); + let (_, hir) = hirs.iter().find(|(p, _)| *p == path).expect("module"); + let id = hir + .classes + .iter() + .find(|c| c.name == class) + .and_then(|c| c.constructor.as_ref()) + .expect("a class with a constructor") + .id; + let cfg = FactsConfig { + max_work: Some(50_000_000), + ..FactsConfig::default() + }; + [crate::compute_units(&list, &cfg), compute(&list, &cfg)].map(|pf| { + let b = pf + .bindings_for(&path) + .and_then(|mb| mb.this_of.iter().find(|(f, _)| *f == id)) + .map(|(_, b)| b.clone()); + (pf, b) + }) +} + +/// What a store `this.` in the constructor relies on: `this` holds +/// only instances of classes whose layout gives `key` a definite slot (a +/// slot store, no receiver check). +fn slot_store(pf: &ProgramFacts, this: &BindingFacts, key: &str) -> bool { + !this.other + && !this.class_ids.is_empty() + && this.class_ids.iter().all(|&c| { + pf.templates.iter().any(|t| { + t.class_id == Some(c) + && t.prefix_stable + && t + .fields + .iter() + .any(|f| f.key == key && f.slot.is_some() && f.definite) + }) + }) +} + +/// Per unit claims no more of the constructor's `this` than the whole +/// program: a `this.parentSaw` store per unit is a slot store only where it +/// is one in the whole program. +fn assert_not_stronger(mods: &[(&str, &str)], module: &str, class: &str) { + let [(pu, u), (pw, w)] = ctor_this(mods, module, class); + let w = w.expect("whole program has the constructor"); + // The test can fail: the whole program sees the relinked subclass's + // instance as the constructor's receiver. + assert!( + w.other || w.class_ids.len() > 1, + "whole program: {w:?}\n{}", + pw.dump_text() + ); + // `super()` of a class per unit follows its declared parent only where + // the whole program proves it unrelinked. + let stronger: Vec<_> = pu + .super_unrelinked + .difference(&pw.super_unrelinked) + .collect(); + assert!( + stronger.is_empty(), + "{mods:?}: per unit claims classes {stronger:?} unrelinked\n{}", + pu.dump_text() + ); + let Some(u) = u else { return }; + assert!( + !slot_store(&pu, &u, "parentSaw") || slot_store(&pw, &w, "parentSaw"), + "{mods:?}: per unit {u:?} is stronger than whole {w:?}\n{}", + pu.dump_text() + ); +} + +const ACTUAL: &str = "class ActualParent { constructor() { this.parentSaw = Object.keys(this).join(\",\"); this.dynamic = 1; } }"; + +const RELINKS: [&str; 3] = [ + "Object.setPrototypeOf(OrderedDefault, ActualParent);", + "Reflect.setPrototypeOf(OrderedDefault, ActualParent);", + "(OrderedDefault as any).__proto__ = ActualParent;", +]; + +const CTORS: [&str; 2] = ["", "constructor() { super(); }"]; + +#[test] +fn a_relinked_parent_constructor_runs_on_the_subclass_instance_in_one_module() { + for relink in RELINKS { + for ctor in CTORS { + let src = format!( + "class DeclaredParent {{}}\n{ACTUAL}\nclass OrderedDefault extends DeclaredParent {{ own = 2; {ctor} }}\n{relink}\nconst o: any = new OrderedDefault();\nconsole.log(o.parentSaw, Object.keys(o).join(\",\"));\nconst ap: any = new ActualParent();\nconsole.log(ap.parentSaw, ap.dynamic);" + ); + assert_not_stronger(&[("t", &src)], "t", "ActualParent"); + } + } +} + +#[test] +fn a_relinked_parent_of_another_module_runs_on_the_subclass_instance() { + let a = format!( + "export {ACTUAL}\nconst ap: any = new ActualParent();\nconsole.log(ap.parentSaw, ap.dynamic);" + ); + for relink in RELINKS { + for ctor in CTORS { + // The subclass and the relink in the importing module. + let m = format!( + "import {{ ActualParent }} from \"./a\";\nclass DeclaredParent {{}}\nclass OrderedDefault extends DeclaredParent {{ own = 2; {ctor} }}\n{relink}\nconst o: any = new OrderedDefault();\nconsole.log(o.parentSaw);" + ); + assert_not_stronger(&[("a", &a), ("m", &m)], "a", "ActualParent"); + // The subclass in a third module, the relink beside the `new`. + let b = format!( + "class DeclaredParent {{}}\nexport class OrderedDefault extends DeclaredParent {{ own = 2; {ctor} }}" + ); + let m = format!( + "import {{ ActualParent }} from \"./a\";\nimport {{ OrderedDefault }} from \"./b\";\n{relink}\nconst o: any = new OrderedDefault();\nconsole.log(o.parentSaw);" + ); + assert_not_stronger(&[("a", &a), ("b", &b), ("m", &m)], "a", "ActualParent"); + // The relink in the parent's own module, of another module's class. + let a2 = format!( + "import {{ OrderedDefault }} from \"./b\";\nexport {ACTUAL}\n{relink}\nconst ap: any = new ActualParent();\nconsole.log(ap.parentSaw, ap.dynamic);" + ); + let m = "import { OrderedDefault } from \"./b\";\nimport \"./a\";\nconst o: any = new OrderedDefault();\nconsole.log(o.parentSaw);"; + assert_not_stronger(&[("b", &b), ("a", &a2), ("m", m)], "a", "ActualParent"); + } + } +} + +#[test] +fn a_relinked_parent_in_one_unit_runs_on_the_subclass_instance() { + // The two classes name each other, so they are one declaration unit, + // and the relink runs in the subclass's static block, in that unit. + for relink in RELINKS { + let src = format!( + "class DeclaredParent {{}}\nclass ActualParent {{ constructor() {{ this.parentSaw = Object.keys(this).join(\",\"); this.dynamic = 1; }} sub() {{ return OrderedDefault; }} }}\nclass OrderedDefault extends DeclaredParent {{ own = 2; constructor() {{ super(); }} sup() {{ return ActualParent; }} static {{ {relink} }} }}\nconst o: any = new OrderedDefault();\nconst ap: any = new ActualParent();\nconsole.log(o.parentSaw, ap.parentSaw, ap.dynamic);" + ); + assert_not_stronger(&[("t", &src)], "t", "ActualParent"); + } +} diff --git a/crates/perry/tests/relinked_parent_ctor.rs b/crates/perry/tests/relinked_parent_ctor.rs new file mode 100644 index 00000000000..08bfdd6ae34 --- /dev/null +++ b/crates/perry/tests/relinked_parent_ctor.rs @@ -0,0 +1,84 @@ +//! A class relinked as another class's parent (`Object.setPrototypeOf(B, +//! A)`, `Reflect.setPrototypeOf`, `B.__proto__ = A`): `B`'s `super()` runs +//! `A`'s constructor on the `B` instance under construction. Per unit, the +//! relink is a statement unit of its own, so `A`'s unit and `B`'s unit must +//! both learn of it: `A`'s constructor stores into a receiver of `B`'s +//! layout, and `B`'s `super()` follows the live superclass. Both modes give +//! node's output. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn perry_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_perry")) +} + +fn clean(cmd: &mut Command) -> &mut Command { + for (key, _) in std::env::vars() { + if key.starts_with("PERRY_") || key == "NODE_OPTIONS" { + cmd.env_remove(key); + } + } + cmd +} + +/// Compile `source` (whole program when `whole`) and return its stdout. +fn compile_and_run(dir: &Path, source: &str, whole: bool) -> String { + let src = dir.join("main.ts"); + std::fs::write(&src, source).unwrap(); + let exe = dir.join(if whole { "main_w" } else { "main_u" }); + let mut cmd = Command::new(perry_bin()); + clean(&mut cmd).current_dir(dir).env("PERRY_NO_CACHE", "1"); + if whole { + cmd.env("PERRY_FACTS_UNITS", "0"); + } + let out = cmd + .arg("compile") + .arg(&src) + .arg("-o") + .arg(&exe) + .output() + .expect("compile"); + assert!( + out.status.success(), + "compile failed (whole={whole})\n{}", + String::from_utf8_lossy(&out.stderr) + ); + let ran = clean(&mut Command::new(&exe)).output().expect("run"); + assert!(ran.status.success(), "run failed: {:?}", ran.status); + String::from_utf8_lossy(&ran.stdout).into_owned() +} + +#[test] +fn a_relinked_parent_constructor_runs_on_the_subclass_instance() { + let relinks = [ + "Object.setPrototypeOf(OrderedDefault, ActualParent);", + "Reflect.setPrototypeOf(OrderedDefault, ActualParent);", + "(OrderedDefault as any).__proto__ = ActualParent;", + ]; + let ctors = ["", "constructor() { super(); }"]; + let dir = std::env::temp_dir().join(format!("perry_rlk_{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + for relink in relinks { + for ctor in ctors { + let source = format!( + "class DeclaredParent {{}}\n\ + class ActualParent {{ constructor() {{ this.parentSaw = Object.keys(this).join(\",\"); this.dynamic = 1; }} }}\n\ + class OrderedDefault extends DeclaredParent {{ own = 2; {ctor} }}\n\ + {relink}\n\ + const o: any = new OrderedDefault();\n\ + console.log(o.parentSaw, Object.keys(o).join(\",\"));\n\ + const ap: any = new ActualParent();\n\ + console.log(ap.parentSaw, ap.dynamic);\n" + ); + for whole in [false, true] { + assert_eq!( + compile_and_run(&dir, &source, whole), + " parentSaw,dynamic,own\n 1\n", + "whole={whole}\n{source}" + ); + } + } + } + let _ = std::fs::remove_dir_all(&dir); +} From 661a80a61f42e4fa92928712f80ec7255b92f4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:36:57 +0000 Subject: [PATCH 024/144] wpfacts: a class relinked as another unit's parent runs its constructor on that receiver A class given as a class object's [[Prototype]] (Object.setPrototypeOf, Reflect.setPrototypeOf, a __proto__ store) is that class's parent: its super() runs the given class's constructor on its instance. Per unit the relink is often a unit of its own, and two holes followed. The given class's unit kept this of its constructor as its own instances (DECISIONS 57 extended only classes an extends names). The relinking unit now reports another unit's class it gives as a parent (census extends ), and its unit publishes the constructor contract as for an extends. A class of the unit given to a class it does not construct (another unit's or unknown) escapes to the code running super(). Each unit claimed super_unrelinked for every class object it held, stand-ins included, and the driver joins the claims: a unit constructing another unit's class vouched it unrelinked. Only a class's own unit claims it now. --- crates/perry-wpfacts/src/gen.rs | 10 ++++++++ crates/perry-wpfacts/src/gen/export.rs | 5 +++- crates/perry-wpfacts/src/lib.rs | 6 +++++ crates/perry-wpfacts/src/model.rs | 9 ++++++++ crates/perry-wpfacts/src/solve.rs | 1 + crates/perry-wpfacts/src/solve/apply.rs | 29 ++++++++++++++++++++++++ crates/perry-wpfacts/src/solve/store.rs | 1 + crates/perry-wpfacts/src/summary.rs | 1 + crates/perry-wpfacts/src/units/driver.rs | 5 +++- 9 files changed, 65 insertions(+), 2 deletions(-) diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index a2a438aee7b..5d0d4942854 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -757,6 +757,16 @@ impl<'a> Gen<'a> { } } out.extend(self.override_reports()); + // A class of another unit this unit gave as a class object's parent + // (`Object.setPrototypeOf(B, A)`, `B.__proto__ = A`): a `super()` + // runs its constructor on a receiver its unit does not see, as for + // a class whose `extends` names it (DECISIONS 57). Not a break: its + // unit publishes the constructor contract. + for (d, (_, tm, _)) in &self.stand_ins { + if self.m.relinked_parents.contains(tm) { + out.push(format!("extends {d}")); + } + } out.sort(); out.dedup(); out diff --git a/crates/perry-wpfacts/src/gen/export.rs b/crates/perry-wpfacts/src/gen/export.rs index 463193de913..e341273e8b4 100644 --- a/crates/perry-wpfacts/src/gen/export.rs +++ b/crates/perry-wpfacts/src/gen/export.rs @@ -304,7 +304,10 @@ impl<'a> Gen<'a> { Some(tm) if published.is_some_and(|p| !p.broken(&self.m.tmpls[tm as usize].display)) => { - let extended = published.is_some_and(|p| p.extended(path_of(self, mi), name)); + let extended = published.is_some_and(|p| { + p.extended(path_of(self, mi), name) + || p.broken(&format!("extends {}", self.m.tmpls[tm as usize].display)) + }); self.publish_contract_class(Some((mi, a)), n, tm, extended, r); } _ => { diff --git a/crates/perry-wpfacts/src/lib.rs b/crates/perry-wpfacts/src/lib.rs index d3818d5a632..440d702edbc 100644 --- a/crates/perry-wpfacts/src/lib.rs +++ b/crates/perry-wpfacts/src/lib.rs @@ -656,6 +656,9 @@ fn compute_act<'a>( } else { g.m.fnproto_write.map(|r| g.m.reasons[r as usize].clone()) }; + // Per unit, a class's own unit says whether it is relinked: another + // unit's class (a stand-in) may be relinked where this unit does not + // see it, and the driver joins every unit's claims. let super_unrelinked = if stats.aborted { Default::default() } else { @@ -666,6 +669,9 @@ fn compute_act<'a>( let model::TokKind::ClassObj(tm) = kind else { return None; }; + if g.m.foreign.contains_key(tm) { + return None; + } let model::TmplKind::Class { class_id, .. } = g.m.tmpls[*tm as usize].kind else { return None; }; diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index a1c3315357b..d0d086f9fc5 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -715,6 +715,10 @@ pub enum Cons { /// them (`ObjectProto` collapses them), whose members the chain cannot /// name: the chain also holds an unknown prototype. ProtoGiven { protos: NodeId }, + /// The node holds a value given as the [[Prototype]] of class object + /// `of` (or of ⊤): `of`'s `super()` runs a class held here as its + /// parent, on an instance of `of`. + ParentGiven { of: Tok }, /// The node is `ProtoNode(t)`: dst ⊇ every prototype, transitively. ProtoClosure { dst: NodeId }, /// The node is the prototype closure of receiver `origin`: builtin @@ -919,6 +923,7 @@ impl Cons { Cons::CreateProto { .. } => "createproto", Cons::ChainUnion { .. } => "chainunion", Cons::ProtoGiven { .. } => "protogiven", + Cons::ParentGiven { .. } => "parentgiven", Cons::ProtoClosure { .. } => "protoclosure", Cons::ProtoMethods { .. } => "protomethods", Cons::AnyAccessors { .. } => "anyaccessors", @@ -1419,6 +1424,10 @@ pub struct Model { pub arr_named_srcs: FxHashMap>, /// Targets of a possible [[Prototype]] write (TOP included). pub super_relink: crate::pts::FxHashSet, + /// Another unit's classes this unit gives as a class object's parent: + /// their constructor runs on a receiver their unit does not see. The + /// census reports each (`Gen::reports`, `extends `). + pub relinked_parents: crate::pts::FxHashSet, /// Source constructor of a type-specialized instance layout (compiler data). pub constructor_origins: FxHashMap, pub constructor_this_done: crate::pts::FxHashSet<(CallId, TmplId, Tok)>, diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index dfc2ee9ec89..c045700e3be 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1816,6 +1816,7 @@ impl Model { format!("chainunion {n} {} {dst} {of}", self.key_name(*key)) } Cons::ProtoGiven { protos } => format!("protogiven {n} {protos}"), + Cons::ParentGiven { of } => format!("parentgiven {n} {of}"), Cons::ProtoClosure { dst } => format!("protoclosure {n} {dst}"), Cons::ProtoMethods { origin, key, call } => { format!("protomethods {n} {origin} {} {call}", self.key_name(*key)) diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index 336ec8492eb..43ef6edf4df 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -704,6 +704,9 @@ impl Model { } Cons::ProtoFrom { src } => { for &t in toks { + if t == TOP || matches!(self.toks[t as usize], TokKind::ClassObj(_)) { + self.cons(src, Cons::ParentGiven { of: t }); + } // `%Object.prototype%` is an immutable prototype exotic // object: setting its [[Prototype]] to anything but its // own (null) throws. @@ -734,6 +737,32 @@ impl Model { self.add_tok(protos, TOP); } } + // A class given as the parent of class object `of`: `of`'s + // `super()` runs its constructor on `of`'s instances. A class of + // this unit given to a class it constructs runs there + // (`Cons::SuperCall` follows the live superclass); given to + // another unit's class or to ⊤, the code running `super()` is + // unknown here, so the class escapes to it. Another unit's class + // is that unit's to run on the receiver: the census reports it + // (`extends `), and its unit publishes its constructor + // as it does for an `extends` naming it (DECISIONS 57). + Cons::ParentGiven { of } => { + let theirs = |m: &Self, t: Tok| match m.toks[t as usize] { + TokKind::ClassObj(tm) => m.foreign.contains_key(&tm), + _ => t == TOP, + }; + for &p in toks { + let TokKind::ClassObj(tm) = self.toks[p as usize] else { + continue; + }; + if self.foreign.contains_key(&tm) { + self.relinked_parents.insert(tm); + } else if theirs(self, of) { + let reason = self.reason("class given as the parent of a class unknown code constructs"); + self.escape(p, reason); + } + } + } Cons::ProtoOf { dst } => { let pk = self.atom("__proto__"); for &t in toks { diff --git a/crates/perry-wpfacts/src/solve/store.rs b/crates/perry-wpfacts/src/solve/store.rs index a019544a991..cd2ee18240a 100644 --- a/crates/perry-wpfacts/src/solve/store.rs +++ b/crates/perry-wpfacts/src/solve/store.rs @@ -362,6 +362,7 @@ impl Model { { self.super_relink.insert(t); if let TokKind::ClassObj(tm) = self.toks[t as usize] { + self.cons(src, Cons::ParentGiven { of: t }); let parent = self.proto_node(t); self.edge(src, parent); let instance = self.tok(TokKind::Obj(tm, NO_CTX)); diff --git a/crates/perry-wpfacts/src/summary.rs b/crates/perry-wpfacts/src/summary.rs index 2e52db0b2e8..e50fd2952a2 100644 --- a/crates/perry-wpfacts/src/summary.rs +++ b/crates/perry-wpfacts/src/summary.rs @@ -334,6 +334,7 @@ pub fn map_cons( of, }, ProtoGiven { protos } => ProtoGiven { protos: n(protos) }, + ParentGiven { of } => ParentGiven { of }, ProtoClosure { dst } => ProtoClosure { dst: n(dst) }, ProtoMethods { origin, key, call } => ProtoMethods { origin, diff --git a/crates/perry-wpfacts/src/units/driver.rs b/crates/perry-wpfacts/src/units/driver.rs index a7c5d0cee38..b2bf24febaf 100644 --- a/crates/perry-wpfacts/src/units/driver.rs +++ b/crates/perry-wpfacts/src/units/driver.rs @@ -1393,11 +1393,14 @@ pub(super) fn resolve_again( // An override report changes the overridden class's unit // (DECISIONS 59). // So does a typed-array field report (DECISIONS 126). + // So does a relinked parent's report (`extends `). let d = d .strip_prefix("override ") .or_else(|| d.strip_prefix("ta ")) .and_then(|r| r.split_once(" of ")) - .map_or(d.as_str(), |(_, c)| c); + .map(|(_, c)| c) + .or_else(|| d.strip_prefix("extends ")) + .unwrap_or(d.as_str()); match owner.get(d) { Some(sis) => work.extend(sis.iter().copied()), // No unit owns it: every unit again, but a unit over the bound From 8cd20c4f0f5cf578dbba409041ff5a5759b2cb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 23:08:21 +0000 Subject: [PATCH 025/144] changelog: relinked parent constructor this per unit --- changelog.d/rlk-relinked-ctor-this.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 changelog.d/rlk-relinked-ctor-this.md diff --git a/changelog.d/rlk-relinked-ctor-this.md b/changelog.d/rlk-relinked-ctor-this.md new file mode 100644 index 00000000000..4fad4fc9fac --- /dev/null +++ b/changelog.d/rlk-relinked-ctor-this.md @@ -0,0 +1,11 @@ +Per-unit compiles of a class relinked as another class's parent are correct. +After `Object.setPrototypeOf(B, A)`, `Reflect.setPrototypeOf(B, A)` or +`B.__proto__ = A`, `B`'s `super()` runs `A`'s constructor on a `B` instance. +When the relink sat in a different statement or module than `A` or `B`, two +per-unit facts were stronger than the whole program's: `this` of `A`'s +constructor held only `A` instances, and a unit that merely constructed `B` +vouched that `B` was never relinked, so `super()` called `B`'s declared +parent (wrong output, for example `undefined` instead of the fields `A`'s +constructor sets). Now the unit doing the relink reports the given class, and +that class's unit runs its constructor on any receiver, as it does for a class +an `extends` names. Only a class's own unit says whether it is relinked. From c298991fed63cac0762aa15a6e6c155e60a37fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 00:06:10 +0000 Subject: [PATCH 026/144] changelog: name the fragment for numbering --- .../{rlk-relinked-ctor-this.md => 0-rlk-relinked-ctor-this.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{rlk-relinked-ctor-this.md => 0-rlk-relinked-ctor-this.md} (100%) diff --git a/changelog.d/rlk-relinked-ctor-this.md b/changelog.d/0-rlk-relinked-ctor-this.md similarity index 100% rename from changelog.d/rlk-relinked-ctor-this.md rename to changelog.d/0-rlk-relinked-ctor-this.md From 0fe949941a4c56234b509e9a2d52cea976129807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 01:08:15 +0100 Subject: [PATCH 027/144] changelog: number the PR fragment --- ...-rlk-relinked-ctor-this.md => 13589-rlk-relinked-ctor-this.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-rlk-relinked-ctor-this.md => 13589-rlk-relinked-ctor-this.md} (100%) diff --git a/changelog.d/0-rlk-relinked-ctor-this.md b/changelog.d/13589-rlk-relinked-ctor-this.md similarity index 100% rename from changelog.d/0-rlk-relinked-ctor-this.md rename to changelog.d/13589-rlk-relinked-ctor-this.md From 3c815b13f56c5a83a0171775a33fa44b838d7880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 01:21:50 +0000 Subject: [PATCH 028/144] runtime: constructing AbortSignal through a value or a live super() throws Illegal constructor --- changelog.d/13589-rlk-relinked-ctor-this.md | 4 ++++ .../src/object/class_registry/construct/builtin.rs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.d/13589-rlk-relinked-ctor-this.md b/changelog.d/13589-rlk-relinked-ctor-this.md index 4fad4fc9fac..c1dab661489 100644 --- a/changelog.d/13589-rlk-relinked-ctor-this.md +++ b/changelog.d/13589-rlk-relinked-ctor-this.md @@ -9,3 +9,7 @@ parent (wrong output, for example `undefined` instead of the fields `A`'s constructor sets). Now the unit doing the relink reports the given class, and that class's unit runs its constructor on any receiver, as it does for a class an `extends` names. Only a class's own unit says whether it is relinked. + +Constructing `AbortSignal` through a value (`const S = AbortSignal; new S()`) +or through the live `super()` of `class X extends AbortSignal` now throws +`TypeError: Illegal constructor`, as in Node; it used to return a signal. diff --git a/crates/perry-runtime/src/object/class_registry/construct/builtin.rs b/crates/perry-runtime/src/object/class_registry/construct/builtin.rs index 88b5be2fd4c..cedaab46e04 100644 --- a/crates/perry-runtime/src/object/class_registry/construct/builtin.rs +++ b/crates/perry-runtime/src/object/class_registry/construct/builtin.rs @@ -48,7 +48,10 @@ pub(super) unsafe fn construct( BuiltinConstructorIdentity::BigInt => { return Some(crate::error::js_throw_bigint_constructor_type_error()); } - BuiltinConstructorIdentity::Navigator => { + // WHATWG: the `AbortSignal` constructor throws; signals come from an + // `AbortController` or the static factories. A direct `new`, a value + // `new` and a live `super()` of a subclass all reach this switch. + BuiltinConstructorIdentity::Navigator | BuiltinConstructorIdentity::AbortSignal => { return Some(crate::error::js_throw_illegal_constructor_type_error()); } BuiltinConstructorIdentity::Iterator => { From c69f3560c55857f618eb4d7d20b6e835d1c3aecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:42:38 +0000 Subject: [PATCH 029/144] test: a standalone class constructor stores this fields through its own facts --- .../perry/tests/standalone_ctor_this_facts.rs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 crates/perry/tests/standalone_ctor_this_facts.rs diff --git a/crates/perry/tests/standalone_ctor_this_facts.rs b/crates/perry/tests/standalone_ctor_this_facts.rs new file mode 100644 index 00000000000..89c40c20db1 --- /dev/null +++ b/crates/perry/tests/standalone_ctor_this_facts.rs @@ -0,0 +1,96 @@ +//! A class's standalone constructor (`___constructor`, the +//! body `new` of a class value and `super(...)` call) reads the facts of +//! the constructor's own `this`: the store `this.k = v` into a definite +//! birth field of a prefix-stable class is a slot store, as it is in the +//! inlined `new Packet(...)`, not the by-name `js_put_value_set` site. +//! +//! The standalone body used to be compiled as a function with id 0, so its +//! `this` read the facts of whichever function has HIR id 0 (here `first`). + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn perry_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_perry")) +} + +fn clean(cmd: &mut Command) -> &mut Command { + for (key, _) in std::env::vars() { + if key.starts_with("PERRY_") || key == "NODE_OPTIONS" { + cmd.env_remove(key); + } + } + cmd +} + +/// Compile `source`; return the saved LLVM IR of the entry module and the +/// program's stdout. +fn compile_and_run(dir: &Path, source: &str) -> (String, String) { + let src = dir.join("main.ts"); + std::fs::write(&src, source).unwrap(); + let exe = dir.join("main_bin"); + let ll_dir = dir.join("ll"); + std::fs::create_dir_all(&ll_dir).unwrap(); + let mut cmd = Command::new(perry_bin()); + let out = clean(&mut cmd) + .current_dir(dir) + .env("PERRY_NO_CACHE", "1") + .env("PERRY_SAVE_LL", &ll_dir) + .arg("compile") + .arg(&src) + .arg("-o") + .arg(&exe) + .output() + .expect("compile"); + assert!( + out.status.success(), + "compile failed\n{}", + String::from_utf8_lossy(&out.stderr) + ); + let ll = std::fs::read_to_string(ll_dir.join("main_ts.ll")).expect("saved main_ts.ll"); + let mut run = Command::new(&exe); + let ran = clean(&mut run).output().expect("run"); + assert!(ran.status.success(), "run failed: {:?}", ran.status); + (ll, String::from_utf8_lossy(&ran.stdout).into_owned()) +} + +/// The body of the function defined as `@(`. +fn body<'a>(ir: &'a str, symbol: &str) -> &'a str { + let head = format!("@{symbol}("); + let at = ir + .match_indices("define ") + .map(|(i, _)| i) + .find(|&i| { + ir[i..] + .lines() + .next() + .is_some_and(|line| line.contains(&head)) + }) + .unwrap_or_else(|| panic!("no function {symbol}")); + let end = ir[at..].find("\n}\n").map_or(ir.len(), |e| at + e); + &ir[at..end] +} + +const PACKETS: &str = r#" +function first() { return 1; } +class Packet { + constructor(link, kind) { this.link = link; this.kind = kind; this.count = 0; } + bump() { this.count = this.count + 1; return this.count; } +} +const make = [() => new Packet(null, 1)][0]; +let s = 0; +for (let i = 0; i < 1000; i++) { const p = new Packet(null, i); s += p.kind + p.bump(); } +console.log(s + first() + make().kind); +"#; + +#[test] +fn standalone_constructor_stores_this_fields_through_its_own_facts() { + let dir = tempfile::tempdir().unwrap(); + let (ir, out) = compile_and_run(dir.path(), PACKETS); + assert_eq!(out.trim(), "500502"); + let ctor = body(&ir, "main_ts__Packet_constructor"); + assert!( + !ctor.contains("@js_put_value_set("), + "the standalone constructor's `this.k = v` stores are by-name sites:\n{ctor}" + ); +} From 87baefc710bccf8628ef3a5172b8372827ce0b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:55:49 +0000 Subject: [PATCH 030/144] codegen: a standalone class constructor is compiled under its constructor's function id --- crates/perry-codegen/src/codegen/class_artifacts.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/perry-codegen/src/codegen/class_artifacts.rs b/crates/perry-codegen/src/codegen/class_artifacts.rs index cfc32b17f6b..b673380a0f9 100644 --- a/crates/perry-codegen/src/codegen/class_artifacts.rs +++ b/crates/perry-codegen/src/codegen/class_artifacts.rs @@ -691,8 +691,12 @@ pub(super) fn emit_class_artifacts(c: ClassArtifactsCtx<'_>) -> Result<()> { } else { (Vec::new(), Vec::new(), Vec::new()) }; + // The constructor's own HIR id: facts, definite-init proofs and + // allocation hotness are keyed by it (`compile_method` reads + // `method.id`). A synthesized default constructor has no HIR + // function, so it gets an id nothing is keyed by. let ctor_as_method = perry_hir::Function { - id: 0, + id: class.constructor.as_ref().map_or(u32::MAX, |c| c.id), name: format!("{}_constructor", class.name), type_params: Vec::new(), params: ctor_body.0, From eb926c4af3be19e82a40ec1d723a949c720f7442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:43:32 +0000 Subject: [PATCH 031/144] changelog: standalone constructor function id --- changelog.d/0-ctor-own-fn-id.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog.d/0-ctor-own-fn-id.md diff --git a/changelog.d/0-ctor-own-fn-id.md b/changelog.d/0-ctor-own-fn-id.md new file mode 100644 index 00000000000..08f16cee0d7 --- /dev/null +++ b/changelog.d/0-ctor-own-fn-id.md @@ -0,0 +1,7 @@ +A class's standalone constructor (the body `super(...)` and `new` of a class +value run) is compiled under its constructor's own function id instead of +id 0. Its `this`, definite-init proofs and allocation hotness were read +from whichever function had HIR id 0; now a store `this.k = v` into a +definite birth field of a prefix-stable class is the slot store the inlined +`new C(...)` already emits. A synthesized default constructor gets an id +nothing is keyed by. From 72d58e27a530c019ccfe13796102c8826ff7b74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:45:44 +0100 Subject: [PATCH 032/144] changelog: number the PR fragment --- changelog.d/{0-ctor-own-fn-id.md => 13584-ctor-own-fn-id.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-ctor-own-fn-id.md => 13584-ctor-own-fn-id.md} (100%) diff --git a/changelog.d/0-ctor-own-fn-id.md b/changelog.d/13584-ctor-own-fn-id.md similarity index 100% rename from changelog.d/0-ctor-own-fn-id.md rename to changelog.d/13584-ctor-own-fn-id.md From c111dfcdd8b73fee63331cd9eaeb1cf034ae631c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 01:37:45 +0000 Subject: [PATCH 033/144] cjs_wrap: a CommonJS namespace exports only the keys the module writes The CJS-to-ESM wrap hoists top-level classes out of the wrapper for class identity and exported every hoisted class by name. A private class became a namespace key (SerdeContextConfig and UnionSerde in @aws-sdk/core protocols), `module.exports = Class` also exported `Class`, and `exports.X = wrap(X)` exported the class instead of the key's value. A hoisted class is now exported directly only under a key the module writes, when every write of the key stores the class binding (export_key_is_own_binding, built on the same export-write scan as extract_exports_from_source) and the binding is never reassigned. Other keys are read from module.exports through a mangled snapshot that does not redeclare the class binding. --- changelog.d/cnl-cjs-namespace.md | 11 ++ .../compile/cjs_wrap/extract_exports.rs | 114 +++++++++++++++--- .../src/commands/compile/cjs_wrap/mod.rs | 2 +- .../src/commands/compile/cjs_wrap/tests.rs | 17 +-- .../cjs_wrap/tests/namespace_exports.rs | 70 +++++++++++ .../src/commands/compile/cjs_wrap/wrap.rs | 47 ++++---- ...p_cjs_namespace_exports_default_helper.cjs | 5 + .../gap_cjs_namespace_exports_helper.cjs | 22 ++++ test-files/test_gap_cjs_namespace_exports.ts | 31 +++++ 9 files changed, 274 insertions(+), 45 deletions(-) create mode 100644 changelog.d/cnl-cjs-namespace.md create mode 100644 crates/perry/src/commands/compile/cjs_wrap/tests/namespace_exports.rs create mode 100644 test-files/gap_cjs_namespace_exports_default_helper.cjs create mode 100644 test-files/gap_cjs_namespace_exports_helper.cjs create mode 100644 test-files/test_gap_cjs_namespace_exports.ts diff --git a/changelog.d/cnl-cjs-namespace.md b/changelog.d/cnl-cjs-namespace.md new file mode 100644 index 00000000000..1223aceec11 --- /dev/null +++ b/changelog.d/cnl-cjs-namespace.md @@ -0,0 +1,11 @@ +A CommonJS module's namespace now holds exactly the keys the module writes to +`exports`/`module.exports` (plus `default` and `module.exports`), as in Node. +The CJS-to-ESM wrap hoists top-level classes to module scope for class +identity, and it used to export every hoisted class: a private helper class +(`@aws-sdk/core` protocols' `SerdeContextConfig`, `UnionSerde`) showed up in +`import * as ns`, and `module.exports = Class` also exported `Class` by name. A +hoisted class is now a direct export only under a key the module writes, when +every write of that key stores the class binding itself (`exports.X = X`, +`{ X }`, esbuild's `X: () => X`) and the binding is never reassigned. Any other +key value, such as `exports.X = wrap(X)`, is read from `module.exports` instead +of being replaced by the class. diff --git a/crates/perry/src/commands/compile/cjs_wrap/extract_exports.rs b/crates/perry/src/commands/compile/cjs_wrap/extract_exports.rs index 6bc9bbf9898..b9b7262167e 100644 --- a/crates/perry/src/commands/compile/cjs_wrap/extract_exports.rs +++ b/crates/perry/src/commands/compile/cjs_wrap/extract_exports.rs @@ -405,7 +405,7 @@ pub fn extract_object_literal_exports_from_require(source: &str) -> Vec<(String, /// through `__toCommonJS` into `module.exports`. pub fn extract_exports_from_source(source: &str) -> Vec { let mut names = Vec::new(); - let push_unique = |names: &mut Vec, name: &str| { + for_each_export_write(source, &mut |name, _| { if name == "__esModule" { return; } @@ -426,8 +426,73 @@ pub fn extract_exports_from_source(source: &str) -> Vec { if !names.contains(&owned) { names.push(owned); } + }); + names +} + +/// True when this module provably puts its own top-level binding `name` on +/// `exports` under the key `name`: at least one recognized export write of +/// that key exists, and every one of them stores exactly the bare identifier +/// `name` (`exports.X = X`, `module.exports = { X }`, `{ X: X }`, or +/// esbuild's `__export(t, { X: () => X })`). Any other write of the key (a +/// different value, a defineProperty descriptor) refutes it. Only then may +/// the wrap surface the key as the binding itself (`export { X }`); otherwise +/// the key's value is read from `module.exports`. +pub fn export_key_is_own_binding(source: &str, name: &str) -> bool { + let mut proven = false; + let mut refuted = false; + for_each_export_write(source, &mut |key, value| { + if key != name { + return; + } + if value.is_some_and(|value| value_is_bare_identifier(value, name)) { + proven = true; + } else { + refuted = true; + } + }); + proven && !refuted +} + +/// Does the expression text `value` (which may run on to the end of the +/// source) consist of exactly the identifier `name`? The identifier must end +/// the expression: the next token is a statement or list terminator, or an +/// ASI line break followed by a token that cannot continue the expression. +fn value_is_bare_identifier(value: &str, name: &str) -> bool { + let Some(rest) = value.trim_start().strip_prefix(name) else { + return false; + }; + let after = rest.trim_start_matches([' ', '\t']); + let Some(&next) = after.as_bytes().first() else { + return true; }; + if matches!(next, b';' | b',' | b'}' | b')') { + return true; + } + if !matches!(next, b'\n' | b'\r') { + return false; + } + let following = after.trim_start(); + let Some(&first) = following.as_bytes().first() else { + return true; + }; + let word_len = following + .bytes() + .take_while(|c| c.is_ascii_alphanumeric() || *c == b'_' || *c == b'$') + .count(); + let word = &following[..word_len]; + ((first.is_ascii_alphabetic() || first == b'_' || first == b'$') + && word != "in" + && word != "instanceof") + || matches!(first, b';' | b'}') +} +/// Calls `out(key, value)` for every recognized write of a named export key. +/// `value` is the written expression's text (for an assignment, the source +/// from after its `=` on; for an object-literal entry, the entry's value; for +/// an esbuild getter, the getter's body expression), or `None` when the shape +/// does not show the stored value textually (a defineProperty descriptor). +fn for_each_export_write(source: &str, out: &mut dyn FnMut(&str, Option<&str>)) { // Shape 1: `exports.X = ...` / `module.exports.X = ...` // The boundary class excludes `.` so `e.exports.X = …` (a property write on // some OTHER object — e.g. a webpack/ncc inner module's own exports param, @@ -449,8 +514,8 @@ pub fn extract_exports_from_source(source: &str) -> Vec { .into_iter() .flatten() { - if let Some(m) = cap.get(1) { - push_unique(&mut names, m.as_str()); + if let (Some(write), Some(m)) = (cap.get(0), cap.get(1)) { + out(m.as_str(), Some(&source[write.end()..])); } } @@ -472,8 +537,8 @@ pub fn extract_exports_from_source(source: &str) -> Vec { .into_iter() .flatten() { - if let Some(m) = cap.get(1) { - push_unique(&mut names, m.as_str()); + if let (Some(write), Some(m)) = (cap.get(0), cap.get(1)) { + out(m.as_str(), Some(&source[write.end()..])); } } @@ -503,7 +568,7 @@ pub fn extract_exports_from_source(source: &str) -> Vec { .get(call.start()..) .is_some_and(|rest| rest.starts_with(b"Object")) { - push_unique(&mut names, cap.get(2).unwrap().as_str()); + out(cap.get(2).unwrap().as_str(), None); } } @@ -605,7 +670,7 @@ pub fn extract_exports_from_source(source: &str) -> Vec { } let body_end = q - 1; // points at the closing `}` let body = &source[body_start..body_end]; - extract_object_literal_keys(body, &mut |name| push_unique(&mut names, name)); + extract_object_literal_entries(body, &mut |name, value| out(name, Some(value))); search_from = q; } @@ -683,22 +748,33 @@ pub fn extract_exports_from_source(source: &str) -> Vec { } if depth == 0 { let body = &source[open + 1..q - 1]; - extract_object_literal_keys(body, &mut |name| push_unique(&mut names, name)); + // Each entry is a getter `() => `; the export's value is + // ``. Any other entry form does not show the value. + extract_object_literal_entries(body, &mut |name, value| { + let getter_body = value + .trim_start() + .strip_prefix("()") + .and_then(|rest| rest.trim_start().strip_prefix("=>")); + out(name, getter_body); + }); } } - - names } -/// Extract top-level keys from an object-literal body (the text between +/// Extract top-level entries from an object-literal body (the text between /// `{` and `}`, exclusive). Skips nested braces / brackets / parens so /// `fn: function() { return 1; }` doesn't pull `return` as a key. Calls -/// `out` with each shorthand or `name:` key encountered at depth 0. -pub fn extract_object_literal_keys(body: &str, out: &mut dyn FnMut(&str)) { +/// `out(key, value)` for each shorthand or `name:` entry at depth 0: the +/// shorthand `{ name }` reports `name` itself as its value, `{ name: }` +/// reports `` (up to the next depth-0 `,` or the end of the body). +fn extract_object_literal_entries(body: &str, out: &mut dyn FnMut(&str, &str)) { let bytes = body.as_bytes(); let mut i = 0; let mut at_entry_start = true; let mut depth: i32 = 0; + // An explicit `name: ` entry whose value is still being scanned: + // (key start, key end, value start). + let mut open_entry: Option<(usize, usize, usize)> = None; while i < bytes.len() { let b = bytes[i]; match b { @@ -712,6 +788,9 @@ pub fn extract_object_literal_keys(body: &str, out: &mut dyn FnMut(&str)) { i += 1; } b',' if depth == 0 => { + if let Some((key_start, key_end, value_start)) = open_entry.take() { + out(&body[key_start..key_end], &body[value_start..i]); + } at_entry_start = true; i += 1; } @@ -739,8 +818,10 @@ pub fn extract_object_literal_keys(body: &str, out: &mut dyn FnMut(&str)) { j += 1; } // Accept shorthand (`,` / end-of-body) or explicit key (`:`). - if j == bytes.len() || bytes[j] == b',' || bytes[j] == b':' { - out(name); + if j == bytes.len() || bytes[j] == b',' { + out(name, name); + } else if bytes[j] == b':' { + open_entry = Some((start, i, j + 1)); } at_entry_start = false; } else { @@ -757,4 +838,7 @@ pub fn extract_object_literal_keys(body: &str, out: &mut dyn FnMut(&str)) { } } } + if let Some((key_start, key_end, value_start)) = open_entry { + out(&body[key_start..key_end], &body[value_start..]); + } } diff --git a/crates/perry/src/commands/compile/cjs_wrap/mod.rs b/crates/perry/src/commands/compile/cjs_wrap/mod.rs index ed841c7b53c..f7996c5672e 100644 --- a/crates/perry/src/commands/compile/cjs_wrap/mod.rs +++ b/crates/perry/src/commands/compile/cjs_wrap/mod.rs @@ -73,7 +73,7 @@ mod static_edge_tests; use deferred_requires::deferred_require_specs_for_path; use detect::is_js_reserved_word; use extract_exports::{ - extract_exports_from_source, extract_named_exports_from_require, + export_key_is_own_binding, extract_exports_from_source, extract_named_exports_from_require, extract_object_literal_exports_from_require, extract_single_module_exports_assignment, module_reexport_specs, }; diff --git a/crates/perry/src/commands/compile/cjs_wrap/tests.rs b/crates/perry/src/commands/compile/cjs_wrap/tests.rs index 395a426ef6a..8793c635c25 100644 --- a/crates/perry/src/commands/compile/cjs_wrap/tests.rs +++ b/crates/perry/src/commands/compile/cjs_wrap/tests.rs @@ -17,6 +17,7 @@ use std::path::PathBuf; mod detect_tests; mod flat_export_collision; mod hoist_scanner; +mod namespace_exports; mod source_graph; // #5247: the wrapped output must report where the ORIGINAL body begins, and @@ -940,7 +941,9 @@ fn wrap_emits_direct_default_export_for_class_module_exports() { "should bypass _cjs for single-class module.exports, got:\n{}", wrapped ); - assert!(wrapped.contains("export { Child };")); + // `module.exports = Child` exports no key named `Child` (node's + // namespace is `default` + `module.exports`): no named export. + assert!(!wrapped.contains("export { Child };"), "{wrapped}"); } #[test] @@ -964,8 +967,8 @@ fn wrap_flat_emits_class_module_exports_that_closes_over_top_level_const() { wrapped ); assert!( - wrapped.contains("export { StackUtils };"), - "expected named export of StackUtils, got:\n{}", + !wrapped.contains("export { StackUtils };"), + "`module.exports = StackUtils` exports no `StackUtils` key, got:\n{}", wrapped ); assert!( @@ -1221,8 +1224,8 @@ fn wrap_rewrites_module_exports_class_expression_named() { wrapped ); assert!( - wrapped.contains("export { Abstract };"), - "expected named re-export of Abstract for class identity, got:\n{}", + !wrapped.contains("export { Abstract };"), + "`module.exports = class Abstract` exports no `Abstract` key, got:\n{}", wrapped ); assert!( @@ -1251,8 +1254,8 @@ fn wrap_rewrites_module_exports_class_expression_with_extends() { wrapped ); assert!( - wrapped.contains("export { Child };"), - "expected named re-export of Child, got:\n{}", + !wrapped.contains("export { Child };"), + "`module.exports = class Child` exports no `Child` key, got:\n{}", wrapped ); } diff --git a/crates/perry/src/commands/compile/cjs_wrap/tests/namespace_exports.rs b/crates/perry/src/commands/compile/cjs_wrap/tests/namespace_exports.rs new file mode 100644 index 00000000000..937df11e5db --- /dev/null +++ b/crates/perry/src/commands/compile/cjs_wrap/tests/namespace_exports.rs @@ -0,0 +1,70 @@ +//! A CommonJS module's ESM view exports exactly the keys the module writes to +//! `exports`/`module.exports`. Hoisting a top-level class out of the wrapper +//! (for class identity) must not turn the class into an export, and a key is +//! the class binding itself only when every write of the key stores it. +use super::super::extract_exports::export_key_is_own_binding; +use super::*; + +#[test] +fn hoisted_private_class_is_not_exported() { + let source = r#""use strict"; +class Hidden { hi() { return 1; } } +class Base { base() { return 2; } } +class Pub extends Base { pub() { return new Hidden().hi(); } } +exports.Pub = Pub; +exports.Alias = Base; +"#; + let wrapped = wrap_commonjs(source, &PathBuf::from("/tmp/protocols.cjs")); + // All three classes are hoisted to module scope. + assert!(wrapped.contains("class Hidden {"), "{wrapped}"); + assert!(wrapped.contains("export { Pub };"), "{wrapped}"); + assert!(!wrapped.contains("export { Hidden };"), "{wrapped}"); + assert!(!wrapped.contains("export { Base };"), "{wrapped}"); + // `Alias` is the `Base` class read back from the module's exports. + assert!(wrapped.contains("export const Alias = _cjs.Alias;"), "{wrapped}"); +} + +#[test] +fn hoisted_class_key_holding_another_value_reads_the_export() { + let source = r#"class Boxed { boxed() { return 1; } } +function box(C) { return { inner: C }; } +exports.Boxed = box(Boxed); +"#; + let wrapped = wrap_commonjs(source, &PathBuf::from("/tmp/boxed.cjs")); + assert!(wrapped.contains("class Boxed {"), "{wrapped}"); + assert!(!wrapped.contains("export { Boxed };"), "{wrapped}"); + // The key's value is the module's, under a name that does not redeclare + // the module-scope class binding. + assert!(wrapped.contains("const __cjsexp_Boxed = _cjs.Boxed;"), "{wrapped}"); + assert!(wrapped.contains("export { __cjsexp_Boxed as Boxed };"), "{wrapped}"); +} + +#[test] +fn export_key_is_own_binding_needs_every_write_to_store_the_binding() { + for source in [ + "exports.X = X;", + "module.exports.X = X\nexports.Y = Y\n", + "exports['X'] = X;", + "module.exports = { X };", + "module.exports = { X: X, Y: 1 };", + "__export(src_exports, {\n X: () => X,\n Y: () => other\n});", + ] { + assert!(export_key_is_own_binding(source, "X"), "{source}"); + } + for source in [ + "", + "exports.Y = X;", + "exports.X = wrap(X);", + "exports.X = X.inner;", + "exports.X = X\n.inner;", + "exports.X = X = other;", + "exports.XY = XY;", + "exports.X = X;\nexports.X = other;", + "module.exports = { X: () => X };", + "module.exports = { X: other };", + "Object.defineProperty(exports, 'X', { get: () => X });", + "exports.X = X;\nObject.defineProperty(exports, \"X\", { value: 1 });", + ] { + assert!(!export_key_is_own_binding(source, "X"), "{source}"); + } +} diff --git a/crates/perry/src/commands/compile/cjs_wrap/wrap.rs b/crates/perry/src/commands/compile/cjs_wrap/wrap.rs index dda813db7ed..fba2e88f373 100644 --- a/crates/perry/src/commands/compile/cjs_wrap/wrap.rs +++ b/crates/perry/src/commands/compile/cjs_wrap/wrap.rs @@ -1172,8 +1172,12 @@ pub(in crate::commands::compile) fn wrap_commonjs_with_addon_paths( is_global_value_builtin_name(n) && !identifier_is_declared_binding(source, n) }; let export_snapshot = |n: &str| { - if flat_default_class.is_some() || builtin_value_global_collision(n) { - // Flat emission keeps the original bindings at module scope. + if flat_default_class.is_some() + || builtin_value_global_collision(n) + || hoisted_class_names.iter().any(|class| class == n) + { + // Flat emission keeps the original bindings at module scope, and + // a hoisted class is a module-scope binding of the same name. // Alias all snapshots, including destructuring and multi-declarator // bindings the lexical scanner misses. Read the CJS property, which // can differ from the same-named original binding. @@ -1183,16 +1187,6 @@ pub(in crate::commands::compile) fn wrap_commonjs_with_addon_paths( } }; - let direct_class_exports = if hoisted_class_names.is_empty() { - String::new() - } else { - hoisted_class_names - .iter() - .map(|n| format!("export {{ {} }};", n)) - .collect::>() - .join("\n") - }; - // Issue #665 follow-up: detect `(?:module\.)?exports\.X = require('Y')` // patterns and forward them as direct ESM re-exports of `Y`'s default // export. This preserves class identity through index-file aggregators @@ -1253,9 +1247,27 @@ pub(in crate::commands::compile) fn wrap_commonjs_with_addon_paths( .map(|(n, _)| n.clone()) .collect(); + // A hoisted class surfaces as a direct binding (`export { X }`, keeping + // class identity for the consumer) only under an export key the module + // actually writes, and only when every write of that key stores the class + // binding itself (`exports.X = X`) and the binding is never reassigned. + // A namespace holds exactly the module's exports: a hoisted class the + // module keeps private is no export, and a key holding another value + // (`exports.X = wrap(X)`) is read from `module.exports` like any other. + let direct_class_names: Vec<&String> = hoisted_class_names + .iter() + .filter(|n| named_exports.contains(n) && !named_reexport_names.contains(n)) + .filter(|n| export_key_is_own_binding(source, n) && !identifier_is_reassigned(source, n)) + .collect(); + let direct_class_exports = direct_class_names + .iter() + .map(|n| format!("export {{ {n} }};")) + .collect::>() + .join("\n"); + let named_export_decls = named_exports .iter() - .filter(|n| !hoisted_class_names.contains(n)) + .filter(|n| !direct_class_names.contains(n)) .filter(|n| !named_reexport_names.contains(n)) .map(|n| export_snapshot(n)) .collect::>() @@ -1750,14 +1762,6 @@ function __perry_require_static_{i}() {{ // still carries any sibling classes we DID hoist; `{flat_class}` itself // was refused a hoist (it closes over an IIFE-local), so it stays in // `{body_for_iife}` and lands at module scope here unchanged. - // An explicit CJS property with the class's name already has its - // snapshot/re-export below. Do not export that name a second time. - let flat_class_named_export = - if named_exports.contains(flat_class) || named_reexport_names.contains(flat_class) { - String::new() - } else { - format!("export {{ {flat_class} }};") - }; format!( r#"{imports} {cjs_link_decls} @@ -1770,7 +1774,6 @@ function __perry_require_static_{i}() {{ const _cjs = __cjs_module.exports; {path_register} export default {flat_class}; -{flat_class_named_export} {direct_class_exports} {direct_named_reexports} {named_export_decls} diff --git a/test-files/gap_cjs_namespace_exports_default_helper.cjs b/test-files/gap_cjs_namespace_exports_default_helper.cjs new file mode 100644 index 00000000000..56acf3163b7 --- /dev/null +++ b/test-files/gap_cjs_namespace_exports_default_helper.cjs @@ -0,0 +1,5 @@ +"use strict"; +class Solo { + solo() { return "solo"; } +} +module.exports = Solo; diff --git a/test-files/gap_cjs_namespace_exports_helper.cjs b/test-files/gap_cjs_namespace_exports_helper.cjs new file mode 100644 index 00000000000..28c9c85b978 --- /dev/null +++ b/test-files/gap_cjs_namespace_exports_helper.cjs @@ -0,0 +1,22 @@ +"use strict"; +// Top-level declarations this module does NOT export: a namespace import must +// not see them (node takes the names from what the module assigns to exports). +class Hidden { + hi() { return "hidden"; } +} +class Base { + base() { return "base"; } +} +class Pub extends Base { + pub() { return new Hidden().hi(); } +} +class Boxed { + boxed() { return "boxed"; } +} +function helperFn() { return "helperFn"; } +const helperConst = "helperConst"; +function box(C) { return { inner: C }; } +exports.Pub = Pub; +exports.Alias = Base; +exports.Boxed = box(Boxed); +exports.run = () => helperFn() + "/" + helperConst; diff --git a/test-files/test_gap_cjs_namespace_exports.ts b/test-files/test_gap_cjs_namespace_exports.ts new file mode 100644 index 00000000000..438654d8b7b --- /dev/null +++ b/test-files/test_gap_cjs_namespace_exports.ts @@ -0,0 +1,31 @@ +// A CommonJS module's namespace holds exactly what the module puts on +// `exports`/`module.exports` (plus `default` and `module.exports`), never its +// non-exported top-level classes, functions or consts. +import * as ns from "./gap_cjs_namespace_exports_helper.cjs"; +import lib from "./gap_cjs_namespace_exports_helper.cjs"; +import * as solo from "./gap_cjs_namespace_exports_default_helper.cjs"; +import Solo from "./gap_cjs_namespace_exports_default_helper.cjs"; +import { Pub, Alias } from "./gap_cjs_namespace_exports_helper.cjs"; + +const nsAny: any = ns; +console.log("keys", Object.keys(ns).sort().join(",")); +for (const name of ["Hidden", "Base", "Pub", "Alias", "Boxed", "helperFn", "helperConst", "box", "run"]) { + console.log(name, name in ns, typeof nsAny[name], name in lib); +} +console.log("default keys", Object.keys(lib).sort().join(",")); +console.log("pub", new nsAny.Pub().pub(), new nsAny.Pub().base(), nsAny.Pub === lib.Pub); +console.log("alias", new nsAny.Alias().base(), nsAny.Alias === lib.Alias); +console.log("boxed", typeof nsAny.Boxed, typeof nsAny.Boxed.inner, new nsAny.Boxed.inner().boxed(), nsAny.Boxed === lib.Boxed); +console.log("run", nsAny.run()); +console.log("solo keys", Object.keys(solo).sort().join(",")); +const soloAny: any = solo; +console.log("solo", "Solo" in solo, typeof soloAny.Solo, soloAny.default === Solo, new Solo().solo()); + +// Class identity of the exported classes survives (subclassing, super calls). +class SubSolo extends Solo { + sub() { return super.solo() + "!"; } +} +class SubPub extends Pub { + sub() { return super.pub() + "/" + super.base() + "!"; } +} +console.log("sub", new SubSolo().sub(), new SubSolo() instanceof Solo, new SubPub().sub(), new SubPub() instanceof Alias); From 610c439f6f8250019df7b60cfb4be3ea3dca648a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 02:29:34 +0000 Subject: [PATCH 034/144] changelog: fragment for numbering --- changelog.d/{cnl-cjs-namespace.md => 0-cnl-cjs-namespace.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{cnl-cjs-namespace.md => 0-cnl-cjs-namespace.md} (100%) diff --git a/changelog.d/cnl-cjs-namespace.md b/changelog.d/0-cnl-cjs-namespace.md similarity index 100% rename from changelog.d/cnl-cjs-namespace.md rename to changelog.d/0-cnl-cjs-namespace.md From 1cf8c83273c72f526c22e09d74dfc65b4a5699f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 03:32:26 +0100 Subject: [PATCH 035/144] changelog: number the PR fragment --- .../{0-cnl-cjs-namespace.md => 13599-cnl-cjs-namespace.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-cnl-cjs-namespace.md => 13599-cnl-cjs-namespace.md} (100%) diff --git a/changelog.d/0-cnl-cjs-namespace.md b/changelog.d/13599-cnl-cjs-namespace.md similarity index 100% rename from changelog.d/0-cnl-cjs-namespace.md rename to changelog.d/13599-cnl-cjs-namespace.md From a587c7faa27b449372d841a6533e1268ee0b5e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 00:08:40 +0000 Subject: [PATCH 036/144] wpfacts: code that runs with this in a constructor can read the keys not yet assigned A method call on this, a super method or accessor, and a read of a key that is not own yet (an accessor on the prototype chain may run) all run code with this. The construction phase took them for plain reads, so a key assigned after them was claimed set before anyone could observe the instance: a declared field lost its initial undefined from its kind (`this.p === undefined` in such a method folded to false). They now close the phase. --- crates/perry-wpfacts/src/ctor_phase.rs | 42 +++++++-- crates/perry-wpfacts/src/tests.rs | 1 + .../perry-wpfacts/src/tests/ctor_observed.rs | 87 +++++++++++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 crates/perry-wpfacts/src/tests/ctor_observed.rs diff --git a/crates/perry-wpfacts/src/ctor_phase.rs b/crates/perry-wpfacts/src/ctor_phase.rs index d1bc40c15a7..5e479745fc6 100644 --- a/crates/perry-wpfacts/src/ctor_phase.rs +++ b/crates/perry-wpfacts/src/ctor_phase.rs @@ -9,7 +9,10 @@ //! //! A leak is any use of `this` other than as the receiver of a named //! property read/write in this body: passing it, storing it, calling a -//! method on it, capturing it in a closure. Conservative by construction: +//! method on it (the callee receives it), capturing it in a closure, a +//! `super` method call or accessor, and a read of a key not yet assigned +//! that is no method of the class (it consults the prototype chain, where +//! an accessor can run with `this`). Conservative by construction: //! everything unrecognised closes the phase. use perry_hir::walker::walk_expr_children; @@ -37,6 +40,8 @@ struct St { struct W { ph: CtorPhase, + /// The class's own plain methods: reading one runs no code. + methods: BTreeSet, late: BTreeSet, read_before: BTreeSet, exits: Vec>, @@ -51,29 +56,32 @@ pub fn analyze(c: &Class) -> CtorPhase { (&f.init, named) }) .collect(); + let methods: BTreeSet = c.methods.iter().map(|m| m.name.clone()).collect(); match &c.constructor { - Some(ctor) => analyze_body(&fields, &ctor.params, &ctor.body), - None => analyze_body(&fields, &[], &[]), + Some(ctor) => analyze_body(&fields, methods, &ctor.params, &ctor.body), + None => analyze_body(&fields, methods, &[], &[]), } } /// The construction phase of a constructor-function body (`new f()`). pub fn analyze_fn(f: &perry_hir::Function) -> CtorPhase { - analyze_body(&[], &f.params, &f.body) + analyze_body(&[], BTreeSet::new(), &f.params, &f.body) } /// Same for a closure body used as a constructor. pub fn analyze_closure(params: &[perry_hir::Param], body: &[Stmt]) -> CtorPhase { - analyze_body(&[], params, body) + analyze_body(&[], BTreeSet::new(), params, body) } fn analyze_body( fields: &[(&Option, Option<&str>)], + methods: BTreeSet, params: &[perry_hir::Param], body: &[Stmt], ) -> CtorPhase { let mut w = W { ph: CtorPhase::default(), + methods, late: BTreeSet::new(), read_before: BTreeSet::new(), exits: Vec::new(), @@ -269,11 +277,32 @@ impl W { fn expr(&mut self, st: &mut St, e: &Expr) { match e { Expr::This => self.leak(st), + // `this.m(...)`: the callee runs with `this` as its receiver. + Expr::Call { callee, .. } | Expr::CallSpread { callee, .. } + if matches!(callee.as_ref(), Expr::PropertyGet { object, .. } + if matches!(object.as_ref(), Expr::This)) => + { + self.leak(st); + walk_expr_children(e, &mut |c| self.expr(st, c)); + } + // A `super` method or accessor runs with `this`. + Expr::SuperMethodCall { .. } + | Expr::SuperMethodCallSpread { .. } + | Expr::SuperPropertyGet { .. } + | Expr::SuperPropertySet { .. } => { + self.leak(st); + walk_expr_children(e, &mut |c| self.expr(st, c)); + } Expr::PropertyGet { object, property, .. } if matches!(object.as_ref(), Expr::This) => { if !st.assigned.contains(property) { self.read_before.insert(property.clone()); + // Not own yet: the read walks the prototype chain, where + // an accessor runs with `this`. + if !self.methods.contains(property) { + self.leak(st); + } } } Expr::PropertySet { @@ -305,6 +334,9 @@ impl W { } if matches!(object.as_ref(), Expr::This) => { if !st.assigned.contains(property) { self.read_before.insert(property.clone()); + if !self.methods.contains(property) { + self.leak(st); + } } self.store(st, e, property); } diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index af883e64cb1..add4ed92857 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -52,6 +52,7 @@ mod summary; mod super_heritage; mod unit_alias; mod unit_cjs; +mod ctor_observed; mod ctor_return; mod unit_ctor; mod unit_effects; diff --git a/crates/perry-wpfacts/src/tests/ctor_observed.rs b/crates/perry-wpfacts/src/tests/ctor_observed.rs new file mode 100644 index 00000000000..6bfd833a326 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/ctor_observed.rs @@ -0,0 +1,87 @@ +//! A key the constructor assigns after code that runs with `this` (a method +//! call on `this`, a `super` method or accessor, a read of a key that is not +//! own yet) can be observed unset: its kind holds `undefined`. Per unit +//! agrees with the whole program. + +use super::*; + +fn units(src: &str) -> ProgramFacts { + let module = perry_parser::parse_typescript(src, "t.ts").expect("parses"); + let hir = perry_hir::lower_module(&module, "t", "t.ts").expect("lowers"); + crate::compute_units(&[("t.ts".to_string(), &hir)], &FactsConfig::default()) +} + +/// In both modes: class `A`'s field `p` holds `undefined` exactly when the +/// constructor can be observed before it assigns `p`. +fn check(src: &str, observed: bool) { + for pf in [facts(src), units(src)] { + let p = field(tmpl(&pf, "A"), "p"); + let undef = p.kind.prims & crate::model::prim::UNDEF != 0; + assert_eq!(undef, observed, "{}", pf.dump_text()); + } +} + +#[test] +fn assigned_before_any_observation_is_definite() { + check( + r#" + class P { v = 5; } + class A { p: P; s: string; constructor() { this.p = new P(); this.s = this.peek(); } + peek(): string { return typeof this.p; } } + console.log(new A().s); + "#, + false, + ); +} + +#[test] +fn a_method_call_on_this_observes_the_unassigned_key() { + check( + r#" + class P { v = 5; } + class A { p: P; s: string; constructor(e: boolean) { this.s = e ? this.peek() : "-"; this.p = new P(); } + peek(): string { return typeof this.p; } } + console.log(new A(true).s); + "#, + true, + ); +} + +#[test] +fn a_getter_read_on_this_observes_the_unassigned_key() { + check( + r#" + class P { v = 5; } + class A { p: P; s: string; constructor() { this.s = this.peek; this.p = new P(); } + get peek(): string { return typeof this.p; } } + console.log(new A().s); + "#, + true, + ); +} + +#[test] +fn a_super_method_call_observes_the_unassigned_key() { + check( + r#" + class P { v = 5; } + class B { peek(): string { return typeof (this as any).p; } } + class A extends B { p: P; s: string; constructor() { super(); this.s = super.peek(); this.p = new P(); } } + console.log(new A().s); + "#, + true, + ); +} + +#[test] +fn an_undeclared_key_assigned_after_a_method_call_holds_undefined() { + check( + r#" + class P { v = 5; } + class A { constructor(e) { this.s = e ? this.peek() : "-"; this.p = new P(); } + peek() { return typeof this.p; } } + console.log(new A(true).s); + "#, + true, + ); +} From 833ad2ba186e2548c4672da4efb815333b60468e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 00:16:56 +0000 Subject: [PATCH 037/144] test: a method called from the constructor sees the unassigned key; changelog --- .../0-ctor-this-call-reads-unset-key.md | 1 + .../tests/ctor_this_call_reads_unset_key.rs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 changelog.d/0-ctor-this-call-reads-unset-key.md create mode 100644 crates/perry/tests/ctor_this_call_reads_unset_key.rs diff --git a/changelog.d/0-ctor-this-call-reads-unset-key.md b/changelog.d/0-ctor-this-call-reads-unset-key.md new file mode 100644 index 00000000000..e86b3b43157 --- /dev/null +++ b/changelog.d/0-ctor-this-call-reads-unset-key.md @@ -0,0 +1 @@ +A method call on `this` inside a constructor, a `super` method or accessor, and a read of a key that is not own yet (an accessor on the prototype chain may run) all run code that holds `this` before the constructor has assigned every key. Facts treated them as plain reads, so a key assigned after them was claimed set before anyone could observe the instance, and a declared field lost its initial `undefined`. `this.p === undefined` inside such a method then folded to false. They now close the constructor phase. diff --git a/crates/perry/tests/ctor_this_call_reads_unset_key.rs b/crates/perry/tests/ctor_this_call_reads_unset_key.rs new file mode 100644 index 00000000000..5e003d172cc --- /dev/null +++ b/crates/perry/tests/ctor_this_call_reads_unset_key.rs @@ -0,0 +1,50 @@ +//! A method called on `this` from a constructor can read a key the +//! constructor assigns later: it reads `undefined`. Facts must not claim the +//! key is already set (`this.p === undefined` folded to false). + +use std::path::PathBuf; +use std::process::Command; + +fn clean(cmd: &mut Command) -> &mut Command { + for (key, _) in std::env::vars() { + if key.starts_with("PERRY_") || key == "NODE_OPTIONS" { + cmd.env_remove(key); + } + } + cmd +} + +fn run(whole: bool) -> String { + let dir = tempfile::tempdir().unwrap(); + let src = dir.path().join("main.js"); + std::fs::write( + &src, + r#"class P { constructor() { this.v = 5; } } +class A { + constructor(early) { this.s = early ? this.peek() : "-"; this.p = new P(); } + peek() { return "t=" + typeof this.p + " " + (this.p === undefined); } +} +console.log(new A(false).s, new A(true).s); +"#, + ) + .unwrap(); + let exe = dir.path().join("main"); + let mut cmd = Command::new(PathBuf::from(env!("CARGO_BIN_EXE_perry"))); + clean(&mut cmd).current_dir(dir.path()).env("PERRY_NO_CACHE", "1"); + if whole { + cmd.env("PERRY_FACTS_UNITS", "0"); + } + let out = cmd.arg("compile").arg(&src).arg("-o").arg(&exe).output().expect("compile"); + assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + let mut run = Command::new(&exe); + let ran = clean(&mut run).output().expect("run"); + assert!(ran.status.success(), "run failed: {:?}", ran.status); + String::from_utf8_lossy(&ran.stdout).trim().to_string() +} + +#[test] +fn a_method_called_from_the_constructor_sees_the_unassigned_key() { + // node: `- t=undefined true` + assert_eq!(run(false), "- t=undefined true", "per unit"); + assert_eq!(run(true), "- t=undefined true", "whole program"); +} From a55a76a8026becb24d45c0a223e2aeb7e448f1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 01:12:37 +0000 Subject: [PATCH 038/144] wpfacts, codegen: a key the constructor stores is present only once stored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A key the constructor stores (`ClassFieldOrigin::ConstructorStore`) holds the hole until the store (object model §3.2: presence is the slot word), so code that runs with `this` before it (a method call on `this`) reads it absent, through the prototype chain. Facts took every such key for present from birth, and an elided read loaded the hole as a value: `this.p === undefined` was false, `this.p ?? d` gave the hole, `String(this.p)` "NaN", `this.f()` threw although the prototype had `f`. Facts: such a key is definite only when the construction phase stores it before anyone can observe the instance; otherwise it is reserved (known slot, unknown presence: `FieldFacts::reserved`, carried in the class contract), and the class's layout is not fixed. Codegen: a read of a reserved key is the load plus the hole compare, whose miss is the complete generic read; stores take the checked route; number facts and proven field calls do not use it; the dispatch tower keeps its keys recheck before a `$pshape` clone. --- .../0-ctor-this-call-reads-unset-key.md | 2 +- .../src/codegen/program_facts_view.rs | 5 + crates/perry-codegen/src/expr/facts_number.rs | 2 +- .../perry-codegen/src/expr/wpfacts_access.rs | 30 +++- .../numeric/number_result_tests.rs | 1 + crates/perry-wpfacts/src/builtins.rs | 1 + .../src/builtins/native_protocol.rs | 1 + crates/perry-wpfacts/src/derive.rs | 35 +++- crates/perry-wpfacts/src/gen.rs | 3 + crates/perry-wpfacts/src/gen/class_values.rs | 3 + crates/perry-wpfacts/src/gen/walk.rs | 18 +- crates/perry-wpfacts/src/gen_expr.rs | 1 + crates/perry-wpfacts/src/gen_rest.rs | 2 +- crates/perry-wpfacts/src/lib.rs | 1 + crates/perry-wpfacts/src/lib/contracts.rs | 5 + crates/perry-wpfacts/src/model.rs | 6 + crates/perry-wpfacts/src/model/operations.rs | 2 + .../src/solve/call_and_construct.rs | 2 + crates/perry-wpfacts/src/solve/store.rs | 7 +- .../perry-wpfacts/src/tests/ctor_observed.rs | 45 ++++- crates/perry-wpfacts/src/tests/unit_alias.rs | 1 + crates/perry-wpfacts/src/units.rs | 13 ++ .../src/commands/compile/program_facts.rs | 3 +- .../tests/ctor_this_call_reads_unset_key.rs | 168 +++++++++++++++--- 24 files changed, 313 insertions(+), 44 deletions(-) diff --git a/changelog.d/0-ctor-this-call-reads-unset-key.md b/changelog.d/0-ctor-this-call-reads-unset-key.md index e86b3b43157..7669375b08b 100644 --- a/changelog.d/0-ctor-this-call-reads-unset-key.md +++ b/changelog.d/0-ctor-this-call-reads-unset-key.md @@ -1 +1 @@ -A method call on `this` inside a constructor, a `super` method or accessor, and a read of a key that is not own yet (an accessor on the prototype chain may run) all run code that holds `this` before the constructor has assigned every key. Facts treated them as plain reads, so a key assigned after them was claimed set before anyone could observe the instance, and a declared field lost its initial `undefined`. `this.p === undefined` inside such a method then folded to false. They now close the constructor phase. +A method call on `this` inside a constructor, a `super` method or accessor, and a read of a key that is not own yet (an accessor on the prototype chain may run) all run code that holds `this` before the constructor has assigned every key. Facts treated them as plain reads, so a key assigned after them was claimed set before anyone could observe the instance, and a declared field lost its initial `undefined`. They now close the constructor phase. A key the constructor stores holds the hole until the store, and facts no longer call it present from birth: such a key is now reserved (its slot is known, its presence is not). An elided read of it pairs the load with the hole compare and an absent key reads through the prototype chain, stores take the checked route, and the class's layout is not fixed, so `$pshape` clones keep their keys recheck. Before, `this.p === undefined`, `this.p ?? d`, `String(this.p)`, `this.p + 1` and `this.f()` in such a method read the hole as a value and disagreed with node. diff --git a/crates/perry-codegen/src/codegen/program_facts_view.rs b/crates/perry-codegen/src/codegen/program_facts_view.rs index cb64715b09b..ae6c1cec163 100644 --- a/crates/perry-codegen/src/codegen/program_facts_view.rs +++ b/crates/perry-codegen/src/codegen/program_facts_view.rs @@ -61,6 +61,11 @@ pub struct FactsField { /// known, presence is not. A read pairs the load with the hole compare; /// stores stay on the checked route (an add orders the key). pub declared: bool, + /// A constructor-store reservation code can observe before the + /// constructor stores it: the slot is the class's own (codegen's layout), + /// presence is not. A read pairs the load with the hole compare; stores + /// stay on the checked route (filling the hole adds the key). + pub reserved: bool, /// The field holds only these bodies of this module (FVA): a call /// through it is a call of them. Empty when it may hold anything else. pub fns: Vec, diff --git a/crates/perry-codegen/src/expr/facts_number.rs b/crates/perry-codegen/src/expr/facts_number.rs index 4f3a33130e4..552169bee72 100644 --- a/crates/perry-codegen/src/expr/facts_number.rs +++ b/crates/perry-codegen/src/expr/facts_number.rs @@ -282,7 +282,7 @@ fn this_kinds(ctx: &FnCtx<'_>, cids: &[u32], key: &str) -> Option { for &cid in cids { let f = ctx.wpfacts.field(cid, key)?; crate::facts_consumed::used(); - if f.declared { + if f.declared || f.reserved { return None; } mask |= f.kind_mask; diff --git a/crates/perry-codegen/src/expr/wpfacts_access.rs b/crates/perry-codegen/src/expr/wpfacts_access.rs index 53b8c08ccd4..eeb7a58ea34 100644 --- a/crates/perry-codegen/src/expr/wpfacts_access.rs +++ b/crates/perry-codegen/src/expr/wpfacts_access.rs @@ -97,9 +97,9 @@ pub(crate) struct FieldProof { /// Interpreted code can run: the site tests /// `PERRY_INTERP_PROTO_WRITTEN` (decision 150). pub interp_guard: bool, - /// The key is declared in some receiver class: present only once stored, - /// so the load is followed by the hole compare, whose miss is the site's - /// complete generic operation. + /// The key is declared or reserved in some receiver class: present only + /// once stored, so the load is followed by the hole compare, whose miss + /// is the site's complete generic operation. pub maybe_absent: bool, } @@ -404,6 +404,30 @@ pub(crate) fn prove( ); return None; } + if f.reserved { + // The slot holds the hole until the constructor stores the key + // (object model §3.2: presence is the slot word). A read pairs + // the load with the hole compare; a store fills the hole, which + // adds the key, on the checked route. A raw-f64 lane has no + // hole word. + if access != Access::Get + || super::class_field_inline_guard::class_birth_slot_is_f64(ctx, &name, idx) + { + crate::fallback_diag::refuse( + "reserved key: stores add it on the checked route; reads need a boxed slot", + ); + return None; + } + maybe_absent = true; + slot.get_or_insert(idx); + slots.push(idx); + kind_mask |= f.kind_mask; + ta_stable = false; + ta_kind = Some(None); + all_f64 = false; + names.push(name); + continue; + } if access != Access::Get { let keys_global = ctx.class_keys_globals.get(&name)?; if crate::codegen::slot_may_be_constfn(keys_global, idx) { diff --git a/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs b/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs index 80244539fc2..49c17db15f5 100644 --- a/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs +++ b/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs @@ -37,6 +37,7 @@ fn body(with_facts: bool, checked: bool, interp: bool, kind: u16) -> String { ta_stable: false, ta_kind: None, declared: false, + reserved: false, fns: Vec::new(), }], }, diff --git a/crates/perry-wpfacts/src/builtins.rs b/crates/perry-wpfacts/src/builtins.rs index 503a523ae61..d718ca3772e 100644 --- a/crates/perry-wpfacts/src/builtins.rs +++ b/crates/perry-wpfacts/src/builtins.rs @@ -194,6 +194,7 @@ fn site_tmpl(m: &mut Model, call: CallId, what: &'static str) -> Tok { birth_keys: vec![], definite: vec![], declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/builtins/native_protocol.rs b/crates/perry-wpfacts/src/builtins/native_protocol.rs index a252b0e4064..65898806483 100644 --- a/crates/perry-wpfacts/src/builtins/native_protocol.rs +++ b/crates/perry-wpfacts/src/builtins/native_protocol.rs @@ -315,6 +315,7 @@ pub(super) fn anon_tmpl(m: &mut Model, what: &'static str) -> Tok { birth_keys: vec![], definite: vec![], declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/derive.rs b/crates/perry-wpfacts/src/derive.rs index 633a5352679..4d77a8fac35 100644 --- a/crates/perry-wpfacts/src/derive.rs +++ b/crates/perry-wpfacts/src/derive.rs @@ -78,6 +78,11 @@ pub struct FieldFacts { /// A declared key (`Template::declared`): `slot` is known, presence is /// not, so a read is the load plus the hole compare. pub declared: bool, + /// A constructor-store reservation that is not `definite` + /// (`Template::reserved`): `slot` is known, presence is not (the slot + /// holds the hole until the constructor stores the key), so a read is + /// the load plus the hole compare, and a store adds the key. + pub reserved: bool, pub data_field: bool, pub readonly: bool, pub writers_ctor: usize, @@ -204,6 +209,11 @@ impl FieldFacts { } self.definite &= o.definite; self.declared &= o.declared; + // A known slot whose presence one of them does not know. + self.reserved = (self.definite || self.reserved) + && (o.definite || o.reserved) + && !(self.definite && o.definite); + self.definite &= o.definite; self.data_field &= o.data_field; self.readonly &= o.readonly; self.writers_ctor = self.writers_ctor.max(o.writers_ctor); @@ -244,13 +254,17 @@ impl TemplateFacts { for f in self.fields.iter_mut() { match o.fields.iter().find(|g| g.key == f.key) { Some(g) => f.join(g), - None => f.definite = false, + None => { + f.definite = false; + f.reserved = false; + } } } for g in &o.fields { if !self.fields.iter().any(|f| f.key == g.key) { let mut g = g.clone(); g.definite = false; + g.reserved = false; self.fields.push(g); } } @@ -492,11 +506,19 @@ fn template_facts( let is_accessor = accessor_keys.contains(&a); let definite = birth && tpl.definite.contains(&a) && !is_accessor; let declared = birth && tpl.declared.contains(&a) && !is_accessor; - if birth && !definite && !declared { + let reserved = birth && tpl.reserved.contains(&a) && !definite && !is_accessor; + if birth && !definite && !declared && !reserved { slot_ok = false; fixed_layout = false; } - let slot = (birth && (definite || declared) && slot_ok).then_some(i as u32); + // A reserved key keeps its slot, but an instance can be observed + // while the slot still holds the hole: its presence is not the + // complete birth layout's, so code compiled for that layout (the + // `$pshape` clones) keeps its keys recheck. + if reserved { + fixed_layout = false; + } + let slot = (birth && (definite || declared || reserved) && slot_ok).then_some(i as u32); let w = m.writers.get(&(tm, a)).cloned().unwrap_or_default(); let any = m.any_writers.get(&tm).cloned().unwrap_or_default(); let readonly = escape.is_none() @@ -527,6 +549,7 @@ fn template_facts( slot, definite, declared, + reserved, data_field: !is_accessor, readonly, writers_ctor: w.ctor.len(), @@ -1031,7 +1054,11 @@ fn judge( continue; } match tf.fields.iter().find(|f| f.key == *key) { - Some(f) if (f.definite || f.declared) && f.data_field && f.slot.is_some() => { + Some(f) + if (f.definite || f.declared || f.reserved) + && f.data_field + && f.slot.is_some() => + { slots.push(f.slot) } Some(f) if !f.data_field => { diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index 5d0d4942854..5aa1575f6bb 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -1122,6 +1122,7 @@ impl<'a> Gen<'a> { birth_keys: Vec::new(), definite: Vec::new(), declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: c.native_extends.clone(), @@ -1587,6 +1588,7 @@ impl<'a> Gen<'a> { birth_keys: vec![], definite: vec![], declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, @@ -1623,6 +1625,7 @@ impl<'a> Gen<'a> { birth_keys: vec![], definite: vec![], declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/gen/class_values.rs b/crates/perry-wpfacts/src/gen/class_values.rs index 39c0a75ddb8..6db297c1311 100644 --- a/crates/perry-wpfacts/src/gen/class_values.rs +++ b/crates/perry-wpfacts/src/gen/class_values.rs @@ -493,6 +493,7 @@ impl<'a> Gen<'a> { parent_links: Vec::new(), null_proto: false, declared: vec![], + reserved: Vec::new(), }); let t = self.m.tok(TokKind::Obj(tm, NO_CTX)); self.stand_in_arrs.insert(display.to_string(), t); @@ -754,6 +755,7 @@ impl<'a> Gen<'a> { } let birth_keys = c.birth_keys.iter().map(|k| self.m.atom(k)).collect(); let definite = c.definite.iter().map(|k| self.m.atom(k)).collect(); + let reserved = c.reserved.iter().map(|k| self.m.atom(k)).collect(); self.m.tmpls.push(Template { // DECISIONS 210: a constructor function's instances keep their // kind: no class id guards them, and their chain is the @@ -771,6 +773,7 @@ impl<'a> Gen<'a> { birth_keys, definite, declared: Vec::new(), + reserved, parent: None, ctor: Some(ctor), native_parent: c.native_parent.clone(), diff --git a/crates/perry-wpfacts/src/gen/walk.rs b/crates/perry-wpfacts/src/gen/walk.rs index dd9eb499255..6eb82206513 100644 --- a/crates/perry-wpfacts/src/gen/walk.rs +++ b/crates/perry-wpfacts/src/gen/walk.rs @@ -87,6 +87,7 @@ impl<'a> Gen<'a> { } let mut keys = Vec::new(); let mut definite = Vec::new(); + let mut reserved = Vec::new(); if let Some(p) = self.m.tmpls[tm as usize].parent { if let TmplKind::Class { module: pm, .. } = self.m.tmpls[p as usize].kind { let pmod = self.modules[pm as usize].1; @@ -94,6 +95,7 @@ impl<'a> Gen<'a> { } keys = self.m.tmpls[p as usize].birth_keys.clone(); definite = self.m.tmpls[p as usize].definite.clone(); + reserved = self.m.tmpls[p as usize].reserved.clone(); } if let Some((_, np)) = self.m.tmpls[tm as usize].native_parent.clone() { // Keys the native parent constructor defines (summary, E21). @@ -115,10 +117,14 @@ impl<'a> Gen<'a> { } let a = self.m.atom(&f.name); push_unique(&mut keys, a); - if f.origin == perry_hir::ClassFieldOrigin::Declared { - push_unique(&mut declared, a); - } else { - push_unique(&mut definite, a); + match f.origin { + perry_hir::ClassFieldOrigin::Declared => push_unique(&mut declared, a), + // A reservation holds the hole until the constructor stores + // it: present from birth on only when the construction phase + // stores it before anyone can observe the instance + // (`CtorPhase::definite`, below). + perry_hir::ClassFieldOrigin::ConstructorStore => push_unique(&mut reserved, a), + perry_hir::ClassFieldOrigin::Definition => push_unique(&mut definite, a), } } self.m.tmpls[tm as usize].declared = declared; @@ -139,14 +145,17 @@ impl<'a> Gen<'a> { // keys are the prefix (`settle_parent_layouts`). if self.parent_pending(tm) { definite.clear(); + reserved.clear(); } else if self.value_parent_on_chain(tm) && self.parent_chain_leaks(tm) { let n = self.m.tmpls[tm as usize] .parent .map_or(0, |p| self.m.tmpls[p as usize].definite.len()); definite.truncate(n); } + reserved.retain(|a| !definite.contains(a)); self.m.tmpls[tm as usize].birth_keys = keys; self.m.tmpls[tm as usize].definite = definite; + self.m.tmpls[tm as usize].reserved = reserved; } /// Some class on `tm`'s chain has a parent value not settled to one @@ -246,6 +255,7 @@ impl<'a> Gen<'a> { for &(tm, _) in &redo { self.m.tmpls[tm as usize].birth_keys.clear(); self.m.tmpls[tm as usize].definite.clear(); + self.m.tmpls[tm as usize].reserved.clear(); } for (tm, mi) in redo { self.compute_birth_keys(tm, modules[mi].1); diff --git a/crates/perry-wpfacts/src/gen_expr.rs b/crates/perry-wpfacts/src/gen_expr.rs index e8e3e0b4e1a..f79dc52419c 100644 --- a/crates/perry-wpfacts/src/gen_expr.rs +++ b/crates/perry-wpfacts/src/gen_expr.rs @@ -323,6 +323,7 @@ impl<'a> Gen<'a> { birth_keys: atoms.clone(), definite: atoms, declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/gen_rest.rs b/crates/perry-wpfacts/src/gen_rest.rs index cb9c7fd517c..8156229e7b6 100644 --- a/crates/perry-wpfacts/src/gen_rest.rs +++ b/crates/perry-wpfacts/src/gen_rest.rs @@ -67,7 +67,6 @@ impl<'a> Gen<'a> { Some((class_name, args, arg("get"), arg("set"))) } - /// The class whose method home `home` reads, when it is that home. fn class_home_of(&self, fx: &Fx, home: &Expr) -> Option { match home { @@ -755,6 +754,7 @@ impl<'a> Gen<'a> { birth_keys: vec![], definite: vec![], declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/lib.rs b/crates/perry-wpfacts/src/lib.rs index 440d702edbc..f2bfce191c6 100644 --- a/crates/perry-wpfacts/src/lib.rs +++ b/crates/perry-wpfacts/src/lib.rs @@ -897,6 +897,7 @@ fn compute_act<'a>( ctor_fn, birth_keys: atoms_of(&g, &g.m.tmpls[tm as usize].birth_keys), definite: atoms_of(&g, &g.m.tmpls[tm as usize].definite), + reserved: atoms_of(&g, &g.m.tmpls[tm as usize].reserved), ctor_leaks: g.chain_leaks(tm), ctor_returns: g.chain_returns(tm), binding_prim: match e { diff --git a/crates/perry-wpfacts/src/lib/contracts.rs b/crates/perry-wpfacts/src/lib/contracts.rs index dffe25d5422..051538d1556 100644 --- a/crates/perry-wpfacts/src/lib/contracts.rs +++ b/crates/perry-wpfacts/src/lib/contracts.rs @@ -261,6 +261,7 @@ pub(super) fn nested_layouts( // (DECISIONS 101). birth_keys: atoms_of(g, &g.m.tmpls[itm as usize].birth_keys), definite: atoms_of(g, &g.m.tmpls[itm as usize].definite), + reserved: atoms_of(g, &g.m.tmpls[itm as usize].reserved), ctor_leaks: true, ctor_returns: g.chain_returns(itm), binding_prim: 0, @@ -1087,6 +1088,7 @@ pub(super) fn describe_nodes( // (DECISIONS 101). birth_keys: atoms_of(g, &g.m.tmpls[tm as usize].birth_keys), definite: atoms_of(g, &g.m.tmpls[tm as usize].definite), + reserved: atoms_of(g, &g.m.tmpls[tm as usize].reserved), ctor_leaks: true, ctor_returns: g.chain_returns(tm), binding_prim: 0, @@ -1433,6 +1435,7 @@ pub(super) fn describe_toks( // (DECISIONS 101). birth_keys: atoms_of(g, &g.m.tmpls[tm as usize].birth_keys), definite: atoms_of(g, &g.m.tmpls[tm as usize].definite), + reserved: atoms_of(g, &g.m.tmpls[tm as usize].reserved), ctor_leaks: true, ctor_returns: g.chain_returns(tm), binding_prim: 0, @@ -1620,6 +1623,7 @@ pub(super) fn fn_inst_contract( ctor_fn: Some(fn_contract_of(g, templates, f, layouts_out, open_tm)), birth_keys: atoms_of(g, &tpl.birth_keys), definite: atoms_of(g, &tpl.definite), + reserved: atoms_of(g, &tpl.reserved), ctor_leaks: true, ctor_returns: m.ctor_returns_object(f), binding_prim: 0, @@ -1887,6 +1891,7 @@ pub(super) fn arg_contract_of( // (DECISIONS 101). birth_keys: atoms_of(g, &g.m.tmpls[tm as usize].birth_keys), definite: atoms_of(g, &g.m.tmpls[tm as usize].definite), + reserved: atoms_of(g, &g.m.tmpls[tm as usize].reserved), ctor_leaks: true, ctor_returns: g.chain_returns(tm), binding_prim: 0, diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index d0d086f9fc5..5d4790ab8e6 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -285,6 +285,12 @@ pub struct Template { /// slots, absent until stored (each read pairs its load with the hole /// compare). pub declared: Vec, + /// Birth keys reserved for a constructor store + /// (`ClassFieldOrigin::ConstructorStore`) that are not `definite`: code + /// can observe the instance before the constructor stores them (a method + /// call on `this`, ...). Known slots, holding the hole (absent) until + /// stored; each read pairs its load with the hole compare. + pub reserved: Vec, /// Parent template (class `extends`), when statically known. pub parent: Option, /// The constructor function of a class template (None: implicit ctor). diff --git a/crates/perry-wpfacts/src/model/operations.rs b/crates/perry-wpfacts/src/model/operations.rs index 4d8c4168fbd..c342d9f8eab 100644 --- a/crates/perry-wpfacts/src/model/operations.rs +++ b/crates/perry-wpfacts/src/model/operations.rs @@ -1410,6 +1410,7 @@ impl Model { birth_keys: Vec::new(), definite: Vec::new(), declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, @@ -1445,6 +1446,7 @@ impl Model { birth_keys: Vec::new(), definite: Vec::new(), declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index 86a75a9feb9..824a6e900e8 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -696,6 +696,7 @@ impl Model { birth_keys, definite, declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: Some(f), native_parent: None, @@ -1230,6 +1231,7 @@ impl Model { birth_keys: Vec::new(), definite: Vec::new(), declared: Vec::new(), + reserved: Vec::new(), parent: None, ctor: None, native_parent: None, diff --git a/crates/perry-wpfacts/src/solve/store.rs b/crates/perry-wpfacts/src/solve/store.rs index cd2ee18240a..229a38f8f98 100644 --- a/crates/perry-wpfacts/src/solve/store.rs +++ b/crates/perry-wpfacts/src/solve/store.rs @@ -553,10 +553,9 @@ impl Model { k != "__proto__" && !facts.fields.iter().any(|f| !f.data_field && &f.key == k) } - "class" => facts - .fields - .iter() - .any(|f| f.data_field && &f.key == k && (f.definite || f.declared)), + "class" => facts.fields.iter().any(|f| { + f.data_field && &f.key == k && (f.definite || f.declared || f.reserved) + }), _ => false, } } diff --git a/crates/perry-wpfacts/src/tests/ctor_observed.rs b/crates/perry-wpfacts/src/tests/ctor_observed.rs index 6bfd833a326..532a7a6436e 100644 --- a/crates/perry-wpfacts/src/tests/ctor_observed.rs +++ b/crates/perry-wpfacts/src/tests/ctor_observed.rs @@ -1,7 +1,8 @@ //! A key the constructor assigns after code that runs with `this` (a method //! call on `this`, a `super` method or accessor, a read of a key that is not -//! own yet) can be observed unset: its kind holds `undefined`. Per unit -//! agrees with the whole program. +//! own yet) can be observed unset: its kind holds `undefined`, and a key +//! the constructor stores is reserved (known slot, presence not known). +//! Per unit agrees with the whole program. use super::*; @@ -85,3 +86,43 @@ fn an_undeclared_key_assigned_after_a_method_call_holds_undefined() { true, ); } + +/// A key the constructor stores (a reservation, `ClassFieldOrigin:: +/// ConstructorStore`) keeps its slot either way; it is present from birth +/// on only when no code can observe the instance before the store. Else it +/// is reserved: known slot, unknown presence (the slot holds the hole), and +/// the layout is not fixed (an observer may see the hole). +fn check_presence(src: &str, observed: bool) { + for pf in [facts(src), units(src)] { + let t = tmpl(&pf, "A"); + let p = field(t, "p"); + assert_eq!(p.slot, Some(1), "{}", pf.dump_text()); + assert_eq!(p.definite, !observed, "{}", pf.dump_text()); + assert_eq!(p.reserved, observed, "{}", pf.dump_text()); + assert_eq!(t.fixed_layout, !observed, "{}", pf.dump_text()); + } +} + +#[test] +fn a_constructor_store_after_a_method_call_is_reserved() { + check_presence( + r#" + class A { constructor(e) { this.s = e ? this.peek() : "-"; this.p = 1; } + peek() { return typeof this.p; } } + console.log(new A(true).s); + "#, + true, + ); +} + +#[test] +fn a_constructor_store_before_any_observation_is_definite() { + check_presence( + r#" + class A { constructor(e) { this.s = "-"; this.p = 1; } + peek() { return typeof this.p; } } + console.log(new A(true).peek()); + "#, + false, + ); +} diff --git a/crates/perry-wpfacts/src/tests/unit_alias.rs b/crates/perry-wpfacts/src/tests/unit_alias.rs index 82e476d3f8c..8ba33157e31 100644 --- a/crates/perry-wpfacts/src/tests/unit_alias.rs +++ b/crates/perry-wpfacts/src/tests/unit_alias.rs @@ -657,6 +657,7 @@ fn a_layout_met_again_in_its_own_description_is_named_once() { ctor_fn: None, birth_keys: Vec::new(), definite: Vec::new(), + reserved: Vec::new(), ctor_leaks: true, ctor_returns: false, binding_prim: 0, diff --git a/crates/perry-wpfacts/src/units.rs b/crates/perry-wpfacts/src/units.rs index f1af775b896..d06d04d301b 100644 --- a/crates/perry-wpfacts/src/units.rs +++ b/crates/perry-wpfacts/src/units.rs @@ -85,6 +85,9 @@ pub(crate) struct ClassContract { /// the same unit does. pub birth_keys: Vec, pub definite: Vec, + /// The birth keys reserved for a constructor store that are not + /// definite (`Template::reserved`): known slots, present once stored. + pub reserved: Vec, pub ctor_leaks: bool, /// Its constructor, or one up its chain, may return an object other than /// `this` (`return expr`): a reader's `new` then yields that value, not @@ -813,7 +816,17 @@ impl ClassContract { // The layout prefix a subclass in another unit takes: the common // birth keys, definite in both; a leak in either leaks. common_prefix(&mut self.birth_keys, &o.birth_keys); + // A known slot whose presence one of them does not know. + let known = |c: &Self, k: &String| c.definite.contains(k) || c.reserved.contains(k); + let reserved: Vec = self + .definite + .iter() + .chain(self.reserved.iter()) + .filter(|k| known(o, k) && !(self.definite.contains(k) && o.definite.contains(k))) + .cloned() + .collect(); self.definite.retain(|k| o.definite.contains(k)); + self.reserved = reserved; self.ctor_leaks |= o.ctor_leaks; self.ctor_returns |= o.ctor_returns; self.binding_prim |= o.binding_prim; diff --git a/crates/perry/src/commands/compile/program_facts.rs b/crates/perry/src/commands/compile/program_facts.rs index 8cf34019edb..43f575c9307 100644 --- a/crates/perry/src/commands/compile/program_facts.rs +++ b/crates/perry/src/commands/compile/program_facts.rs @@ -435,7 +435,7 @@ fn elision_for(pf: &perry_wpfacts::ProgramFacts, path: &str) -> perry_codegen::E let fields = t .fields .iter() - .filter(|f| (f.definite || f.declared) && f.data_field) + .filter(|f| (f.definite || f.declared || f.reserved) && f.data_field) .filter_map(|f| { Some(FactsField { fns: bodies(&f.fns), @@ -446,6 +446,7 @@ fn elision_for(pf: &perry_wpfacts::ProgramFacts, path: &str) -> perry_codegen::E ta_stable: f.ta_stable, ta_kind: f.ta_kind, declared: f.declared, + reserved: f.reserved, }) }) .collect(); diff --git a/crates/perry/tests/ctor_this_call_reads_unset_key.rs b/crates/perry/tests/ctor_this_call_reads_unset_key.rs index 5e003d172cc..1f000f125a7 100644 --- a/crates/perry/tests/ctor_this_call_reads_unset_key.rs +++ b/crates/perry/tests/ctor_this_call_reads_unset_key.rs @@ -1,6 +1,10 @@ -//! A method called on `this` from a constructor can read a key the -//! constructor assigns later: it reads `undefined`. Facts must not claim the -//! key is already set (`this.p === undefined` folded to false). +//! A key the constructor stores after code that runs with `this` (here a +//! method call on `this`) holds the hole until the store: the key is absent, +//! and a read continues to the prototype chain (object model §3.2). Code +//! that observes the instance then must see what node sees. The facts keep +//! such a key's slot but not its presence (`FieldFacts::reserved`), and an +//! elided read pairs its load with the hole compare. One program per +//! lowering that consumes the read; each runs per unit and whole program. use std::path::PathBuf; use std::process::Command; @@ -14,37 +18,155 @@ fn clean(cmd: &mut Command) -> &mut Command { cmd } -fn run(whole: bool) -> String { +fn run(src_text: &str, whole: bool) -> String { let dir = tempfile::tempdir().unwrap(); let src = dir.path().join("main.js"); - std::fs::write( - &src, - r#"class P { constructor() { this.v = 5; } } -class A { - constructor(early) { this.s = early ? this.peek() : "-"; this.p = new P(); } - peek() { return "t=" + typeof this.p + " " + (this.p === undefined); } -} -console.log(new A(false).s, new A(true).s); -"#, - ) - .unwrap(); + std::fs::write(&src, src_text).unwrap(); let exe = dir.path().join("main"); let mut cmd = Command::new(PathBuf::from(env!("CARGO_BIN_EXE_perry"))); - clean(&mut cmd).current_dir(dir.path()).env("PERRY_NO_CACHE", "1"); + clean(&mut cmd) + .current_dir(dir.path()) + .env("PERRY_NO_CACHE", "1"); if whole { cmd.env("PERRY_FACTS_UNITS", "0"); } - let out = cmd.arg("compile").arg(&src).arg("-o").arg(&exe).output().expect("compile"); - assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + let out = cmd + .arg("compile") + .arg(&src) + .arg("-o") + .arg(&exe) + .output() + .expect("compile"); + assert!( + out.status.success(), + "{}", + String::from_utf8_lossy(&out.stderr) + ); let mut run = Command::new(&exe); let ran = clean(&mut run).output().expect("run"); - assert!(ran.status.success(), "run failed: {:?}", ran.status); + assert!( + ran.status.success(), + "run failed: {:?}\n{}", + ran.status, + String::from_utf8_lossy(&ran.stderr) + ); String::from_utf8_lossy(&ran.stdout).trim().to_string() } +/// `node main.js` prints `expected`. +fn check(src: &str, expected: &str) { + assert_eq!(run(src, false), expected, "per unit"); + assert_eq!(run(src, true), expected, "whole program"); +} + +#[test] +fn strict_equality_with_undefined() { + check( + r#"class P { constructor() { this.v = 5; } } +class A { + constructor(early) { this.s = early ? this.peek() : "-"; this.p = new P(); } + peek() { return "t=" + typeof this.p + " " + (this.p === undefined); } +} +console.log(new A(false).s, new A(true).s); +"#, + "- t=undefined true", + ); +} + #[test] -fn a_method_called_from_the_constructor_sees_the_unassigned_key() { - // node: `- t=undefined true` - assert_eq!(run(false), "- t=undefined true", "per unit"); - assert_eq!(run(true), "- t=undefined true", "whole program"); +fn loose_equality_with_null() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.p = { v: 1 }; } + peek() { return [this.p == null, this.p != undefined, this.p === null].join(","); } +} +console.log(new A().s); +"#, + "true,false,false", + ); +} + +#[test] +fn nullish_coalescing() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.p = "set"; } + peek() { return this.p ?? "dflt"; } +} +console.log(new A().s, new A().p); +"#, + "dflt set", + ); +} + +#[test] +fn string_conversion() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.p = 7; } + peek() { return String(this.p) + "|" + this.p + "|" + `${this.p}`; } +} +console.log(new A().s); +"#, + "undefined|undefined|undefined", + ); +} + +#[test] +fn number_context() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.n = 3; } + peek() { return this.n + 1; } + later() { return this.n + 1; } +} +const a = new A(); +console.log(a.s, a.later()); +"#, + "NaN 4", + ); +} + +#[test] +fn absent_key_reads_the_prototype() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.p = "own"; } + peek() { return this.p; } +} +A.prototype.p = "proto"; +const a = new A(); +console.log(a.s, a.p); +"#, + "proto own", + ); +} + +#[test] +fn call_through_the_absent_key_calls_the_prototype_method() { + check( + r#"class A { + constructor() { this.r = this.run(); this.f = function () { return "own"; }; } + run() { return this.f(); } +} +A.prototype.f = function () { return "proto"; }; +const a = new A(); +console.log(a.r, a.run()); +"#, + "proto own", + ); +} + +#[test] +fn presence_and_enumeration() { + check( + r#"class A { + constructor() { this.s = this.peek(); this.p = 1; this.q = 2; } + peek() { return ["p" in this, Object.keys(this).join("+"), Object.prototype.hasOwnProperty.call(this, "p"), JSON.stringify(this), !this.p, typeof this.p].join(","); } +} +const a = new A(); +console.log(a.s, Object.keys(a).join("+")); +"#, + "false,,false,{},true,undefined s+p+q", + ); } From abaa1b2a7db09e47ec0c0cc7a002af0b301f45eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 04:13:06 +0100 Subject: [PATCH 039/144] changelog: number the PR fragment --- ...reads-unset-key.md => 13601-ctor-this-call-reads-unset-key.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-ctor-this-call-reads-unset-key.md => 13601-ctor-this-call-reads-unset-key.md} (100%) diff --git a/changelog.d/0-ctor-this-call-reads-unset-key.md b/changelog.d/13601-ctor-this-call-reads-unset-key.md similarity index 100% rename from changelog.d/0-ctor-this-call-reads-unset-key.md rename to changelog.d/13601-ctor-this-call-reads-unset-key.md From 8587d82f0a7827852ac1e5131138feb4b16d22f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 05:06:10 +0000 Subject: [PATCH 040/144] fix(modules): forward node:trace_events exports through its ESM namespace An observed `import * as ns from "node:trace_events"` compiles to the synthetic native ESM namespace module, which forwards the API manifest's public named exports. node:trace_events had no manifest surface, so the namespace held only `default` and `ns.createTracing(...)` threw "createTracing is not a function" (the WASI process_id acceptance test). Declare the module (core module, node submodule) and its createTracing and getEnabledCategories exports in the manifest, regenerate the API docs, and check that every export the runtime implements for a Node builtin submodule is forwarded by its ESM namespace. --- Cargo.lock | 1 + changelog.d/0-trace-events-esm-namespace.md | 9 +++++ crates/perry-api-manifest/src/entries.rs | 1 + .../perry-api-manifest/src/entries/part_3.rs | 4 +++ crates/perry-api-manifest/src/lib.rs | 1 + crates/perry/Cargo.toml | 3 ++ .../collect_modules/native_esm_namespace.rs | 36 +++++++++++++++---- docs/api/perry.d.ts | 12 ++++++- docs/src/api/reference.md | 15 +++++++- 9 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 changelog.d/0-trace-events-esm-namespace.md diff --git a/Cargo.lock b/Cargo.lock index 299221b4de8..4a14dc904a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5190,6 +5190,7 @@ dependencies = [ "perry-codegen-wasm", "perry-codegen-wear-tiles", "perry-diagnostics", + "perry-dispatch", "perry-gc-effects", "perry-hex", "perry-hir", diff --git a/changelog.d/0-trace-events-esm-namespace.md b/changelog.d/0-trace-events-esm-namespace.md new file mode 100644 index 00000000000..4e4a56fb93b --- /dev/null +++ b/changelog.d/0-trace-events-esm-namespace.md @@ -0,0 +1,9 @@ +`import * as traceEvents from "node:trace_events"` now exposes `createTracing` +and `getEnabledCategories` on the namespace object. The ESM namespace module +forwards the API manifest's public names, and `node:trace_events` had no +manifest surface, so the namespace held only `default` and +`traceEvents.createTracing(...)` threw "createTracing is not a function" (native +and WASI; it failed the WASI `process_id` acceptance test). The manifest now +declares the module and its two exports, and a unit test checks that every +export the runtime implements for a Node builtin submodule is forwarded by its +ESM namespace. diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index 5d826f7e692..e4d21d70912 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -192,6 +192,7 @@ pub const NODE_SUBMODULES: &[&str] = &[ // helpers) so the unimplemented-API gate and docs recognize the modules. "timers", "timers/promises", + "trace_events", ]; /// Internal manifest keys used by dispatch/property gates but not importable diff --git a/crates/perry-api-manifest/src/entries/part_3.rs b/crates/perry-api-manifest/src/entries/part_3.rs index 0d168052a86..d2a43249ad7 100644 --- a/crates/perry-api-manifest/src/entries/part_3.rs +++ b/crates/perry-api-manifest/src/entries/part_3.rs @@ -752,6 +752,10 @@ pub(crate) const API_MANIFEST_PART_3: &[ApiEntry] = &[ method("diagnostics_channel", "subscribe", false, None), method("diagnostics_channel", "tracingChannel", false, None), method("diagnostics_channel", "unsubscribe", false, None), + // --- node:trace_events direct submodule. + property("trace_events", "default"), + method("trace_events", "createTracing", false, None), + method("trace_events", "getEnabledCategories", false, None), // --- node:fs/promises direct submodule (#2728). Only the named exports // Perry actually backs with runtime thunks (see // `perry-runtime::node_submodules::fs_promises`) are declared. FileHandle diff --git a/crates/perry-api-manifest/src/lib.rs b/crates/perry-api-manifest/src/lib.rs index c9f0443985f..96b0b08b850 100644 --- a/crates/perry-api-manifest/src/lib.rs +++ b/crates/perry-api-manifest/src/lib.rs @@ -351,6 +351,7 @@ pub fn is_node_core_module(module: &str) -> bool { | "timers" | "timers/promises" | "tls" + | "trace_events" | "tty" | "url" | "util" diff --git a/crates/perry/Cargo.toml b/crates/perry/Cargo.toml index 834516f5583..5efd35376f9 100644 --- a/crates/perry/Cargo.toml +++ b/crates/perry/Cargo.toml @@ -191,6 +191,9 @@ tempfile.workspace = true # object cache keys on a hash of this binary, so a linked runtime would make # every runtime edit miss every cached object. perry-runtime.workspace = true +# The native ESM namespace test reads the runtime's submodule export +# declarations (the same file perry-codegen links exports through). +perry-dispatch.workspace = true [build-dependencies] winresource = "0.1.31" diff --git a/crates/perry/src/commands/compile/collect_modules/native_esm_namespace.rs b/crates/perry/src/commands/compile/collect_modules/native_esm_namespace.rs index e58913c56f4..8bd0fb24dce 100644 --- a/crates/perry/src/commands/compile/collect_modules/native_esm_namespace.rs +++ b/crates/perry/src/commands/compile/collect_modules/native_esm_namespace.rs @@ -41,15 +41,19 @@ pub(super) fn materialize(cache_dir: &Path, specifier: &str) -> Result Ok(path) } -fn source(module: &str) -> String { - // The public manifest already defines which bindings a static native named - // import may expose. Forward those exact bindings, rather than copying the - // mutable CommonJS object's properties or inventing a second export table. - let names: BTreeSet<_> = perry_api_manifest::entries_for_module(module) +/// The public manifest already defines which bindings a static native named +/// import may expose. Forward those exact bindings, rather than copying the +/// mutable CommonJS object's properties or inventing a second export table. +fn named_exports(module: &str) -> BTreeSet<&'static str> { + perry_api_manifest::entries_for_module(module) .filter(|entry| perry_api_manifest::module_has_public_named_export(module, entry.name)) .map(|entry| entry.name) .filter(|name| *name != "default") - .collect(); + .collect() +} + +fn source(module: &str) -> String { + let names = named_exports(module); let specifier = serde_json::to_string(&format!("node:{module}")).unwrap(); let mut source = format!("import __perry_default from {specifier};\nexport default __perry_default;\n"); @@ -226,6 +230,26 @@ mod tests { } } + /// The namespace forwards the manifest's public names, so every export the + /// runtime implements for a Node builtin submodule must be one of them; + /// otherwise `ns.` reads undefined (node:trace_events once had no + /// manifest surface and `traceEvents.createTracing` was not a function). + #[test] + fn namespace_forwards_every_runtime_submodule_export() { + for (module, _, exports) in perry_dispatch::native_submodule_exports::SUBMODULE_EXPORTS { + if !is_builtin(module) { + continue; + } + let names = named_exports(module); + for (name, _) in exports.iter().filter(|(name, _)| *name != "default") { + assert!( + names.contains(name), + "node:{module} implements {name} but its ESM namespace does not forward it" + ); + } + } + } + #[test] fn canonical_builtin_names_and_prefix_only_rule() { assert!(is_builtin("fs")); diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index 024ceb19e29..56841dcbd65 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 2024 entries across 111 modules +// Coverage: 2027 entries across 112 modules type PerryI8 = number & { readonly __perryI8?: never }; type PerryI16 = number & { readonly __perryI16?: never }; @@ -3927,6 +3927,16 @@ declare module "tls" { export function setDefaultCACertificates(certs: any): any; } +declare module "trace_events" { + /** stdlib */ + const _default: any; + export default _default; + /** stdlib */ + export function createTracing(...args: any[]): any; + /** stdlib */ + export function getEnabledCategories(...args: any[]): any; +} + declare module "tty" { /** stdlib */ export class ReadStream { [key: string]: any; } diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 843a1f50f34..69df55b805d 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 2798 entries across 112 modules. +Total: 2802 entries across 113 modules. ## Modules @@ -106,6 +106,7 @@ Total: 2798 entries across 112 modules. - [`timers`](#timers) - [`timers/promises`](#timerspromises) - [`tls`](#tls) +- [`trace_events`](#trace_events) - [`tty`](#tty) - [`typescript`](#typescript) - [`undici`](#undici) @@ -342,6 +343,7 @@ Total: 2798 entries across 112 modules. - `prepare` — instance - `raw` — instance - `run` — instance +- `safeIntegers` — instance - `transaction` — instance ## `buffer` @@ -3339,6 +3341,17 @@ Total: 2798 entries across 112 modules. - `DEFAULT_MIN_VERSION` - `rootCertificates` +## `trace_events` + +### Methods + +- `createTracing` — module +- `getEnabledCategories` — module + +### Properties + +- `default` + ## `tty` ### Classes From 19a6de547303add94b2e94d107b4383b549a39c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:08:31 +0100 Subject: [PATCH 041/144] changelog: number the PR fragment --- ...vents-esm-namespace.md => 13607-trace-events-esm-namespace.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-trace-events-esm-namespace.md => 13607-trace-events-esm-namespace.md} (100%) diff --git a/changelog.d/0-trace-events-esm-namespace.md b/changelog.d/13607-trace-events-esm-namespace.md similarity index 100% rename from changelog.d/0-trace-events-esm-namespace.md rename to changelog.d/13607-trace-events-esm-namespace.md From 018e74908df0f51d72093a864a42dbd348540eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 00:22:15 +0000 Subject: [PATCH 042/144] =?UTF-8?q?wpfacts:=20a=20literal=20handed=20to=20?= =?UTF-8?q?a=20closed=20function=20is=20described=20with=20its=20fields=20?= =?UTF-8?q?(UEB,=20UEA2=20=C2=A74.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/perry-wpfacts/src/gen.rs | 7 +++ crates/perry-wpfacts/src/gen/export.rs | 44 +++++++++++++-- crates/perry-wpfacts/src/lib/contracts.rs | 13 +++++ .../src/tests/unit_mode/method_on_read.rs | 56 ++++++++++++++++++- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index 5aa1575f6bb..d53c9f225f6 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -135,6 +135,12 @@ pub struct Gen<'a> { /// (`sunk_fields`, its nodes `sunk_field_toks`). `all_sunk`: the /// published objects whose fields all leave. pub leaving: Vec, + /// This unit's literals handed to another unit's closed function + /// (decision 13) and the literals of this unit their described fields + /// hold: the argument contract describes their fields as a published + /// literal's (`nested_fields`), but nothing of them leaves: the closed + /// callee's uses come back through its contract. + pub handed: Vec, pub field_layouts: FxHashMap<(TmplId, Atom), Vec>, pub field_prims: crate::pts::FxHashSet<(TmplId, Atom)>, /// A field holding other units' stand-ins (builtins, unknown values) @@ -408,6 +414,7 @@ impl<'a> Gen<'a> { fn_contracts: Vec::new(), ret_ok: Default::default(), leaving: Vec::new(), + handed: Vec::new(), field_layouts: FxHashMap::default(), field_prims: Default::default(), field_values: FxHashMap::default(), diff --git a/crates/perry-wpfacts/src/gen/export.rs b/crates/perry-wpfacts/src/gen/export.rs index e341273e8b4..c4ec70bbd5b 100644 --- a/crates/perry-wpfacts/src/gen/export.rs +++ b/crates/perry-wpfacts/src/gen/export.rs @@ -1009,6 +1009,11 @@ impl<'a> Gen<'a> { self.m.callable_pub.insert(f); } } + for t in self.closed_arg_literals() { + if !self.arr_ok(t) && !self.handed.contains(&t) { + self.handed.push(t); + } + } changed |= self.nested_fields(r); // A literal of this unit passed to another unit's closed function // (decision 13) is described by the argument contract as a value @@ -1687,8 +1692,14 @@ impl<'a> Gen<'a> { let mut changed = false; loop { let n0 = self.leaving.len(); + let h0 = self.handed.len(); let mut by: FxHashMap> = FxHashMap::default(); - for &t in &self.leaving { + let lv: crate::pts::FxHashSet = self.leaving.iter().copied().collect(); + for &t in self + .leaving + .iter() + .chain(self.handed.iter().filter(|t| !lv.contains(t))) + { if let Some(tm) = self.m.tmpl_of(t) { by.entry(tm).or_default().push(t); } @@ -1697,12 +1708,16 @@ impl<'a> Gen<'a> { tms.sort_unstable(); for tm in tms { let ts = by.remove(&tm).unwrap_or_default(); + // Only a leaving token's fields are sunk; a handed one's + // (`handed`) are described or read as unknown by the callee. + let leaves: Vec = ts.iter().copied().filter(|t| lv.contains(t)).collect(); + let handed_only = leaves.is_empty(); if !self.keyed_leave(tm) { // Broken by the census: every field leaves as before. self.field_layouts.retain(|&(t2, _), _| t2 != tm); self.field_values.retain(|&(t2, _), _| t2 != tm); self.field_tas.retain(|&(t2, _), _| t2 != tm); - for &t in &ts { + for &t in &leaves { if !self.all_sunk.contains(&t) { self.sink_all_fields(t, r); changed = true; @@ -1845,6 +1860,11 @@ impl<'a> Gen<'a> { continue; } } + // A handed literal's instance of a contract class would + // leave key by key: not described here. + if handed_only && !cls.is_empty() { + ok = false; + } if ok { for x in std::mem::take(&mut cls) { changed |= self.fields_leave(x, r); @@ -1864,7 +1884,7 @@ impl<'a> Gen<'a> { self.field_values.insert((tm, a), ns); self.field_layouts.remove(&(tm, a)); for (_, x) in inner { - changed |= self.fields_leave(x, r); + changed |= self.inner_leave(x, handed_only, r); } continue; } @@ -1880,7 +1900,7 @@ impl<'a> Gen<'a> { self.field_prims.insert((tm, a)); } for (_, x) in inner { - changed |= self.fields_leave(x, r); + changed |= self.inner_leave(x, handed_only, r); } continue; } @@ -1894,7 +1914,7 @@ impl<'a> Gen<'a> { self.field_tas.remove(&(tm, a)); self.field_prims.remove(&(tm, a)); let ru = self.m.reason(UNDESCRIBED); - for &t in &ts { + for &t in &leaves { if let Some(&n) = self.m.fields.get(&(t, a)) { if self.sunk_field_toks.insert((t, a)) { let rf = match self.field_fns.contains_key(&(t, a)) { @@ -1908,13 +1928,25 @@ impl<'a> Gen<'a> { } } } - if self.leaving.len() == n0 { + if self.leaving.len() == n0 && self.handed.len() == h0 { break; } } changed } + /// A literal of this unit a described field holds: it leaves with its + /// holder (`fields_leave`), or is handed with it (`handed`). + fn inner_leave(&mut self, x: Tok, handed: bool, r: u32) -> bool { + if !handed { + return self.fields_leave(x, r); + } + if !self.handed.contains(&x) { + self.handed.push(x); + } + false + } + /// The module an `export … from source` of module `mi` reads: the /// driver's resolution of the source, else an import of the same source. /// Unresolved, the export is ⊤ (a native or unknown module); a program diff --git a/crates/perry-wpfacts/src/lib/contracts.rs b/crates/perry-wpfacts/src/lib/contracts.rs index 051538d1556..e1d8ed34304 100644 --- a/crates/perry-wpfacts/src/lib/contracts.rs +++ b/crates/perry-wpfacts/src/lib/contracts.rs @@ -1912,6 +1912,19 @@ pub(super) fn arg_contract_of( layouts_out, open_tm, ); + // UEB: its fields as a published literal's + // (`Gen::nested_fields` decided them for the + // literals handed to a closed function, + // `Gen::handed`): what they hold, by key. + if cc.literal_shape + && !contracts_out.iter().any(|(_, c)| &c.display == display) + { + cc.field_layouts = + nested_layouts(g, templates, tm, layouts_out, open_tm); + cc.field_values = + field_value_contracts(g, templates, tm, layouts_out, open_tm); + cc.field_tas = field_ta_contracts(g, tm); + } } vc.layouts.push(cc) } diff --git a/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs b/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs index acc05d70b62..9f5b5c0fb28 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/method_on_read.rs @@ -57,8 +57,12 @@ function transform(context: any) { export function run() { return transform({ factory: new Factory() }); } console.log(run());"#; let u = reads_the_method(&[("n", N), ("t", t)]); - assert!(t_proven(&u, "factory"), "{}", u.dump_text()); - assert_eq!(u.round_work.len(), 2, "{:?}", u.round_work); + // UEB: the caller's literal is described with its fields (its + // `factory` holds n's `Factory`), so the whole body proves as whole. + for k in KEYS { + assert!(t_proven(&u, k), "{k}\n{}", u.dump_text()); + } + assert!(t_proven(&u, "kind"), "{}", u.dump_text()); } /// DECISIONS 148: a published class's method run by another unit, its @@ -76,3 +80,51 @@ import { Factory } from "./n"; console.log(new T().transform({ factory: new Factory() }));"#; reads_the_method(&[("n", N), ("t", t), ("b", b)]); } + +/// UEB: a literal handed to a closed callee is described with its fields; +/// what the callee writes into it, or into what a field holds, is never +/// hidden from the caller: per unit never stronger than whole. +#[test] +fn a_closed_callee_writing_into_a_handed_literal_stays_sound() { + for body in [ + "context.factory = { createIdentifier(n: string) { return { kind: 1, text: n, parent: 0 }; } };", + "context.factory.extra = 1;", + "(globalThis as any).keep(context.factory);", + "context.other = context.factory;", + ] { + let t = format!(r#"import {{ Factory }} from "./n"; +function transform(context: any) {{ + {body} + const node = context.factory.createIdentifier("x"); + return node.kind; +}} +export function run() {{ const c = {{ factory: new Factory() }}; const r = transform(c); return r + (c.factory as any).kind; }} +console.log(run());"#); + let mods = [("n", N), ("t", t.as_str())]; + let (u, w) = (units_of(&mods), whole_of(&mods)); + let stronger: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(stronger.is_empty(), "{body}: per unit stronger than whole: {stronger:?}\n{}", u.dump_text()); + } +} + +/// UEB: a nested literal of the caller in a handed literal's field is +/// described too (its own fields), and a field holding the caller's own +/// function stays unknown to the callee (never stronger). +#[test] +fn a_nested_literal_in_a_handed_literal_is_described() { + let t = r#"import { Factory } from "./n"; +function transform(context: any) { + const node = context.inner.factory.createIdentifier("x"); + return node.kind + context.f(1); +} +export function run() { return transform({ inner: { factory: new Factory() }, f: (x: number) => x }); } +console.log(run());"#; + let mods = [("n", N), ("t", t)]; + let (u, w) = (units_of(&mods), whole_of(&mods)); + let stronger: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(stronger.is_empty(), "per unit stronger than whole: {stronger:?}\n{}", u.dump_text()); + for k in ["inner", "factory", "createIdentifier", "kind"] { + assert!(t_proven(&w, k), "whole {k}\n{}", w.dump_text()); + assert!(t_proven(&u, k), "unit {k}\n{}", u.dump_text()); + } +} From 17a931a60d144ff71cf0d6249f2745e0b351e9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 04:04:33 +0000 Subject: [PATCH 043/144] changelog: UEB closed-function literal --- changelog.d/0-ueb-closed-literal.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/0-ueb-closed-literal.md diff --git a/changelog.d/0-ueb-closed-literal.md b/changelog.d/0-ueb-closed-literal.md new file mode 100644 index 00000000000..14fd256ec8a --- /dev/null +++ b/changelog.d/0-ueb-closed-literal.md @@ -0,0 +1 @@ +Per-unit facts: a literal object handed to a closed function is described with its fields in the call contract, so the callee's reads of it are decided in the passing unit. From 86fc76b7f7ea3885be4ca125b9d5cfc8d0cec8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:24:51 +0100 Subject: [PATCH 044/144] changelog: number the PR fragment --- .../{0-ueb-closed-literal.md => 13611-ueb-closed-literal.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-ueb-closed-literal.md => 13611-ueb-closed-literal.md} (100%) diff --git a/changelog.d/0-ueb-closed-literal.md b/changelog.d/13611-ueb-closed-literal.md similarity index 100% rename from changelog.d/0-ueb-closed-literal.md rename to changelog.d/13611-ueb-closed-literal.md From eb57043bed0ff942c00d729acfe401bdd8d2e084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 18:42:52 +0000 Subject: [PATCH 045/144] fix(wpfacts): forwarded copies of one literal shape join, so the unit fixpoint terminates Several units of a module can forward copies of a literal shape no unit there births (effect cli/internal/parser.js: three units forward the {onNone, onSome} shape). The forward branch replaced the entry, so the last forwarder of a round won; a reader solved before it recorded an earlier copy, saw it changed at the round end, and was solved again every round (the Supabase CLI facts pass never ended). The copies now join, ascending from the last round entry, as several owners of one literal shape do (DECISIONS 112). --- .../0-wpfacts-forwarded-copies-join.md | 1 + crates/perry-wpfacts/src/units/driver.rs | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 changelog.d/0-wpfacts-forwarded-copies-join.md diff --git a/changelog.d/0-wpfacts-forwarded-copies-join.md b/changelog.d/0-wpfacts-forwarded-copies-join.md new file mode 100644 index 00000000000..67786853b69 --- /dev/null +++ b/changelog.d/0-wpfacts-forwarded-copies-join.md @@ -0,0 +1 @@ +Per-unit facts reach their fixpoint when several units forward copies of one literal shape that no unit of the module births: the copies join, ascending from the last round, instead of the last forwarder of a round replacing the others. Before, a reader solved between two forwarders never read what the round left and was solved again every round (the Supabase CLI never finished its facts pass). diff --git a/crates/perry-wpfacts/src/units/driver.rs b/crates/perry-wpfacts/src/units/driver.rs index b2bf24febaf..fc0e5a88132 100644 --- a/crates/perry-wpfacts/src/units/driver.rs +++ b/crates/perry-wpfacts/src/units/driver.rs @@ -446,9 +446,26 @@ pub(crate) fn run(sorted: &[(&str, &Module)], cfg: &FactsConfig) -> ProgramFacts published_layouts.insert(c.display.clone()); if u.layouts_fwd.contains(&c.display) { if !owned_layouts.contains(&c.display) { - input - .layouts - .insert(c.display.clone(), Digested::new(c.clone())); + // Several units forward copies of one literal + // shape no unit here births: its entry is their + // join, ascending from the last round's copy, as + // for several owners (DECISIONS 112). Replaced, + // the last forwarder of a round would win and a + // reader solved before it would never read what + // the round leaves. + let e = match input.layouts.get(&c.display).map(|e| &e.c) { + Some(p) + if c.literal_shape + && p.literal_shape + && copy_of.contains_key(&c.display) => + { + let mut j = p.clone(); + j.join(c); + j + } + _ => c.clone(), + }; + input.layouts.insert(c.display.clone(), Digested::new(e)); copy_of.insert(c.display.clone(), si); } continue; From dff40c0865ff410b09574032c978218755603949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:45:09 +0100 Subject: [PATCH 046/144] changelog: number the PR fragment --- ...rded-copies-join.md => 13579-wpfacts-forwarded-copies-join.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-forwarded-copies-join.md => 13579-wpfacts-forwarded-copies-join.md} (100%) diff --git a/changelog.d/0-wpfacts-forwarded-copies-join.md b/changelog.d/13579-wpfacts-forwarded-copies-join.md similarity index 100% rename from changelog.d/0-wpfacts-forwarded-copies-join.md rename to changelog.d/13579-wpfacts-forwarded-copies-join.md From c790df8e1cefd471387e5e40671094479813643a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:45:02 +0000 Subject: [PATCH 047/144] test(wpfacts): forwarded copies of one literal shape reach a fixpoint effect cli/internal/parser.ts reduced to one module: the parse state mode holds { _tag } literals that several units forward. Before #13579 the per-unit rounds never end (the test times out at 60 s); with the join they end after 5 rounds. --- crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../unit_mode/forwarded_literal_copies.rs | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/forwarded_literal_copies.rs diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index 31651b8f206..1ef13093285 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -595,6 +595,8 @@ mod return_transfers; #[path = "unit_mode/commonjs_helper.rs"] mod commonjs_helper; +#[path = "unit_mode/forwarded_literal_copies.rs"] +mod forwarded_literal_copies; #[path = "unit_mode/pub_callable.rs"] mod pub_callable; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/forwarded_literal_copies.rs b/crates/perry-wpfacts/src/tests/unit_mode/forwarded_literal_copies.rs new file mode 100644 index 00000000000..172226868e2 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/forwarded_literal_copies.rs @@ -0,0 +1,48 @@ +//! effect's `cli/internal/parser.ts` (effect/cli `Command`), reduced: the +//! parse state's `mode` holds `{ _tag }` literals, born in +//! `createParseState` and replaced in `processValue`, and the state goes +//! through the units of the scan. Several units forward copies of one +//! literal shape: replaced, not joined, the last forwarder of a round won, +//! a reader solved before it saw the entry change at the end of every +//! round, and the rounds never ended (#13579). + +use super::*; + +const P: &str = r#" +const buildSubcommandIndex = () => { +}; +const createParseState = () => ({ + mode: { _tag: "AwaitingFirstValue" } +}); +const resolveFirstValue = (context) => { + const subIndex = buildSubcommandIndex(); +}; +const processValue = (value, cursor, context, state) => { + if (state.mode._tag === "AwaitingFirstValue") { + const result = resolveFirstValue(state); + state.mode = { _tag: "CollectingArguments" }; + } +}; +const scanCommandLevel = () => { + const state = createParseState(); + for (let token = cursor.take(); token; token = cursor.take()) { + if (token._tag === "Value") { + const subResult = processValue(token.value, cursor, context, state); + } + } +}; +"#; + +#[test] +fn forwarded_copies_of_one_literal_shape_reach_a_fixpoint() { + let mods: &'static [(&'static str, &'static str)] = &[("p", P)]; + // Before: no fixpoint (the solve runs forever), so the bound is a wait. + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let _ = tx.send(units_of(mods)); + }); + let pf = rx + .recv_timeout(std::time::Duration::from_secs(60)) + .expect("per-unit facts reach a fixpoint"); + assert!(pf.rounds <= 5, "rounds {}", pf.rounds); +} From afa9c389a8e02820b57898a7ca85ab2194e7f975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 18:42:52 +0000 Subject: [PATCH 048/144] fix(wpfacts): forwarded copies of one literal shape join, so the unit fixpoint terminates Several units of a module can forward copies of a literal shape no unit there births (effect cli/internal/parser.js: three units forward the {onNone, onSome} shape). The forward branch replaced the entry, so the last forwarder of a round won; a reader solved before it recorded an earlier copy, saw it changed at the round end, and was solved again every round (the Supabase CLI facts pass never ended). The copies now join, ascending from the last round entry, as several owners of one literal shape do (DECISIONS 112). --- changelog.d/0-wpfacts-forwarded-copies-join.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/0-wpfacts-forwarded-copies-join.md diff --git a/changelog.d/0-wpfacts-forwarded-copies-join.md b/changelog.d/0-wpfacts-forwarded-copies-join.md new file mode 100644 index 00000000000..67786853b69 --- /dev/null +++ b/changelog.d/0-wpfacts-forwarded-copies-join.md @@ -0,0 +1 @@ +Per-unit facts reach their fixpoint when several units forward copies of one literal shape that no unit of the module births: the copies join, ascending from the last round, instead of the last forwarder of a round replacing the others. Before, a reader solved between two forwarders never read what the round left and was solved again every round (the Supabase CLI never finished its facts pass). From 1a563b7a9f7ea5b720dd14384c011ca8907a571f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 19:45:09 +0100 Subject: [PATCH 049/144] changelog: number the PR fragment --- changelog.d/0-wpfacts-forwarded-copies-join.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 changelog.d/0-wpfacts-forwarded-copies-join.md diff --git a/changelog.d/0-wpfacts-forwarded-copies-join.md b/changelog.d/0-wpfacts-forwarded-copies-join.md deleted file mode 100644 index 67786853b69..00000000000 --- a/changelog.d/0-wpfacts-forwarded-copies-join.md +++ /dev/null @@ -1 +0,0 @@ -Per-unit facts reach their fixpoint when several units forward copies of one literal shape that no unit of the module births: the copies join, ascending from the last round, instead of the last forwarder of a round replacing the others. Before, a reader solved between two forwarders never read what the round left and was solved again every round (the Supabase CLI never finished its facts pass). From 9f7da2fb751eed216d2c684ae5793a3db482f92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 03:43:57 +0000 Subject: [PATCH 050/144] wpfacts: a forwarded function copy names the functions their owners describe (per-unit fixpoint terminates on zod v4) Per unit, a unit forwarding another unit's function (a stand-in's contract in a result, an argument of a call through a parameter, a value contract) copied its description whole. Two units whose functions pass each other's closures (zod v4 core/compile.ts: emitOwnKeys hands doc.indented a closure calling the body it was given; the record check hands emitOwnKeys a body that comes back through compileChild) then nested the descriptions one level deeper every round: emitOwnKeys' argument contract grew 4x per two rounds (2.2 MB at r4, 646 MB at r12) and d_zod4 hit the 40 GB cap at r16. The readers take a function's contract from its owner's entry by name in any case (DECISIONS 106, Gen::owner_fn), so a forwarded copy now names every function described inside it whose owner publishes an entry (FnContract::named: identity only, no claim); a copy holds one level of other units' descriptions and its size is bounded by the program's. --- crates/perry-wpfacts/src/lib/contracts.rs | 19 +++- crates/perry-wpfacts/src/units.rs | 102 ++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/crates/perry-wpfacts/src/lib/contracts.rs b/crates/perry-wpfacts/src/lib/contracts.rs index e1d8ed34304..d2631db8009 100644 --- a/crates/perry-wpfacts/src/lib/contracts.rs +++ b/crates/perry-wpfacts/src/lib/contracts.rs @@ -114,7 +114,7 @@ pub(super) fn literal_field_fns( let fcs = fs .into_iter() .map(|(f, ft)| match g.stand_in_fc.get(&ft) { - Some(&c) if g.m.funcs[f as usize].func_id == u32::MAX => c.clone(), + Some(&c) if g.m.funcs[f as usize].func_id == u32::MAX => forwarded_fn(g, c), _ => fn_contract_of(g, templates, f, layouts_out, open_tm), }) .collect(); @@ -371,6 +371,17 @@ fn compiled_body_of(g: &gen::Gen, f: model::FnIdx) -> Option<(String, u32, bool) }) } +/// Another unit's function `fc` (a stand-in's contract) as this unit +/// forwards it: whole, with the functions described inside it that have an +/// owner's entry named (`units::name_owned_in_fn`, DECISIONS 106). +pub(super) fn forwarded_fn(g: &gen::Gen, fc: &units::FnContract) -> units::FnContract { + let mut c = fc.clone(); + if let Some(u) = g.unit_in { + units::name_owned_in_fn(&mut c, &|n| u.own_fn(n).is_some()); + } + c +} + pub(super) fn fn_contract_of( g: &gen::Gen, templates: &[TemplateFacts], @@ -990,7 +1001,7 @@ pub(super) fn describe_nodes( // stand-in contract. if let model::TokKind::Func(f2, _) = g.m.toks[t as usize] { let fc = match g.stand_in_fc.get(&t) { - Some(&fc) => fc.clone(), + Some(&fc) => forwarded_fn(g, fc), None if g.m.funcs[f2 as usize].func_id != u32::MAX => fn_named(g, f2), None => { ret_unknown = true; @@ -1373,7 +1384,7 @@ pub(super) fn describe_toks( _ => { if let Some(&fc) = g.stand_in_fc.get(&t) { if !vc.fns.iter().any(|c| c.name == fc.name) { - vc.fns.push(fc.clone()); + vc.fns.push(forwarded_fn(g, fc)); } } continue; @@ -1736,7 +1747,7 @@ pub(super) fn arg_contract_of( if let model::TokKind::Func(f, _) = g.m.toks[t as usize] { match g.stand_in_fc.get(&t) { Some(&fc) if !vc.fns.iter().any(|c| c.name == fc.name) => { - vc.fns.push(fc.clone()) + vc.fns.push(forwarded_fn(g, fc)) } Some(_) => {} // Its closed publication (23), or a valid normal diff --git a/crates/perry-wpfacts/src/units.rs b/crates/perry-wpfacts/src/units.rs index d06d04d301b..8d0951ca530 100644 --- a/crates/perry-wpfacts/src/units.rs +++ b/crates/perry-wpfacts/src/units.rs @@ -317,6 +317,108 @@ pub(crate) struct FnContract { pub closed: Option, } +impl FnContract { + /// The function named, not described (DECISIONS 106): its identity, + /// and of its body only what makes no claim (every parameter, its + /// receiver and its result escape or are unknown). A reader holding it + /// calls the function through its owner's entry (`Gen::owner_fn`); with + /// no entry it runs this, which is sound. + pub(crate) fn named(&self) -> FnContract { + use crate::model::MARK_ESCAPE; + let n = self.params.len(); + FnContract { + compiled_body: self.compiled_body.clone(), + name: self.name.clone(), + method: self.method, + callable: self.callable, + this_class: self.this_class.clone(), + params: vec![MARK_ESCAPE; n], + reach: vec![MARK_ESCAPE; n], + reach_keys: vec![Vec::new(); n], + variadic: self.variadic, + rest: self.rest, + ret: None, + ret_params: Vec::new(), + ret_reach: Vec::new(), + ret_reach_keys: Vec::new(), + ret_unknown: true, + ret_value: Default::default(), + ret_prim: crate::model::prim::ALL, + lexical_this: self.lexical_this, + this_use: MARK_ESCAPE, + this_reach: MARK_ESCAPE, + this_stores: None, + this_calls: Vec::new(), + param_stores: vec![None; n], + param_flows: Vec::new(), + param_calls: Vec::new(), + param_handovers: Vec::new(), + param_cells: Vec::new(), + closed: self.closed.clone(), + } + } +} + +/// DECISIONS 106 for what a forwarded copy of another unit's function +/// holds: every function described inside it whose owner publishes an entry +/// (`owned`) is named (`FnContract::named`), as the reader takes that entry +/// by name in any case. Copied whole, a description held the copies its +/// owner's description held, which held it again: two units whose functions +/// pass each other's closures (zod's `emitOwnKeys` and the record check it +/// calls back) nested one more level every round, the contracts grew 4x +/// per two rounds and the fixpoint never came. Named, a description holds +/// only its own unit's functions and one level of copies: its size is +/// bounded by the program's, whatever the rounds. +pub(crate) fn name_owned_in_fn(fc: &mut FnContract, owned: &dyn Fn(&str) -> bool) { + name_owned_in_value(&mut fc.ret_value, owned); + for pc in &mut fc.param_calls { + for a in &mut pc.args { + name_owned_in_value(&mut a.value, owned); + } + } + for fl in &mut fc.param_flows { + if let Some(d) = &mut fl.value { + name_owned_in_value(&mut d.value, owned); + } + } +} + +fn name_owned_fns(fns: &mut [FnContract], owned: &dyn Fn(&str) -> bool) { + for f in fns { + name_owned(f, owned); + } +} + +fn name_owned(f: &mut FnContract, owned: &dyn Fn(&str) -> bool) { + if owned(&f.name) { + *f = f.named(); + } else { + name_owned_in_fn(f, owned); + } +} + +fn name_owned_in_value(vc: &mut ValueContract, owned: &dyn Fn(&str) -> bool) { + name_owned_fns(&mut vc.fns, owned); + for c in vc.layouts.iter_mut().chain(vc.classes.iter_mut()) { + name_owned_in_class(c, owned); + } +} + +fn name_owned_in_class(c: &mut ClassContract, owned: &dyn Fn(&str) -> bool) { + for (_, fs) in &mut c.field_fns { + name_owned_fns(fs, owned); + } + for (_, vc) in c.field_values.iter_mut().chain(c.static_values.iter_mut()) { + name_owned_in_value(vc, owned); + } + for (_, f) in c.static_fns.iter_mut().chain(c.method_fns.iter_mut()) { + name_owned(f, owned); + } + if let Some(f) = &mut c.ctor_fn { + name_owned(f, owned); + } +} + /// A published module-scope value binding as its readers see it (design /// §11.2 "an object contract", generalized to every binding another /// declaration unit reads, §12): the layouts it may hold (literal-shape From 29654489a0fc6b4e4372189e5b1faf1ebea42c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 03:56:11 +0000 Subject: [PATCH 051/144] test(wpfacts): forwarded function copies stay bounded (zod v4 compile.ts reduction) --- changelog.d/0-wpfacts-forwarded-fn-copies.md | 1 + crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../tests/unit_mode/forwarded_fn_copies.rs | 288 ++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 changelog.d/0-wpfacts-forwarded-fn-copies.md create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/forwarded_fn_copies.rs diff --git a/changelog.d/0-wpfacts-forwarded-fn-copies.md b/changelog.d/0-wpfacts-forwarded-fn-copies.md new file mode 100644 index 00000000000..4ad57484412 --- /dev/null +++ b/changelog.d/0-wpfacts-forwarded-fn-copies.md @@ -0,0 +1 @@ +Per-unit facts reach their fixpoint when two units pass each other's closures (zod v4's compiled validators): a unit forwarding another unit's function now names the functions described inside it whose owners publish a contract, instead of copying their descriptions whole. Before, the descriptions nested one level deeper every round and d_zod4's facts pass ran out of memory (40 GB in 81 s). diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index 1ef13093285..3975a219ddd 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -597,6 +597,8 @@ mod return_transfers; mod commonjs_helper; #[path = "unit_mode/forwarded_literal_copies.rs"] mod forwarded_literal_copies; +#[path = "unit_mode/forwarded_fn_copies.rs"] +mod forwarded_fn_copies; #[path = "unit_mode/pub_callable.rs"] mod pub_callable; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/forwarded_fn_copies.rs b/crates/perry-wpfacts/src/tests/unit_mode/forwarded_fn_copies.rs new file mode 100644 index 00000000000..15a303c4f6b --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/forwarded_fn_copies.rs @@ -0,0 +1,288 @@ +//! zod v4 `core/compile.ts` with its `doc.ts`, reduced (ddmin over +//! declarations, then lines; the other imports are stubs): the record check +//! hands `emitOwnKeys` a body that compiles the value through +//! `compileChild`, and `emitOwnKeys` hands `doc.indented` a closure calling +//! that body. Per unit, each unit described the other unit's closures inside +//! its own (the calls through `doc` carry them) and forwarded the copies it +//! read whole: every round nested the descriptions one level deeper (d_zod4: +//! the `emitOwnKeys` argument contract 2.2 MB at round 4, 646 MB at round 12, +//! the 40 GB cap at round 16), and this reduction never reaches a fixpoint. +//! A forwarded copy names the functions their owners describe (DECISIONS +//! 106), so the rounds end. + +use super::*; + +const DOC: &str = r#" +type ModeWriter = (doc: Doc, modes: { execution: "sync" | "async" }) => void; + +export class Doc { + args: string[]; + closed: Record; + content: string[] = []; + indent = 0; + + constructor(args: string[] = [], closed: Record = {}) { + this.args = args; + this.closed = closed; + } + + indented(fn: (doc: Doc) => void) { + this.indent += 1; + try { + fn(this); + } finally { + this.indent -= 1; + } + } + + write(fn: ModeWriter): void; + write(line: string): void; + write(arg: any) { + if (typeof arg === "function") { + (arg as ModeWriter)(this, { execution: "sync" }); + (arg as ModeWriter)(this, { execution: "async" }); + return; + } + + const content = arg as string; + const lines = content.split("\n").filter((x) => x); + const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length)); + const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x); + for (const line of dedented) { + this.content.push(line); + } + } + + compile(): any { + const F = Function; + const content = this?.content ?? [``]; + const factory = new F( + ...Object.keys(this.closed), + `return function (${this.args.join(", ")}) {\n${content.join("\n")}\n};` + ); + return factory(...Object.values(this.closed)) as any; + } +} +"#; + +const STUBS: &str = r#" +export class $ZodAsyncError extends Error {} +export function isBackEdge(a: any, b?: any): boolean { return false; } +export function isRecursiveSchema(a: any): boolean { return false; } +export function isValidBase64(s: string) { return s.length > 0; } +export function isValidBase64URL(s: string) { return s.length > 0; } +export function isValidCIDRv6(s: string) { return s.length > 0; } +export function isValidCreditCard(s: string) { return s.length > 0; } +export function isValidIBAN(s: string) { return s.length > 0; } +export function isValidIPv6(s: string) { return s.length > 0; } +export function isValidJWT(s: string, a?: any) { return s.length > 0; } +export function mergeValues(a: any, b: any) { return { valid: true, data: a }; } +export function stripTabAndNewline(s: string) { return s; } +export function urlHostnameOk(a: any, b: any) { return true; } +export function urlProtocolOk(a: any, b: any) { return true; } +export function validateURL(a: any) { return a; } +"#; + +const STUBS_RE: &str = r#" +export const number = /^-?\d+(?:\.\d+)?$/; +"#; + +const STUBS_UTIL: &str = r#" +export const CONSTANT_CATCH = Symbol("c"); +export type JWTAlgorithm = string; +export type Primitive = string | number; +export const NUMBER_FORMAT_RANGES: Record = { int32: [-2147483648, 2147483647] }; +export function clone(a: any, b?: any) { return a; } +export function codePointLength(s: string) { return s.length; } +export function esc(s: any) { return JSON.stringify(s); } +export function floatSafeRemainder(a: number, b: number) { return a % b; } +export function isPlainObject(a: any) { return typeof a === "object" && a !== null; } +export function shallowClone(a: any) { return { ...a }; } +"#; + +const COMPILE: &str = r#" +import { Doc } from "./doc"; +import * as regexes from "./stubs_re"; +import * as util from "./stubs_util"; +import { $ZodAsyncError, isBackEdge, isRecursiveSchema, isValidBase64, isValidBase64URL, isValidCIDRv6, isValidCreditCard, isValidIBAN, isValidIPv6, isValidJWT, mergeValues, stripTabAndNewline, urlHostnameOk, urlProtocolOk, validateURL } from "./stubs"; +type SomeType = any; type ParseContextInternal = any; type ParsePayload = any; +declare namespace checks { type $ZodCheckNumberFormatDef = any; type $ZodCheckBigIntFormatDef = any; } +declare namespace core { type output = any; } +export function compileFn(schema: T, options?: CompileFnOptions): CompiledFn> { + try { + } catch {} + const ctx: CompileContext = { + }; + const outputAccessor = generateCheck(doc, ctx, schema, "input", !options?.assertOnly); +} +function compileChild( + doc: Doc, +): string | null { + const constantCount = ctx.constants.size; + const constantCounter = ctx.constantCounter; + const varCounter = ctx.varCounter; + try { + return generateCheck(doc, ctx, schema, accessor, needsValue); + } catch (err) { + if (!(err instanceof ZodCompileUnsupportedError) || !err.islandable) throw err; + ctx.varCounter = varCounter; + return emitRuntimeIsland(doc, ctx, schema, accessor); + } +} + +function emitRuntimeIsland(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string { + for (const check of schemaChecks) { + const def = check._zod.def; + switch (def.check) { + case "greater_than": + break; + break; + case "overwrite": { + void (def satisfies never); + throw new ZodCompileUnsupportedError(`check type ${(def as { check: string }).check}`); + } + } + } + + return currentAccessor; +} + +function generateLessThanCheck( + doc: Doc, +): void { + return undefined as any; +} +function generatePropertyCheck( + accessor: string +): void { + return undefined as any; +} +function generateStringFormatCheck( + doc: Doc, + needsValue = true +): string | null { + return undefined as any; +} +function generateCheck( + doc: Doc, + needsValue = true +): string | null { + const def = schema._zod.def; + let typeAccessor: string | null; + + switch (type) { + case "string": + typeAccessor = "true"; + break; + typeAccessor = generateRecordCheck(doc, ctx, schema, accessor); + break; + case "map": + default: { + void (type satisfies never); + throw new ZodCompileUnsupportedError(`schema type ${type}`); + } + } + + return undefined as any; +} +function generateObjectCheck( + doc: Doc, + ctx: CompileContext, +): string | null { + return undefined as any; +} +function generateArrayCheck( + doc: Doc, + ctx: CompileContext, +): string | null { + return undefined as any; +} +function generateUnionCheck(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string { + const def = schema._zod.def as unknown as { + options: SomeType[]; + }; +} + +function generateDiscriminatedUnionCheck( + doc: Doc, +): string { + return undefined as any; +} +function generateRecordCheck(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string { + const def = schema._zod.def as unknown as { keyType: SomeType; valueType: SomeType }; + + const keyValues = recordDef.partial ? undefined : (def.keyType._zod as unknown as { values?: Set }).values; + if (keyValues) { + const inputKeys: Array = []; + for (const key of keyValues) { + const keyConst = addConstant(ctx, key); + const outKey = generateCheck(doc, ctx, def.keyType, keyConst); + const valOutput = compileChild(doc, ctx, def.valueType, valueVar); + doc.write(`${outputVar}[${outKey}] = ${valOutput};`); + } + } + + keyDef.type === "string" && keyDef.format === undefined && !keyDef.coerce && (keyDef.checks?.length ?? 0) === 0; + if (!keyIsBareString) { + const isLoose = (def as { mode?: string }).mode === "loose"; + const keyFn = compileFn(def.keyType); + emitOwnKeys(doc, ctx, accessor, kVar, (d) => { + d.write(`let ${outKeyVar} = ${keyFast}(${kVar});`); + d.write(`${outputVar}[${outKeyVar}] = ${valOutput};`); + }); + return outputVar; + } + + emitOwnKeys( + doc, + ctx, + accessor, + kVar, + (d) => { + d.write(`const ${valVar} = ${accessor}[${kVar}];`); + const valOutput = compileChild(d, ctx, def.valueType, valVar); + d.write(`${outputVar}[${kVar}] = ${valOutput};`); + }, + `return INVALID;` + ); + + return outputVar; +} + +function emitOwnKeys( + doc: Doc, + ctx: CompileContext, + accessor: string, + kVar: string, + body: (d: Doc) => void, + onSymbol?: string +): void { + const propIsEnumerableConst = addConstant(ctx, Object.prototype.propertyIsEnumerable); + const symsVar = newVar(ctx); + doc.indented((d) => { + d.write(`const ${kVar} = ${symsVar}[${iVar}];`); + d.write(`if (!${propIsEnumerableConst}.call(${accessor}, ${kVar})) continue;`); + if (onSymbol) d.write(onSymbol); + else body(d); + }); +"#; + +const MAIN: &str = r#" +import { compileFn } from "./compile"; +const s: any = { _zod: { def: { type: "record", keyType: { _zod: { def: { type: "string" } } }, valueType: { _zod: { def: { type: "string" } } } } } }; +try { console.log(String(compileFn(s))); } catch (e) { console.log("err", String(e)); } +"#; + +#[test] +fn forwarded_function_copies_stay_bounded() { + let mods: &'static [(&'static str, &'static str)] = &[("doc", DOC), ("stubs", STUBS), ("stubs_re", STUBS_RE), ("stubs_util", STUBS_UTIL), ("compile", COMPILE), ("main", MAIN)]; + // Before: the rounds never end, so the bound is a wait. + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let _ = tx.send(units_of(mods)); + }); + let pf = rx + .recv_timeout(std::time::Duration::from_secs(60)) + .expect("per-unit facts reach a fixpoint"); + assert!(pf.rounds <= 8, "rounds {}", pf.rounds); +} From 261895d40fdc710c3198dfa6026c253503a0f68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:19:52 +0100 Subject: [PATCH 052/144] changelog: number the PR fragment --- ...orwarded-fn-copies.md => 13608-wpfacts-forwarded-fn-copies.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-forwarded-fn-copies.md => 13608-wpfacts-forwarded-fn-copies.md} (100%) diff --git a/changelog.d/0-wpfacts-forwarded-fn-copies.md b/changelog.d/13608-wpfacts-forwarded-fn-copies.md similarity index 100% rename from changelog.d/0-wpfacts-forwarded-fn-copies.md rename to changelog.d/13608-wpfacts-forwarded-fn-copies.md From 12060d1990c6dc5bb0a6335157c18c9ccc562698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 20:15:22 +0000 Subject: [PATCH 053/144] feat(compile): export demand, layer 1 of dead-code elimination The first collection walk summarizes each module's top-level items (what each declares, which module-scope names it mentions, whether evaluating it can do more than create its bindings, by structure). Between the walks the program's export demand is followed from the graph roots through imports, re-exports and export * by name; the second walk lowers each module without the items nothing demands. Edges stay (an import or re-export with no used binding becomes a bare import), so every module evaluates in the same order with every effectful item. A module whose exports cannot be followed by name (dynamic import, a namespace used as a value, CommonJS, eval, with) keeps everything; an unresolved edge into a compiled module turns the pass off for the program. PERRY_EXPORT_DEMAND=off disables it (A/B); PERRY_EXPORT_DEMAND_REPORT= writes per-module counts. --- changelog.d/0-export-demand.md | 1 + crates/perry-codegen/src/expr/v8_interop.rs | 27 +- crates/perry/src/commands/compile.rs | 1 + .../perry/src/commands/compile/bootstrap.rs | 14 + .../src/commands/compile/collect_modules.rs | 3 + .../src/commands/compile/export_demand.rs | 114 +++ .../commands/compile/export_demand/plan.rs | 614 ++++++++++++++ .../commands/compile/export_demand/prune.rs | 73 ++ .../commands/compile/export_demand/summary.rs | 795 ++++++++++++++++++ .../commands/compile/export_demand/tests.rs | 116 +++ .../src/commands/compile/reachability.rs | 2 +- crates/perry/src/commands/compile/types.rs | 3 + 12 files changed, 1743 insertions(+), 20 deletions(-) create mode 100644 changelog.d/0-export-demand.md create mode 100644 crates/perry/src/commands/compile/export_demand.rs create mode 100644 crates/perry/src/commands/compile/export_demand/plan.rs create mode 100644 crates/perry/src/commands/compile/export_demand/prune.rs create mode 100644 crates/perry/src/commands/compile/export_demand/summary.rs create mode 100644 crates/perry/src/commands/compile/export_demand/tests.rs diff --git a/changelog.d/0-export-demand.md b/changelog.d/0-export-demand.md new file mode 100644 index 00000000000..463481580ed --- /dev/null +++ b/changelog.d/0-export-demand.md @@ -0,0 +1 @@ +Compiled programs no longer contain declarations that only unused exports reach. Between the two collection walks the compiler follows the program's export demand through every import, re-export and `export *` by name, and the second walk lowers each module without the top-level items nothing demands: function, class and `const` declarations whose evaluation only creates their bindings, decided by structure, together with the import specifiers only those items used. Every module still evaluates, in the same order, with every item that can run code. A module whose exports can't be followed by name (dynamic `import()`, a namespace used as a value, CommonJS, `eval`) keeps everything. `new globalThis.()` for a name no global owns now throws TypeError instead of building an empty object. diff --git a/crates/perry-codegen/src/expr/v8_interop.rs b/crates/perry-codegen/src/expr/v8_interop.rs index ad0478dc916..2d2dffc81d7 100644 --- a/crates/perry-codegen/src/expr/v8_interop.rs +++ b/crates/perry-codegen/src/expr/v8_interop.rs @@ -302,25 +302,14 @@ pub(crate) fn try_static_class_name<'a>(callee: &'a Expr, ctx: &FnCtx<'_>) -> Op object, property, .. } => { if is_global_object_expr(object.as_ref()) { - // Map construction observes the current global value. Unlike - // the intrinsic-only builtin routes below, this property can - // be an accessor that returns another constructor. - if property == "Map" { - return None; - } - // #10359: `lower_new` resolves the name against the module's - // classes, class aliases and imports before (or instead of) - // the builtin, but a module binding is never a property of - // the global object. With `import { Event } from "./ev"` in - // scope, folding `new globalThis.Event()` built the imported - // class; read the property and construct it at runtime. - if ctx.classes.contains_key(property) - || ctx.local_class_aliases.contains_key(property) - || ctx.import_function_prefixes.contains_key(property) - { - return None; - } - return Some(Cow::Borrowed(property.as_str())); + // A global-object property is never a module class: the + // caller builds a builtin intrinsic through the builtin + // table (`lower_global_intrinsic_new`) and constructs any + // other name's runtime value, which throws when the global + // does not exist. Folded by name, `lower_new` resolved it + // against the module's bindings first (#10359) and built an + // empty placeholder for a name nothing owns. + return None; } // Namespace import: `import * as ns from 'm'; new ns.Foo()`. // The namespace object surfaces either as `LocalGet(id)` (mapped to diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index 6ebdd67c8aa..e01dddc4d9c 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -40,6 +40,7 @@ mod compressed_libs; mod embed; mod env_fold; mod export_analysis; +mod export_demand; mod fallback_diag; mod fn_infer; mod gc_effects_sidecar; diff --git a/crates/perry/src/commands/compile/bootstrap.rs b/crates/perry/src/commands/compile/bootstrap.rs index fa307af78e9..abcd385994a 100644 --- a/crates/perry/src/commands/compile/bootstrap.rs +++ b/crates/perry/src/commands/compile/bootstrap.rs @@ -263,7 +263,20 @@ pub(super) fn rerun_collect_with_class_field_types( // were lowered. The set is kept (it is already the program-wide union), so // the re-walk lowers every module against the final set and cannot grow it. let patched_builtins_rerun = ctx.patched_builtins_grew_after_lower; + // Layer-1 DCE: the export demand of the first walk's graph; the second + // walk lowers each module without what nothing demands. + let mut demand_roots = vec![args + .input + .canonicalize() + .unwrap_or_else(|_| args.input.clone())]; + if let Some(ext_dir) = &args.bundle_extensions { + for (entry_path, _) in discover_extension_entries(ext_dir)? { + demand_roots.push(entry_path.canonicalize().unwrap_or(entry_path)); + } + } + let export_prunes = super::export_demand::make_plan(ctx, &demand_roots)?; if field_map.is_empty() + && !export_prunes && accessor_map.is_empty() && !ctx.solid_client_recollect && !patched_builtins_rerun @@ -316,6 +329,7 @@ pub(super) fn rerun_collect_with_class_field_types( } } ctx.cjs_require_diagnostics = cjs_require_diagnostics; + super::export_demand::check_walks(ctx)?; Ok(()) } diff --git a/crates/perry/src/commands/compile/collect_modules.rs b/crates/perry/src/commands/compile/collect_modules.rs index a3b0e7dfe2d..f8647cbdea0 100644 --- a/crates/perry/src/commands/compile/collect_modules.rs +++ b/crates/perry/src/commands/compile/collect_modules.rs @@ -620,6 +620,9 @@ fn collect_module_one( let ast_module = forwarding_module.as_ref().unwrap_or(ast_module); let native_namespace_module = native_esm_namespace::rewrite_static(ast_module, &ctx.cache_dir)?; let ast_module = native_namespace_module.as_ref().unwrap_or(ast_module); + // Layer-1 DCE: summarized in the first walk, pruned in the second. + let demand_pruned = super::export_demand::visit(ctx, &canonical, ast_module, was_cjs_wrapped); + let ast_module = demand_pruned.as_ref().unwrap_or(ast_module); let file_loader_sources = file_loader_import_sources(ast_module); let source_file_path = canonical.to_string_lossy().to_string(); if !perry_hir::source_remap::is_covered(&canonical) { diff --git a/crates/perry/src/commands/compile/export_demand.rs b/crates/perry/src/commands/compile/export_demand.rs new file mode 100644 index 00000000000..aa59255932f --- /dev/null +++ b/crates/perry/src/commands/compile/export_demand.rs @@ -0,0 +1,114 @@ +//! Export demand: compiled programs keep no declaration that only unused +//! exports reach (layer 1 of dead-code elimination, module/export level, as +//! rollup does it; sound without facts). +//! +//! The first collection walk summarizes each module's top-level items +//! (`summary`): what each declares, which module-scope names it mentions, +//! and whether evaluating it can do anything but create its bindings +//! (`Eval`, by structure: declarations, literals, functions, classes whose +//! heritage, keys and static parts are inert). Between the walks `plan` +//! follows the demand from the graph roots through every import, re-export +//! and `export *` by name. The second walk (`rerun_collect_with_class_field_types`) +//! drops each dead item from the AST before lowering (`prune`), so facts and +//! codegen never see it. Every module still evaluates, in the same order, +//! with every effectful item. +//! +//! `PERRY_EXPORT_DEMAND=off` keeps everything (A/B measurement); +//! `PERRY_EXPORT_DEMAND_REPORT=` writes the per-module counts. + +mod plan; +mod prune; +mod summary; +#[cfg(test)] +mod tests; + +use std::collections::{BTreeMap, HashMap}; +use std::path::{Path, PathBuf}; + +use anyhow::{bail, Result}; +use swc_ecma_ast as ast; + +use super::CompilationContext; + +pub(crate) use plan::Prune; +pub(crate) use summary::Summary; + +/// The walk state: summaries during the first walk, the plan in the second. +#[derive(Default)] +pub(crate) struct ExportDemand { + summaries: HashMap, + plan: Option>, + /// Modules whose second-walk body differs from the summarized one. + changed: Vec, +} + +fn enabled() -> bool { + !matches!( + std::env::var("PERRY_EXPORT_DEMAND").as_deref(), + Ok("off" | "0" | "false" | "no") + ) +} + +/// Called for every module the walk lowers, with the AST lowering receives. +/// First walk: records the summary. Second walk: the pruned AST, if any. +pub(super) fn visit( + ctx: &mut CompilationContext, + canonical: &Path, + module: &ast::Module, + cjs_wrapped: bool, +) -> Option { + if !enabled() { + return None; + } + let state = &mut ctx.export_demand; + match &state.plan { + None => { + state.summaries.insert( + canonical.to_path_buf(), + summary::summarize(module, cjs_wrapped), + ); + None + } + Some(plan) => { + let prune = plan.get(canonical)?; + if summary::fingerprint(module) != prune.fingerprint { + state.changed.push(canonical.to_path_buf()); + return None; + } + Some(prune::prune(module, prune)) + } + } +} + +/// Between the walks: the plan. True when the second walk prunes anything. +pub(super) fn make_plan(ctx: &mut CompilationContext, roots: &[PathBuf]) -> Result { + if !enabled() { + return Ok(false); + } + let summaries = std::mem::take(&mut ctx.export_demand.summaries); + let planned = plan::plan(ctx, &summaries, roots); + let Some(planned) = planned else { + eprintln!( + "perry: export demand: an import of a compiled module did not resolve; nothing pruned" + ); + return Ok(false); + }; + if let Some(path) = std::env::var_os("PERRY_EXPORT_DEMAND_REPORT") { + std::fs::write(&path, planned.report.join("\n") + "\n")?; + } + let any = !planned.prunes.is_empty(); + ctx.export_demand.plan = Some(planned.prunes); + Ok(any) +} + +/// After the second walk: a module the plan pruned others for must have +/// lowered the body that was summarized. +pub(super) fn check_walks(ctx: &CompilationContext) -> Result<()> { + if let Some(p) = ctx.export_demand.changed.first() { + bail!( + "export demand: {} changed between the collection walks; set PERRY_EXPORT_DEMAND=off and report this", + p.display() + ); + } + Ok(()) +} diff --git a/crates/perry/src/commands/compile/export_demand/plan.rs b/crates/perry/src/commands/compile/export_demand/plan.rs new file mode 100644 index 00000000000..17e12e947b3 --- /dev/null +++ b/crates/perry/src/commands/compile/export_demand/plan.rs @@ -0,0 +1,614 @@ +//! The program's export demand: from the graph roots, which exported names +//! some kept item reads, and so which items of each module stay. A module +//! whose exports cannot be followed by name (a dynamic import, a namespace +//! object used as a value, a CommonJS wrap, `eval`) is demanded whole. + +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; +use std::path::{Path, PathBuf}; + +use perry_hir::ImportSpecifier; + +use super::super::CompilationContext; +use super::summary::{Cond, Eval, Kind, Summary}; + +/// What the second walk removes from one module. +#[derive(Debug, Clone, Default)] +pub(crate) struct Prune { + pub fingerprint: u64, + /// Items (by body index) no kept item and no demanded export needs. + pub items: BTreeSet, + /// Import locals no kept item mentions. + pub imports: BTreeSet, + /// Exported names nothing demands (local and re-export specifiers). + pub exports: BTreeSet, +} + +impl Prune { + pub fn is_empty(&self) -> bool { + self.items.is_empty() && self.imports.is_empty() && self.exports.is_empty() + } +} + +#[derive(Default, Clone)] +struct Demand { + whole: bool, + names: BTreeSet, + /// Names a namespace call reaches (`ns.f()` passes the namespace as `this`). + via_namespace: BTreeSet, +} + +/// Edges of one module: its AST sources resolved, and the HIR edges no item +/// shows (dynamic imports, CommonJS requires). +struct Module<'a> { + summary: Option<&'a Summary>, + /// Item index -> resolved target (imports, re-exports, `export *`). + targets: HashMap, + /// Targets every name of which is read: HIR edges no item can follow. + whole_targets: Vec, + /// Plain named HIR imports of a module without a summary. + named_targets: Vec<(PathBuf, String)>, +} + +pub(crate) struct Planned { + pub prunes: BTreeMap, + pub report: Vec, +} + +/// The plan, or `None` when an edge into a compiled module cannot be +/// resolved (then nothing is pruned anywhere). +pub(crate) fn plan( + ctx: &mut CompilationContext, + summaries: &HashMap, + roots: &[PathBuf], +) -> Option { + let paths: Vec = ctx.native_modules.keys().cloned().collect(); + let key_set: HashSet = paths.iter().cloned().collect(); + let mut modules: HashMap = HashMap::new(); + for p in &paths { + let hir_imports = ctx.native_modules[p].imports.clone(); + let hir_exports = ctx.native_modules[p].exports.clone(); + let summary = summaries.get(p).filter(|s| s.prunable); + let mut m = Module { + summary, + targets: HashMap::new(), + whole_targets: Vec::new(), + named_targets: Vec::new(), + }; + let resolved = |source: &str| { + hir_imports + .iter() + .find(|i| i.source == source) + .and_then(|i| i.resolved_path.clone()) + }; + if let Some(s) = summary { + for (index, item) in s.items.iter().enumerate() { + let Some(source) = &item.source else { continue }; + if item.kind == Kind::Erased { + continue; + } + let rp = resolved(source); + match super::super::reachability::resolve_edge( + ctx, + p, + rp.as_deref(), + source, + &key_set, + ) { + Some(t) => { + m.targets.insert(index, t); + } + None if compiled_specifier(ctx, p, source, &key_set) => return None, + None => {} + } + } + } + for import in &hir_imports { + if import.type_only || import.runtime_erased { + continue; + } + let follows_items = summary.is_some() + && !import.is_dynamic + && !import.is_dynamic_target + && !import.is_deferred_require + && !import.is_adopted_require; + if follows_items { + continue; + } + let Some(t) = super::super::reachability::resolve_edge( + ctx, + p, + import.resolved_path.as_deref(), + &import.source, + &key_set, + ) else { + continue; + }; + let plain = !import.is_dynamic + && !import.is_dynamic_target + && !import.is_deferred_require + && !import.is_adopted_require; + let named: Option> = plain + .then(|| { + import + .specifiers + .iter() + .map(|s| match s { + ImportSpecifier::Named { imported, .. } => Some(imported.clone()), + ImportSpecifier::Default { .. } => Some("default".to_string()), + ImportSpecifier::Namespace { .. } => None, + }) + .collect() + }) + .flatten(); + match named { + Some(names) => m + .named_targets + .extend(names.into_iter().map(|n| (t.clone(), n))), + None => m.whole_targets.push(t), + } + } + // A module without a summary re-exports what its HIR says. + if summary.is_none() { + for export in &hir_exports { + let source = match export { + perry_hir::Export::ReExport { source, .. } + | perry_hir::Export::ExportAll { source } + | perry_hir::Export::NamespaceReExport { source, .. } => source, + perry_hir::Export::Named { .. } => continue, + }; + let rp = resolved(source); + if let Some(t) = super::super::reachability::resolve_edge( + ctx, + p, + rp.as_deref(), + source, + &key_set, + ) { + m.whole_targets.push(t); + } + } + } + modules.insert(p.clone(), m); + } + + // Import cycles: an imported binding is initialized when its module is + // not in the reader's cycle (it finished evaluating first). + let scc = components(&paths, &modules); + + // Roots: the entry and every module no edge enters (workers, extension + // entries) are demanded whole, as is every module without a summary. + let mut entered: HashSet<&PathBuf> = HashSet::new(); + for m in modules.values() { + entered.extend(m.targets.values()); + entered.extend(m.whole_targets.iter()); + entered.extend(m.named_targets.iter().map(|(t, _)| t)); + } + let mut demand: HashMap = HashMap::new(); + let mut work: VecDeque = VecDeque::new(); + for p in &paths { + let root = roots.contains(p) || !entered.contains(p) || modules[p].summary.is_none(); + if root { + demand.entry(p.clone()).or_default().whole = true; + } + work.push_back(p.clone()); + } + + let mut live: HashMap, HashSet)> = HashMap::new(); + let mut queued: HashSet = paths.iter().cloned().collect(); + while let Some(p) = work.pop_front() { + queued.remove(&p); + let m = &modules[&p]; + if let Some(s) = m.summary { + // `ns.f()` passes the namespace as `this`: an `f` that reads + // `this` can reach any export. + let d = demand.entry(p.clone()).or_default(); + if !d.whole + && d.via_namespace.iter().any(|n| { + s.items.iter().any(|it| { + it.exports + .iter() + .any(|(e, l)| e == n && s.this_reading.contains(l)) + }) + }) + { + d.whole = true; + } + } + let d = demand.get(&p).cloned().unwrap_or_default(); + let mut out: Vec<(PathBuf, Option, bool)> = Vec::new(); + match m.summary { + None => { + for t in &m.whole_targets { + out.push((t.clone(), None, false)); + } + for (t, n) in &m.named_targets { + out.push((t.clone(), Some(n.clone()), false)); + } + } + Some(s) => { + let (items, names) = + live_items(s, &d, |cond| holds(cond, &p, s, m, &modules, &scc)); + // What the kept items read across edges. + let mut ns_whole: HashSet<&str> = HashSet::new(); + for (i, item) in s.items.iter().enumerate() { + if items[i] { + for e in &item.escapes { + ns_whole.insert(e); + } + } + // An exported namespace import is the namespace object + // itself, a value: `import * as ns; export { ns }`. + for (exported, local) in &item.exports { + if d.whole || d.names.contains(exported) { + ns_whole.insert(local); + } + } + } + for (i, item) in s.items.iter().enumerate() { + let Some(t) = m.targets.get(&i) else { continue }; + match item.kind { + Kind::Import => { + for (local, imported) in &item.imports { + if !names.contains(local) { + continue; + } + match imported { + Some(x) => out.push((t.clone(), Some(x.clone()), false)), + None if ns_whole.contains(local.as_str()) => { + out.push((t.clone(), None, false)) + } + None => { + for (j, other) in s.items.iter().enumerate() { + if !items[j] { + continue; + } + for (ns, member) in &other.members { + if ns == local { + out.push(( + t.clone(), + Some(member.clone()), + true, + )); + } + } + } + } + } + } + } + Kind::ReExport => { + for (exported, imported) in &item.reexports { + if !(d.whole || d.names.contains(exported)) { + continue; + } + match imported { + Some(x) => out.push(( + t.clone(), + Some(x.clone()), + d.via_namespace.contains(exported), + )), + None => out.push((t.clone(), None, false)), + } + } + } + Kind::ExportAll => { + if d.whole { + out.push((t.clone(), None, false)); + } else { + // A name this module does not declare itself + // may come from any `export *` source. + let own: HashSet<&String> = s + .items + .iter() + .flat_map(|it| { + it.exports + .iter() + .map(|(e, _)| e) + .chain(it.reexports.iter().map(|(e, _)| e)) + }) + .collect(); + for n in &d.names { + if !own.contains(n) { + out.push(( + t.clone(), + Some(n.clone()), + d.via_namespace.contains(n), + )); + } + } + } + } + _ => {} + } + } + for t in &m.whole_targets { + out.push((t.clone(), None, false)); + } + live.insert(p.clone(), (items, names)); + } + } + for (t, name, via_ns) in out { + let td = demand.entry(t.clone()).or_default(); + let grew = match name { + None => !std::mem::replace(&mut td.whole, true), + Some(n) => { + let mut g = td.names.insert(n.clone()); + if via_ns { + g |= td.via_namespace.insert(n); + } + g + } + }; + if grew && queued.insert(t.clone()) { + work.push_back(t); + } + } + } + + let mut prunes = BTreeMap::new(); + let mut report = Vec::new(); + for p in &paths { + let m = &modules[p]; + let Some(s) = m.summary else { continue }; + let Some((items, names)) = live.get(p) else { + continue; + }; + let d = demand.get(p).cloned().unwrap_or_default(); + let mut prune = Prune { + fingerprint: s.fingerprint, + ..Default::default() + }; + for (i, item) in s.items.iter().enumerate() { + match item.kind { + Kind::Other if !items[i] => { + prune.items.insert(i); + } + Kind::Import => { + for (local, _) in &item.imports { + if !names.contains(local) { + prune.imports.insert(local.clone()); + } + } + } + Kind::ExportLocal | Kind::ReExport if !d.whole => { + for e in item + .exports + .iter() + .map(|(e, _)| e) + .chain(item.reexports.iter().map(|(e, _)| e)) + { + if !d.names.contains(e) { + prune.exports.insert(e.clone()); + } + } + } + _ => {} + } + } + report.push(format!( + "{}\titems {}\tdead {}\timports_dead {}\texports_dead {}\twhole {}", + p.display(), + s.items.len(), + prune.items.len(), + prune.imports.len(), + prune.exports.len(), + d.whole + )); + if !prune.is_empty() { + prunes.insert(p.clone(), prune); + } + } + Some(Planned { prunes, report }) +} + +/// The kept items of a module under `d`, and the locals they keep. +fn live_items( + s: &Summary, + d: &Demand, + holds: impl Fn(&Cond) -> bool, +) -> (Vec, HashSet) { + let n = s.items.len(); + let mut items = vec![false; n]; + let mut names: HashSet = HashSet::new(); + let mut pending: Vec = Vec::new(); + let keep = |i: usize, items: &mut Vec, pending: &mut Vec| { + if !items[i] { + items[i] = true; + pending.extend(s.items[i].mentions.iter().cloned()); + } + }; + for (i, item) in s.items.iter().enumerate() { + let inert = match &item.eval { + Eval::Effect => false, + Eval::Inert(conds) => conds.iter().all(&holds), + }; + let structural = matches!( + item.kind, + Kind::Import | Kind::ExportLocal | Kind::ReExport | Kind::ExportAll | Kind::Erased + ); + if structural || !inert { + keep(i, &mut items, &mut pending); + } + } + // Demanded exports keep their locals. + for item in &s.items { + for (exported, local) in &item.exports { + if d.whole || d.names.contains(exported) { + pending.push(local.clone()); + } + } + } + let mut by_name: HashMap<&str, Vec> = HashMap::new(); + for (i, item) in s.items.iter().enumerate() { + for name in &item.declares { + by_name.entry(name).or_default().push(i); + } + } + while let Some(name) = pending.pop() { + if !names.insert(name.clone()) { + continue; + } + if let Some(is) = by_name.get(name.as_str()) { + for &i in is { + keep(i, &mut items, &mut pending); + } + } + } + // An import item's own mentions are its locals: they live only through + // other items, so an import keeps nothing by itself. + (items, names) +} + +/// A condition of an inert item, checked against the graph. +fn holds( + cond: &Cond, + p: &PathBuf, + s: &Summary, + m: &Module, + modules: &HashMap, + scc: &HashMap, +) -> bool { + let local = match cond { + Cond::Initialized(l) | Cond::Constructor(l) => l, + }; + // The import item binding `local`, and its target. + let Some((index, imported)) = s.items.iter().enumerate().find_map(|(i, item)| { + item.imports + .iter() + .find(|(l, _)| l == local) + .map(|(_, imported)| (i, imported.clone())) + }) else { + return false; + }; + let Some(target) = m.targets.get(&index) else { + return false; + }; + if scc.get(target) == scc.get(p) { + return false; + } + match cond { + Cond::Initialized(_) => true, + Cond::Constructor(_) => match imported { + Some(name) => exports_constructor(target, &name, modules, 0), + None => false, + }, + } +} + +/// `name` exported by `module` is a class or function declaration. +fn exports_constructor( + module: &Path, + name: &str, + modules: &HashMap, + depth: u32, +) -> bool { + if depth > 32 { + return false; + } + let Some(m) = modules.get(module) else { + return false; + }; + let Some(s) = m.summary else { return false }; + for (i, item) in s.items.iter().enumerate() { + if let Some((_, local)) = item.exports.iter().find(|(e, _)| e == name) { + return s.constructors.contains(local) + && !s + .items + .iter() + .any(|it| it.imports.iter().any(|(l, _)| l == local)); + } + if let Some((_, Some(imported))) = item.reexports.iter().find(|(e, _)| e == name) { + return m + .targets + .get(&i) + .is_some_and(|t| exports_constructor(t, imported, modules, depth + 1)); + } + } + false +} + +/// The specifier names a compiled module that the graph does not hold. +fn compiled_specifier( + ctx: &mut CompilationContext, + importer: &Path, + source: &str, + key_set: &HashSet, +) -> bool { + let _ = key_set; + match super::super::resolve::cached_resolve_import(source, importer, ctx) { + Some((p, _)) => { + let ext = p.extension().and_then(|e| e.to_str()).unwrap_or(""); + matches!( + ext, + "ts" | "tsx" | "mts" | "cts" | "js" | "jsx" | "mjs" | "cjs" + ) + } + None => false, + } +} + +/// Strongly connected components of the module graph (iterative Tarjan). +fn components(paths: &[PathBuf], modules: &HashMap) -> HashMap { + let index_of: HashMap<&PathBuf, usize> = + paths.iter().enumerate().map(|(i, p)| (p, i)).collect(); + let succ: Vec> = paths + .iter() + .map(|p| { + let m = &modules[p]; + m.targets + .values() + .chain(m.whole_targets.iter()) + .chain(m.named_targets.iter().map(|(t, _)| t)) + .filter_map(|t| index_of.get(t).copied()) + .collect() + }) + .collect(); + let n = paths.len(); + let (mut index, mut low, mut on) = (vec![usize::MAX; n], vec![0usize; n], vec![false; n]); + let mut comp = vec![usize::MAX; n]; + let (mut next, mut ncomp) = (0usize, 0usize); + let mut stack: Vec = Vec::new(); + for root in 0..n { + if index[root] != usize::MAX { + continue; + } + let mut call: Vec<(usize, usize)> = vec![(root, 0)]; + index[root] = next; + low[root] = next; + next += 1; + stack.push(root); + on[root] = true; + while let Some(&mut (v, ref mut k)) = call.last_mut() { + if *k < succ[v].len() { + let w = succ[v][*k]; + *k += 1; + if index[w] == usize::MAX { + index[w] = next; + low[w] = next; + next += 1; + stack.push(w); + on[w] = true; + call.push((w, 0)); + } else if on[w] { + low[v] = low[v].min(index[w]); + } + } else { + call.pop(); + if let Some(&(u, _)) = call.last() { + low[u] = low[u].min(low[v]); + } + if low[v] == index[v] { + while let Some(w) = stack.pop() { + on[w] = false; + comp[w] = ncomp; + if w == v { + break; + } + } + ncomp += 1; + } + } + } + } + paths.iter().cloned().zip(comp).collect() +} diff --git a/crates/perry/src/commands/compile/export_demand/prune.rs b/crates/perry/src/commands/compile/export_demand/prune.rs new file mode 100644 index 00000000000..b7f77ea1338 --- /dev/null +++ b/crates/perry/src/commands/compile/export_demand/prune.rs @@ -0,0 +1,73 @@ +//! Remove what the plan found dead from a module's AST before lowering. +//! Every import and re-export edge stays (as a bare `import 'x'` when no +//! binding of it is used), so evaluation order is unchanged. + +use swc_ecma_ast as ast; + +use super::plan::Prune; +use super::summary::export_name; + +pub(crate) fn prune(module: &ast::Module, plan: &Prune) -> ast::Module { + let mut out = module.clone(); + out.body = module + .body + .iter() + .enumerate() + .filter_map(|(index, item)| { + if plan.items.contains(&index) { + return None; + } + match item { + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(import)) + if !import.type_only => + { + let mut import = import.clone(); + import.specifiers.retain(|s| match s { + ast::ImportSpecifier::Named(n) if n.is_type_only => true, + ast::ImportSpecifier::Named(n) => !plan.imports.contains(&*n.local.sym), + ast::ImportSpecifier::Default(d) => !plan.imports.contains(&*d.local.sym), + ast::ImportSpecifier::Namespace(n) => !plan.imports.contains(&*n.local.sym), + }); + Some(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(import))) + } + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed(named)) + if !named.type_only => + { + let mut named = named.clone(); + named.specifiers.retain(|s| match s { + ast::ExportSpecifier::Named(n) if n.is_type_only => true, + ast::ExportSpecifier::Named(n) => { + let exported = n.exported.as_ref().unwrap_or(&n.orig); + !plan.exports.contains(&export_name(exported)) + } + ast::ExportSpecifier::Namespace(n) => { + !plan.exports.contains(&export_name(&n.name)) + } + ast::ExportSpecifier::Default(d) => { + !plan.exports.contains(&*d.exported.sym) + } + }); + if !named.specifiers.is_empty() { + return Some(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed( + named, + ))); + } + // A re-export still evaluates its source: keep the edge. + let src = named.src?; + Some(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import( + ast::ImportDecl { + span: named.span, + specifiers: Vec::new(), + src, + type_only: false, + with: named.with, + phase: ast::ImportPhase::Evaluation, + }, + ))) + } + _ => Some(item.clone()), + } + }) + .collect(); + out +} diff --git a/crates/perry/src/commands/compile/export_demand/summary.rs b/crates/perry/src/commands/compile/export_demand/summary.rs new file mode 100644 index 00000000000..73d28fba3d5 --- /dev/null +++ b/crates/perry/src/commands/compile/export_demand/summary.rs @@ -0,0 +1,795 @@ +//! What one module's top-level items declare, mention and evaluate, read +//! from its AST as lowering receives it. A mention is any identifier naming +//! a module-scope binding, type positions and shadowing locals included: a +//! mention too many keeps a declaration, never drops one. + +use std::collections::{BTreeSet, HashSet}; + +use swc_ecma_ast as ast; +use swc_ecma_visit::{Visit, VisitWith}; + +/// One module's summary. +#[derive(Debug, Clone, Default)] +pub(crate) struct Summary { + /// The item count and spans the plan was made for: the second walk + /// prunes only the same body. + pub fingerprint: u64, + /// False when any binding can be read by name at run time (direct + /// `eval`, `with`) or the module is not an ES module (`export =`, + /// a CommonJS wrap): every item stays and every import is used whole. + pub prunable: bool, + pub items: Vec, + /// Locals whose value is an ordinary `function` that reads `this`: a + /// namespace call `ns.f()` passes the namespace as `this`. + pub this_reading: HashSet, + /// Locals declared only by class or function declarations: valid + /// `extends` targets once initialized. + pub constructors: HashSet, +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct Item { + pub kind: Kind, + /// Module-scope bindings this item creates. + pub declares: Vec, + /// Module-scope bindings this item mentions. + pub mentions: BTreeSet, + /// `ns.member` reads of a namespace import, as (namespace, member). + pub members: BTreeSet<(String, String)>, + /// Namespace imports this item uses other than as `ns.member`. + pub escapes: BTreeSet, + pub eval: Eval, + /// (exported, local) for a local export; `default` is the local of an + /// anonymous default export. + pub exports: Vec<(String, String)>, + /// (exported, imported) of `export { a as b } from`, imported `None` + /// for `export * as b from`. + pub reexports: Vec<(String, Option)>, + /// The specifier source of an import or re-export. + pub source: Option, + /// (local, imported) of an import; imported `None` for a namespace. + pub imports: Vec<(String, Option)>, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) enum Kind { + /// A declaration, a default export or a statement. + #[default] + Other, + Import, + /// `export { a, b as c }` without a source. + ExportLocal, + /// `export { a } from` / `export * as ns from`. + ReExport, + /// `export * from`. + ExportAll, + /// No runtime effect at all (types, `declare`, type-only edges). + Erased, +} + +/// What evaluating an item can do. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) enum Eval { + /// May run user code, read an unknown binding or throw: always kept. + #[default] + Effect, + /// Creates its bindings and nothing else, given the conditions on + /// imported bindings (checked against the module graph). + Inert(Vec), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Cond { + /// The imported binding is initialized when this item runs: its module + /// finished evaluating first (it is not in this module's import cycle). + Initialized(String), + /// Also, it is a class or an ordinary function (a valid `extends`). + Constructor(String), +} + +pub(crate) fn summarize(module: &ast::Module, cjs_wrapped: bool) -> Summary { + let mut scope: HashSet = HashSet::new(); + let mut kinds: Vec = Vec::new(); + for (index, item) in module.body.iter().enumerate() { + for (name, decl) in declared(item) { + scope.insert(name.clone()); + let class = declares_class(item); + kinds.push(Declared { + name, + decl, + at: index, + class, + }); + } + } + let mut runtime_names = RuntimeNames::default(); + module.visit_with(&mut runtime_names); + let mut prunable = !cjs_wrapped && !runtime_names.found; + let mut this_reading = HashSet::new(); + let mut items = Vec::with_capacity(module.body.len()); + for (index, item) in module.body.iter().enumerate() { + let mut out = Item::default(); + let mut mentions = Mentions::new(&scope); + item.visit_with(&mut mentions); + out.mentions = mentions.names; + out.members = mentions.members; + out.escapes = mentions.escapes; + out.declares = declared(item).into_iter().map(|(n, _)| n).collect(); + let order = Order { + kinds: &kinds, + at: index, + }; + match item { + ast::ModuleItem::ModuleDecl(decl) => match decl { + ast::ModuleDecl::Import(import) => { + out.kind = if import.type_only { + Kind::Erased + } else { + Kind::Import + }; + out.source = Some(import.src.value.to_string_lossy().into_owned()); + out.imports = import_locals(import); + out.eval = Eval::Inert(Vec::new()); + } + ast::ModuleDecl::ExportDecl(export) => { + out.eval = decl_eval(&export.decl, &order); + for name in &out.declares { + out.exports.push((name.clone(), name.clone())); + } + if matches!( + &export.decl, + ast::Decl::TsInterface(_) | ast::Decl::TsTypeAlias(_) + ) { + out.kind = Kind::Erased; + } + note_this(&export.decl, &mut this_reading); + } + ast::ModuleDecl::ExportNamed(named) => { + out.eval = Eval::Inert(Vec::new()); + if named.type_only { + out.kind = Kind::Erased; + } else if let Some(src) = &named.src { + out.kind = Kind::ReExport; + out.source = Some(src.value.to_string_lossy().into_owned()); + out.reexports = reexported(named); + } else { + out.kind = Kind::ExportLocal; + for spec in &named.specifiers { + if let ast::ExportSpecifier::Named(spec) = spec { + if spec.is_type_only { + continue; + } + let local = export_name(&spec.orig); + let exported = spec + .exported + .as_ref() + .map_or_else(|| local.clone(), export_name); + out.exports.push((exported, local)); + } + } + } + } + ast::ModuleDecl::ExportAll(all) => { + out.kind = if all.type_only { + Kind::Erased + } else { + Kind::ExportAll + }; + out.source = Some(all.src.value.to_string_lossy().into_owned()); + out.eval = Eval::Inert(Vec::new()); + } + ast::ModuleDecl::ExportDefaultDecl(default) => { + let local = out + .declares + .first() + .cloned() + .unwrap_or_else(|| "default".into()); + out.exports.push(("default".into(), local.clone())); + out.eval = match &default.decl { + ast::DefaultDecl::Fn(f) => { + if function_reads_this(&f.function) { + this_reading.insert(local); + } + fn_eval(&f.function) + } + ast::DefaultDecl::Class(c) => class_eval(&c.class, &order), + ast::DefaultDecl::TsInterfaceDecl(_) => { + out.kind = Kind::Erased; + Eval::Inert(Vec::new()) + } + }; + if out.declares.is_empty() { + out.declares.push("default".into()); + } + } + ast::ModuleDecl::ExportDefaultExpr(default) => { + out.exports.push(("default".into(), "default".into())); + out.declares.push("default".into()); + out.eval = expr_eval(&default.expr, &order); + } + ast::ModuleDecl::TsImportEquals(_) + | ast::ModuleDecl::TsExportAssignment(_) + | ast::ModuleDecl::TsNamespaceExport(_) => { + prunable = false; + } + }, + ast::ModuleItem::Stmt(stmt) => match stmt { + ast::Stmt::Decl(decl) => { + out.eval = decl_eval(decl, &order); + if matches!(decl, ast::Decl::TsInterface(_) | ast::Decl::TsTypeAlias(_)) { + out.kind = Kind::Erased; + } + note_this(decl, &mut this_reading); + } + ast::Stmt::Empty(_) => out.eval = Eval::Inert(Vec::new()), + ast::Stmt::Expr(e) if matches!(&*e.expr, ast::Expr::Lit(ast::Lit::Str(_))) => { + // A directive prologue entry: kept, it is not a binding. + } + _ => {} + }, + } + // An import or an export list names bindings without reading + // them: they live only through the items that do and the demand. + if matches!( + out.kind, + Kind::Import | Kind::ExportLocal | Kind::ReExport | Kind::ExportAll + ) { + out.mentions.clear(); + out.members.clear(); + out.escapes.clear(); + } + items.push(out); + } + let constructors = kinds + .iter() + .filter(|d| { + kinds + .iter() + .filter(|o| o.name == d.name) + .all(|o| o.class || o.decl == Decl::Function) + }) + .map(|d| d.name.clone()) + .collect(); + Summary { + fingerprint: fingerprint(module), + prunable, + items, + this_reading, + constructors, + } +} + +/// The item count and every item's span. +pub(crate) fn fingerprint(module: &ast::Module) -> u64 { + use std::hash::{Hash, Hasher}; + use swc_common::Spanned; + let mut h = std::collections::hash_map::DefaultHasher::new(); + module.body.len().hash(&mut h); + for item in &module.body { + let span = item.span(); + (span.lo.0, span.hi.0).hash(&mut h); + } + h.finish() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Decl { + /// A hoisted function: initialized before any item runs. + Function, + /// A class, `let`, `const` or `var`: initialized by its item. + Lexical, + Import, +} + +/// A module-scope binding: how it is initialized, by which item. +struct Declared { + name: String, + decl: Decl, + at: usize, + class: bool, +} + +fn declares_class(item: &ast::ModuleItem) -> bool { + matches!( + item, + ast::ModuleItem::Stmt(ast::Stmt::Decl(ast::Decl::Class(_))) + | ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(ast::ExportDecl { + decl: ast::Decl::Class(_), + .. + })) + ) || matches!( + item, + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDefaultDecl(ast::ExportDefaultDecl { + decl: ast::DefaultDecl::Class(_), + .. + })) + ) +} + +fn declared(item: &ast::ModuleItem) -> Vec<(String, Decl)> { + let mut out = Vec::new(); + match item { + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(import)) => { + if !import.type_only { + for (local, _) in import_locals(import) { + out.push((local, Decl::Import)); + } + } + } + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(export)) => { + decl_names(&export.decl, &mut out) + } + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDefaultDecl(default)) => { + match &default.decl { + ast::DefaultDecl::Fn(f) => { + if let Some(id) = &f.ident { + out.push((id.sym.to_string(), Decl::Function)); + } + } + ast::DefaultDecl::Class(c) => { + if let Some(id) = &c.ident { + out.push((id.sym.to_string(), Decl::Lexical)); + } + } + ast::DefaultDecl::TsInterfaceDecl(_) => {} + } + } + ast::ModuleItem::Stmt(ast::Stmt::Decl(decl)) => decl_names(decl, &mut out), + _ => {} + } + out +} + +fn decl_names(decl: &ast::Decl, out: &mut Vec<(String, Decl)>) { + match decl { + ast::Decl::Fn(f) => out.push((f.ident.sym.to_string(), Decl::Function)), + ast::Decl::Class(c) => out.push((c.ident.sym.to_string(), Decl::Lexical)), + ast::Decl::Var(var) => { + for d in &var.decls { + let mut names = PatNames::default(); + d.name.visit_with(&mut names); + out.extend(names.0.into_iter().map(|n| (n, Decl::Lexical))); + } + } + ast::Decl::TsEnum(e) => out.push((e.id.sym.to_string(), Decl::Lexical)), + ast::Decl::TsModule(m) => { + if let ast::TsModuleName::Ident(id) = &m.id { + out.push((id.sym.to_string(), Decl::Lexical)); + } + } + ast::Decl::Using(using) => { + for d in &using.decls { + let mut names = PatNames::default(); + d.name.visit_with(&mut names); + out.extend(names.0.into_iter().map(|n| (n, Decl::Lexical))); + } + } + ast::Decl::TsInterface(_) | ast::Decl::TsTypeAlias(_) => {} + } +} + +#[derive(Default)] +struct PatNames(Vec); +impl Visit for PatNames { + fn visit_binding_ident(&mut self, id: &ast::BindingIdent) { + self.0.push(id.id.sym.to_string()); + } + // Default values and computed keys are expressions, not bindings. + fn visit_expr(&mut self, _: &ast::Expr) {} + fn visit_prop_name(&mut self, _: &ast::PropName) {} +} + +fn import_locals(import: &ast::ImportDecl) -> Vec<(String, Option)> { + import + .specifiers + .iter() + .filter_map(|spec| match spec { + ast::ImportSpecifier::Named(named) if !named.is_type_only => Some(( + named.local.sym.to_string(), + Some( + named + .imported + .as_ref() + .map_or_else(|| named.local.sym.to_string(), export_name), + ), + )), + ast::ImportSpecifier::Named(_) => None, + ast::ImportSpecifier::Default(d) => { + Some((d.local.sym.to_string(), Some("default".into()))) + } + ast::ImportSpecifier::Namespace(ns) => Some((ns.local.sym.to_string(), None)), + }) + .collect() +} + +fn reexported(named: &ast::NamedExport) -> Vec<(String, Option)> { + named + .specifiers + .iter() + .filter_map(|spec| match spec { + ast::ExportSpecifier::Named(spec) if !spec.is_type_only => { + let imported = export_name(&spec.orig); + let exported = spec + .exported + .as_ref() + .map_or_else(|| imported.clone(), export_name); + Some((exported, Some(imported))) + } + ast::ExportSpecifier::Named(_) => None, + ast::ExportSpecifier::Namespace(ns) => Some((export_name(&ns.name), None)), + ast::ExportSpecifier::Default(d) => { + Some((d.exported.sym.to_string(), Some("default".into()))) + } + }) + .collect() +} + +pub(crate) fn export_name(name: &ast::ModuleExportName) -> String { + match name { + ast::ModuleExportName::Ident(id) => id.sym.to_string(), + ast::ModuleExportName::Str(s) => s.value.to_string_lossy().into_owned(), + } +} + +/// Direct `eval` and `with` read bindings by a name only known at run time. +#[derive(Default)] +struct RuntimeNames { + found: bool, +} +impl Visit for RuntimeNames { + fn visit_call_expr(&mut self, call: &ast::CallExpr) { + if let ast::Callee::Expr(callee) = &call.callee { + if matches!(&**callee, ast::Expr::Ident(id) if &*id.sym == "eval") { + self.found = true; + } + } + call.visit_children_with(self); + } + fn visit_with_stmt(&mut self, _: &ast::WithStmt) { + self.found = true; + } +} + +/// Every module-scope name an item mentions; namespace imports used only as +/// `ns.member` (value or type) are recorded by member instead. +struct Mentions<'a> { + scope: &'a HashSet, + names: BTreeSet, + members: BTreeSet<(String, String)>, + escapes: BTreeSet, +} + +impl<'a> Mentions<'a> { + fn new(scope: &'a HashSet) -> Self { + Self { + scope, + names: BTreeSet::new(), + members: BTreeSet::new(), + escapes: BTreeSet::new(), + } + } + fn ident(&mut self, sym: &str) { + if self.scope.contains(sym) { + self.names.insert(sym.to_string()); + self.escapes.insert(sym.to_string()); + } + } + fn member(&mut self, ns: &str, member: &str) { + if self.scope.contains(ns) { + self.names.insert(ns.to_string()); + self.members.insert((ns.to_string(), member.to_string())); + } + } +} + +impl Visit for Mentions<'_> { + fn visit_ident(&mut self, id: &ast::Ident) { + self.ident(&id.sym); + } + fn visit_member_expr(&mut self, m: &ast::MemberExpr) { + if let (ast::Expr::Ident(obj), ast::MemberProp::Ident(prop)) = (&*m.obj, &m.prop) { + self.member(&obj.sym, &prop.sym); + return; + } + m.visit_children_with(self); + } + fn visit_ts_qualified_name(&mut self, q: &ast::TsQualifiedName) { + if let ast::TsEntityName::Ident(left) = &q.left { + self.member(&left.sym, &q.right.sym); + return; + } + q.visit_children_with(self); + } + fn visit_jsx_member_expr(&mut self, m: &ast::JSXMemberExpr) { + if let ast::JSXObject::Ident(obj) = &m.obj { + self.member(&obj.sym, &m.prop.sym); + return; + } + m.visit_children_with(self); + } +} + +/// What the plan needs to order an item's reads: which names are hoisted +/// functions, which are declared by an earlier item, which are imports. +struct Order<'a> { + kinds: &'a [Declared], + at: usize, +} + +impl Order<'_> { + fn decl(&self, name: &str) -> Option { + self.kinds.iter().find(|d| d.name == name).map(|d| d.decl) + } + /// Every declaration of `name` is an item before this one. + fn earlier(&self, name: &str) -> bool { + self.kinds + .iter() + .filter(|d| d.name == name) + .all(|d| d.at < self.at) + } + /// `name` is a class declaration (and nothing else). + fn class(&self, name: &str) -> bool { + self.kinds + .iter() + .filter(|d| d.name == name) + .all(|d| d.class) + } +} + +fn decl_eval(decl: &ast::Decl, order: &Order) -> Eval { + match decl { + ast::Decl::Fn(f) => fn_eval(&f.function), + ast::Decl::Class(c) => class_eval(&c.class, order), + ast::Decl::Var(var) => { + if var.declare { + return Eval::Inert(Vec::new()); + } + let mut conds = Vec::new(); + for d in &var.decls { + // Destructuring can run iterators and getters. + if !matches!(d.name, ast::Pat::Ident(_)) { + return Eval::Effect; + } + if let Some(init) = &d.init { + match expr_eval(init, order) { + Eval::Inert(c) => conds.extend(c), + Eval::Effect => return Eval::Effect, + } + } + } + Eval::Inert(conds) + } + ast::Decl::TsInterface(_) | ast::Decl::TsTypeAlias(_) => Eval::Inert(Vec::new()), + ast::Decl::TsEnum(e) => { + if e.declare { + return Eval::Inert(Vec::new()); + } + let literal = e.members.iter().all(|m| { + m.init.as_deref().is_none_or(|i| { + matches!(i, ast::Expr::Lit(ast::Lit::Num(_) | ast::Lit::Str(_))) + }) + }); + if literal { + Eval::Inert(Vec::new()) + } else { + Eval::Effect + } + } + ast::Decl::TsModule(m) if m.declare => Eval::Inert(Vec::new()), + ast::Decl::TsModule(_) | ast::Decl::Using(_) => Eval::Effect, + } +} + +fn fn_eval(f: &ast::Function) -> Eval { + // Parameters and the body run on a call; decorators would run now. + if f.decorators.is_empty() && f.params.iter().all(|p| p.decorators.is_empty()) { + Eval::Inert(Vec::new()) + } else { + Eval::Effect + } +} + +fn class_eval(class: &ast::Class, order: &Order) -> Eval { + if !class.decorators.is_empty() { + return Eval::Effect; + } + let mut conds = Vec::new(); + if let Some(sup) = &class.super_class { + match &**sup { + ast::Expr::Lit(ast::Lit::Null(_)) => {} + ast::Expr::Ident(id) => match order.decl(&id.sym) { + Some(Decl::Function) => {} + Some(Decl::Lexical) if order.earlier(&id.sym) && order.class(&id.sym) => {} + Some(Decl::Import) => conds.push(Cond::Constructor(id.sym.to_string())), + _ => return Eval::Effect, + }, + _ => return Eval::Effect, + } + } + for member in &class.body { + let inert = match member { + ast::ClassMember::Constructor(c) => c.params.iter().all(|p| match p { + ast::ParamOrTsParamProp::Param(p) => p.decorators.is_empty(), + ast::ParamOrTsParamProp::TsParamProp(p) => p.decorators.is_empty(), + }), + ast::ClassMember::Method(m) => { + key_is_literal(&m.key) + && m.function.decorators.is_empty() + && m.function.params.iter().all(|p| p.decorators.is_empty()) + } + ast::ClassMember::PrivateMethod(m) => { + m.function.decorators.is_empty() + && m.function.params.iter().all(|p| p.decorators.is_empty()) + } + ast::ClassMember::ClassProp(p) => { + key_is_literal(&p.key) + && p.decorators.is_empty() + && (!p.is_static + || p.value + .as_deref() + .is_none_or(|v| match expr_eval(v, order) { + Eval::Inert(c) => { + conds.extend(c); + true + } + Eval::Effect => false, + })) + } + ast::ClassMember::PrivateProp(p) => { + p.decorators.is_empty() + && (!p.is_static + || p.value + .as_deref() + .is_none_or(|v| match expr_eval(v, order) { + Eval::Inert(c) => { + conds.extend(c); + true + } + Eval::Effect => false, + })) + } + ast::ClassMember::TsIndexSignature(_) | ast::ClassMember::Empty(_) => true, + ast::ClassMember::StaticBlock(_) => false, + ast::ClassMember::AutoAccessor(a) => { + let key_ok = match &a.key { + ast::Key::Private(_) => true, + ast::Key::Public(k) => key_is_literal(k), + }; + key_ok && a.decorators.is_empty() && (!a.is_static || a.value.is_none()) + } + }; + if !inert { + return Eval::Effect; + } + } + Eval::Inert(conds) +} + +fn key_is_literal(key: &ast::PropName) -> bool { + match key { + ast::PropName::Ident(_) | ast::PropName::Str(_) | ast::PropName::Num(_) => true, + ast::PropName::Computed(c) => matches!( + &*c.expr, + ast::Expr::Lit(ast::Lit::Str(_) | ast::Lit::Num(_)) + ), + ast::PropName::BigInt(_) => true, + } +} + +/// Evaluating `expr` creates a value and nothing else. +fn expr_eval(expr: &ast::Expr, order: &Order) -> Eval { + let mut conds = Vec::new(); + if inert_expr(expr, order, &mut conds) { + Eval::Inert(conds) + } else { + Eval::Effect + } +} + +fn inert_expr(expr: &ast::Expr, order: &Order, conds: &mut Vec) -> bool { + match expr { + ast::Expr::Lit(lit) => !matches!(lit, ast::Lit::JSXText(_)), + ast::Expr::Fn(f) => matches!(fn_eval(&f.function), Eval::Inert(_)), + ast::Expr::Arrow(_) => true, + ast::Expr::Class(c) => match class_eval(&c.class, order) { + Eval::Inert(c) => { + conds.extend(c); + true + } + Eval::Effect => false, + }, + ast::Expr::Tpl(t) => t.exprs.is_empty(), + ast::Expr::Paren(p) => inert_expr(&p.expr, order, conds), + ast::Expr::TsAs(e) => inert_expr(&e.expr, order, conds), + ast::Expr::TsSatisfies(e) => inert_expr(&e.expr, order, conds), + ast::Expr::TsTypeAssertion(e) => inert_expr(&e.expr, order, conds), + ast::Expr::TsConstAssertion(e) => inert_expr(&e.expr, order, conds), + ast::Expr::TsNonNull(e) => inert_expr(&e.expr, order, conds), + ast::Expr::Array(a) => a + .elems + .iter() + .flatten() + .all(|e| e.spread.is_none() && inert_expr(&e.expr, order, conds)), + ast::Expr::Object(o) => o.props.iter().all(|p| match p { + ast::PropOrSpread::Spread(_) => false, + ast::PropOrSpread::Prop(p) => match &**p { + ast::Prop::KeyValue(kv) => { + key_is_literal(&kv.key) && inert_expr(&kv.value, order, conds) + } + ast::Prop::Shorthand(id) => inert_read(&id.sym, order, conds), + ast::Prop::Method(m) => key_is_literal(&m.key), + ast::Prop::Getter(g) => key_is_literal(&g.key), + ast::Prop::Setter(s) => key_is_literal(&s.key), + ast::Prop::Assign(_) => false, + }, + }), + ast::Expr::Ident(id) => inert_read(&id.sym, order, conds), + ast::Expr::Unary(u) + if matches!( + u.op, + ast::UnaryOp::TypeOf | ast::UnaryOp::Void | ast::UnaryOp::Bang + ) => + { + inert_expr(&u.arg, order, conds) + } + ast::Expr::Unary(u) if matches!(u.op, ast::UnaryOp::Minus | ast::UnaryOp::Plus) => { + matches!(&*u.arg, ast::Expr::Lit(ast::Lit::Num(_))) + } + ast::Expr::Cond(c) => { + inert_expr(&c.test, order, conds) + && inert_expr(&c.cons, order, conds) + && inert_expr(&c.alt, order, conds) + } + ast::Expr::Seq(s) => s.exprs.iter().all(|e| inert_expr(e, order, conds)), + // Even a member read can call a getter; calls and `new` run code. + _ => false, + } +} + +/// Reading `name` here cannot throw: a hoisted function, a binding an +/// earlier item initialized, an import whose module ran first, or the +/// global `undefined`. +fn inert_read(name: &str, order: &Order, conds: &mut Vec) -> bool { + match order.decl(name) { + Some(Decl::Function) => true, + Some(Decl::Lexical) => order.earlier(name), + Some(Decl::Import) => { + conds.push(Cond::Initialized(name.to_string())); + true + } + None => name == "undefined", + } +} + +fn note_this(decl: &ast::Decl, out: &mut HashSet) { + match decl { + ast::Decl::Fn(f) if function_reads_this(&f.function) => { + out.insert(f.ident.sym.to_string()); + } + ast::Decl::Var(var) => { + for d in &var.decls { + if let (ast::Pat::Ident(id), Some(init)) = (&d.name, &d.init) { + if let ast::Expr::Fn(f) = &**init { + if function_reads_this(&f.function) { + out.insert(id.id.sym.to_string()); + } + } + } + } + } + _ => {} + } +} + +fn function_reads_this(f: &ast::Function) -> bool { + #[derive(Default)] + struct This(bool); + impl Visit for This { + fn visit_this_expr(&mut self, _: &ast::ThisExpr) { + self.0 = true; + } + } + let mut v = This::default(); + f.visit_with(&mut v); + v.0 +} diff --git a/crates/perry/src/commands/compile/export_demand/tests.rs b/crates/perry/src/commands/compile/export_demand/tests.rs new file mode 100644 index 00000000000..fbe2be356c3 --- /dev/null +++ b/crates/perry/src/commands/compile/export_demand/tests.rs @@ -0,0 +1,116 @@ +use super::summary::{summarize, Eval, Kind}; + +fn parse(src: &str) -> swc_ecma_ast::Module { + perry_parser::parse_typescript(src, "fixture.ts").expect("parses") +} + +fn inert(src: &str) -> Vec { + summarize(&parse(src), false) + .items + .iter() + .map(|i| matches!(i.eval, Eval::Inert(_))) + .collect() +} + +#[test] +fn declarations_literals_and_inert_classes_create_bindings_only() { + for src in [ + "export function f(x = effect()) { return effect(x); }", + "export const a = 1, b = 'x', c = [1, { k: null }], d = () => effect();", + "export class C { m() { effect(); } static s = 1; x = effect(); get g() { return 1; } }", + "function base() {} export class D extends base {}", + "class A {} export class B extends A { static #p = 2; }", + "export const o = { m() { return effect(); }, get g() { return 1; } };", + "export enum E { A = 1, B = 'b', C }", + "export default function () { throw new Error(); }", + "export const t = typeof undefined;", + "export interface I { x: number }", + ] { + let s = inert(src); + assert!(s.iter().all(|x| *x), "{src}: {s:?}"); + } +} + +#[test] +fn anything_that_can_run_code_or_throw_is_kept() { + for (src, at) in [ + ("export const a = effect();", 0), + ("export const a = missing;", 0), + ("export const a = o.getter;", 0), + ("export const { a } = o;", 0), + ("export const a = [...xs];", 0), + ("export const a = { ...o };", 0), + ("export const a = { [k]: 1 };", 0), + ("export class C { static { effect(); } }", 0), + ("export class C { static s = effect(); }", 0), + ("export class C extends effect() {}", 0), + ("export class C extends Later {} class Later {}", 0), + ("export class C { [k]() {} }", 0), + ("@dec export class C {}", 0), + ("export const a = `x${y}`;", 0), + ("export enum E { A = f() }", 0), + ("console.log(1);", 0), + ("const b = a; const a = 1;", 0), + ] { + let s = inert(src); + assert!(!s[at], "{src}: {s:?}"); + } +} + +#[test] +fn imported_reads_wait_for_the_graph() { + let s = summarize( + &parse("import { B } from './b'; export class C extends B {} export const x = B;"), + false, + ); + match &s.items[1].eval { + Eval::Inert(conds) => assert_eq!(conds.len(), 1), + other => panic!("{other:?}"), + } + match &s.items[2].eval { + Eval::Inert(conds) => assert_eq!(conds.len(), 1), + other => panic!("{other:?}"), + } +} + +#[test] +fn mentions_follow_names_and_namespace_members() { + let s = summarize( + &parse("import * as ns from './n'; import { a } from './a'; export const f = () => ns.x + a; export const g = () => ns;"), + false, + ); + assert_eq!(s.items[0].kind, Kind::Import); + assert!(s.items[0].mentions.is_empty()); + assert!(s.items[2].mentions.contains("a")); + assert!(s.items[2] + .members + .contains(&("ns".to_string(), "x".to_string()))); + assert!(!s.items[2].escapes.contains("ns")); + assert!(s.items[3].escapes.contains("ns")); +} + +#[test] +fn eval_and_with_keep_the_module_whole() { + assert!(!summarize(&parse("export const a = 1; eval('a');"), false).prunable); + assert!(summarize(&parse("export const a = 1; (0, eval)('a');"), false).prunable); + assert!(!summarize(&parse("export const a = 1;"), true).prunable); +} + +#[test] +fn prune_keeps_every_edge() { + let module = parse( + "import { a, b } from './a'; export { c } from './c'; export { d as e } from './d'; export const x = a; export const y = 2;", + ); + let s = summarize(&module, false); + let plan = super::Prune { + fingerprint: s.fingerprint, + items: [4].into_iter().collect(), + imports: ["b".to_string()].into_iter().collect(), + exports: ["c".to_string()].into_iter().collect(), + }; + let out = super::prune::prune(&module, &plan); + assert_eq!(out.body.len(), 4); + let text = format!("{:?}", out.body); + assert!(!text.contains("\"y\""), "{text}"); + assert!(text.contains("\"./c\""), "the edge stays: {text}"); +} diff --git a/crates/perry/src/commands/compile/reachability.rs b/crates/perry/src/commands/compile/reachability.rs index 6e3eadb1233..b9073918ddd 100644 --- a/crates/perry/src/commands/compile/reachability.rs +++ b/crates/perry/src/commands/compile/reachability.rs @@ -502,7 +502,7 @@ fn rewrite_pruned_edges(ctx: &mut CompilationContext, pruned: &HashSet) /// Resolve an import/export edge to its canonical `native_modules` key, if the /// target is a compiled module present in the graph. Prefers the precomputed /// `resolved_path` (set during collection), falling back to the resolver. -fn resolve_edge( +pub(super) fn resolve_edge( ctx: &mut CompilationContext, importer: &Path, resolved_path: Option<&str>, diff --git a/crates/perry/src/commands/compile/types.rs b/crates/perry/src/commands/compile/types.rs index 8d867151a28..905b8566dab 100644 --- a/crates/perry/src/commands/compile/types.rs +++ b/crates/perry/src/commands/compile/types.rs @@ -1267,6 +1267,8 @@ pub struct CompilationContext { /// Sources (any enables): `PERRY_TREE_SHAKE=1` env, or /// `perry.experiments.treeShake: true` in the host `package.json`. pub tree_shake: bool, + /// Layer-1 dead-code elimination state across the two collection walks. + pub export_demand: super::export_demand::ExportDemand, /// #2309: refusals (`new Function` RuntimeUnknown, #463 unimplemented APIs) /// recorded while lowering `node_modules` modules under tree-shaking, /// instead of hard-erroring. After reachability is computed, any whose @@ -1509,6 +1511,7 @@ impl CompilationContext { allow_native_library: Vec::new(), allow_compile_packages: Vec::new(), tree_shake: false, + export_demand: Default::default(), deferred_refusals: Vec::new(), side_effects_cache: HashMap::new(), define: HashMap::new(), From dfca5900a1e7478b20b17e9a166b0cc0a04ed6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 30 Oct 2026 21:14:33 +0000 Subject: [PATCH 054/144] export demand: a re-export the collection-time pruner postponed stays as it is --- .../compile/collect_modules/reexport_prune.rs | 8 +++ .../commands/compile/export_demand/plan.rs | 54 ++++++++++++++----- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/crates/perry/src/commands/compile/collect_modules/reexport_prune.rs b/crates/perry/src/commands/compile/collect_modules/reexport_prune.rs index 74460865b28..969189f9bc1 100644 --- a/crates/perry/src/commands/compile/collect_modules/reexport_prune.rs +++ b/crates/perry/src/commands/compile/collect_modules/reexport_prune.rs @@ -66,6 +66,14 @@ fn enabled() -> bool { } impl ReexportPruner { + /// `path` is the source of a re-export this pass postponed: nothing + /// demands it, and the walk does not collect it. + pub(crate) fn postponed(&self, path: &Path) -> bool { + self.edges + .iter() + .any(|e| !e.active && (e.source_path == path || e.target == path)) + } + fn demand(&mut self, path: &Path, incoming: Demand) -> bool { let current = self.demands.entry(path.to_owned()).or_default(); if current.all { diff --git a/crates/perry/src/commands/compile/export_demand/plan.rs b/crates/perry/src/commands/compile/export_demand/plan.rs index 17e12e947b3..7756fc4815f 100644 --- a/crates/perry/src/commands/compile/export_demand/plan.rs +++ b/crates/perry/src/commands/compile/export_demand/plan.rs @@ -47,6 +47,9 @@ struct Module<'a> { whole_targets: Vec, /// Plain named HIR imports of a module without a summary. named_targets: Vec<(PathBuf, String)>, + /// Re-export items whose source the collection-time pruner postponed: + /// left exactly as they are. + postponed: HashSet, } pub(crate) struct Planned { @@ -64,6 +67,7 @@ pub(crate) fn plan( let paths: Vec = ctx.native_modules.keys().cloned().collect(); let key_set: HashSet = paths.iter().cloned().collect(); let mut modules: HashMap = HashMap::new(); + let mut unresolved: Vec = Vec::new(); for p in &paths { let hir_imports = ctx.native_modules[p].imports.clone(); let hir_exports = ctx.native_modules[p].exports.clone(); @@ -73,6 +77,7 @@ pub(crate) fn plan( targets: HashMap::new(), whole_targets: Vec::new(), named_targets: Vec::new(), + postponed: HashSet::new(), }; let resolved = |source: &str| { hir_imports @@ -97,7 +102,22 @@ pub(crate) fn plan( Some(t) => { m.targets.insert(index, t); } - None if compiled_specifier(ctx, p, source, &key_set) => return None, + None => match compiled_specifier(ctx, p, source) { + // A re-export the collection-time pruner postponed: + // nothing demands it and its edge is gone from HIR. + Some(t) + if matches!(item.kind, Kind::ReExport | Kind::ExportAll) + && ctx.reexport_pruner.postponed(&t) => + { + m.postponed.insert(index); + } + Some(t) => unresolved.push(format!( + "{} -> {source} -> {}", + p.display(), + t.display() + )), + None => {} + }, None => {} } } @@ -171,6 +191,16 @@ pub(crate) fn plan( modules.insert(p.clone(), m); } + if !unresolved.is_empty() { + eprintln!( + "perry: export demand: {} edges into compiled files outside the graph:", + unresolved.len() + ); + for u in unresolved.iter().take(12) { + eprintln!("perry: export demand: {u}"); + } + return None; + } // Import cycles: an imported binding is initialized when its module is // not in the reader's cycle (it finished evaluating first). let scc = components(&paths, &modules); @@ -370,6 +400,7 @@ pub(crate) fn plan( } } } + Kind::ReExport if m.postponed.contains(&i) => {} Kind::ExportLocal | Kind::ReExport if !d.whole => { for e in item .exports @@ -532,19 +563,14 @@ fn compiled_specifier( ctx: &mut CompilationContext, importer: &Path, source: &str, - key_set: &HashSet, -) -> bool { - let _ = key_set; - match super::super::resolve::cached_resolve_import(source, importer, ctx) { - Some((p, _)) => { - let ext = p.extension().and_then(|e| e.to_str()).unwrap_or(""); - matches!( - ext, - "ts" | "tsx" | "mts" | "cts" | "js" | "jsx" | "mjs" | "cjs" - ) - } - None => false, - } +) -> Option { + let (p, _) = super::super::resolve::cached_resolve_import(source, importer, ctx)?; + let ext = p.extension().and_then(|e| e.to_str()).unwrap_or(""); + matches!( + ext, + "ts" | "tsx" | "mts" | "cts" | "js" | "jsx" | "mjs" | "cjs" + ) + .then(|| p.canonicalize().unwrap_or(p)) } /// Strongly connected components of the module graph (iterative Tarjan). From 2d9877a83c099dcb66529cb8af25f79c1e7d9024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:20:58 +0100 Subject: [PATCH 055/144] changelog: number the PR fragment --- changelog.d/{0-export-demand.md => 13609-export-demand.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-export-demand.md => 13609-export-demand.md} (100%) diff --git a/changelog.d/0-export-demand.md b/changelog.d/13609-export-demand.md similarity index 100% rename from changelog.d/0-export-demand.md rename to changelog.d/13609-export-demand.md From a2fc8dc860a353ec012ff7c2544ab94e4c08cdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 05:55:04 +0000 Subject: [PATCH 056/144] compile env: key PERRY_EXPORT_DEMAND, exclude its report knob --- crates/perry/src/commands/compile/compile_env.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/perry/src/commands/compile/compile_env.rs b/crates/perry/src/commands/compile/compile_env.rs index 91fe45de27b..7fcf3ac0b67 100644 --- a/crates/perry/src/commands/compile/compile_env.rs +++ b/crates/perry/src/commands/compile/compile_env.rs @@ -49,6 +49,7 @@ pub(super) const COMPILE_ENV_VARS: &[&str] = &[ "PERRY_ENTRY_SYMBOL", "PERRY_ENUM_DIAG", "PERRY_EVAL_CSP", + "PERRY_EXPORT_DEMAND", "PERRY_EXTRA_LINK_ARGS", "PERRY_FACTS_BUDGET_SECS", "PERRY_FACTS_ELIDE", @@ -229,6 +230,7 @@ pub(super) fn compile_env_value(name: &str) -> Option { #[cfg(test)] const COMPILE_ENV_EXCLUSIONS: &[(&str, &str)] = &[ + ("PERRY_EXPORT_DEMAND_REPORT", "Writes the export-demand report file only; no pruning decision or emitted code depends on it."), ("PERRY_REALM_REACH", "Compiler-only diagnostic of source facts; changes no emitted bytes, runtime state or archive selection."), ("PERRY_REACH_REPORT", "Compiler-only diagnostic; bypasses object caches and does not change emitted code."), ("PERRY_SOURCE_NEED_REPORT", "Compiler-only retained-source census; bypasses object caches and does not change emitted code."), From 41d31b7a7974075483f2860fbb73698054de2057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 05:58:53 +0000 Subject: [PATCH 057/144] runtime: make atom_bounds visible to the wasm image checks and the Windows bounds test (E0425 on wasi-execution) --- changelog.d/0-wasi-atom-bounds.md | 7 +++++++ crates/perry-runtime/src/object/layout_desc.rs | 2 ++ crates/perry-runtime/src/object/layout_desc/atoms.rs | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelog.d/0-wasi-atom-bounds.md diff --git a/changelog.d/0-wasi-atom-bounds.md b/changelog.d/0-wasi-atom-bounds.md new file mode 100644 index 00000000000..be60f550f6b --- /dev/null +++ b/changelog.d/0-wasi-atom-bounds.md @@ -0,0 +1,7 @@ +Fix the WASI runtime build with the `wasm-image-selftest` feature (the +`wasi-execution` CI job). The oversized-file split moved `atom_bounds` into the +private `layout_desc::atoms` module, so the linked wasm image checks +(`wasm_program_image_checks.rs`) and the Windows atom-bounds test, both +children of `layout_desc`, no longer resolved it (E0425). `atom_bounds` is now +`pub(super)` and imported into `layout_desc` under exactly the cfgs of its two +consumers; native and normal WASI builds are unchanged. diff --git a/crates/perry-runtime/src/object/layout_desc.rs b/crates/perry-runtime/src/object/layout_desc.rs index b00916dd932..7678a0d4907 100644 --- a/crates/perry-runtime/src/object/layout_desc.rs +++ b/crates/perry-runtime/src/object/layout_desc.rs @@ -645,6 +645,8 @@ unsafe fn image_atom_symbol(atom: *const StaticAtom) -> Option<*mut crate::symbo mod atoms; #[cfg(test)] use atoms::image_atoms; +#[cfg(any(all(feature = "wasm-image-selftest", target_family = "wasm"), all(test, target_os = "windows")))] +use atoms::atom_bounds; pub(crate) use atoms::{atom_bytes, coloured_atoms, seed_static_atoms, shadow_name_descriptor}; pub use atoms::StaticAtom; diff --git a/crates/perry-runtime/src/object/layout_desc/atoms.rs b/crates/perry-runtime/src/object/layout_desc/atoms.rs index 9ba3981a09c..ca7c574d4fa 100644 --- a/crates/perry-runtime/src/object/layout_desc/atoms.rs +++ b/crates/perry-runtime/src/object/layout_desc/atoms.rs @@ -41,7 +41,7 @@ pub(crate) fn shadow_name_descriptor() -> &'static StaticAtom { } /// (start, end) of the atom section. -fn atom_bounds() -> (usize, usize) { +pub(super) fn atom_bounds() -> (usize, usize) { std::hint::black_box(std::ptr::addr_of!(ATOM_SECTION_SENTINEL) as usize); #[cfg(target_vendor = "apple")] { From f2e0824c0f97bde60d9491302ef23aca2c77f155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 07:01:13 +0100 Subject: [PATCH 058/144] changelog: number the PR fragment --- changelog.d/{0-wasi-atom-bounds.md => 13616-wasi-atom-bounds.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wasi-atom-bounds.md => 13616-wasi-atom-bounds.md} (100%) diff --git a/changelog.d/0-wasi-atom-bounds.md b/changelog.d/13616-wasi-atom-bounds.md similarity index 100% rename from changelog.d/0-wasi-atom-bounds.md rename to changelog.d/13616-wasi-atom-bounds.md From 11850fc416450999bdaf3f770453c9253f57c393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 09:21:05 +0100 Subject: [PATCH 059/144] wpfacts: a replayed parameter call belongs to its stand-in run (UEB forwarding fix + census) (#13610) --- changelog.d/13610-ueb-forwarded-call-args.md | 1 + crates/perry-wpfacts/src/diagnostics.rs | 34 +- crates/perry-wpfacts/src/gen/export.rs | 9 + crates/perry-wpfacts/src/gen/import_values.rs | 19 + crates/perry-wpfacts/src/model/operations.rs | 5 + crates/perry-wpfacts/src/solve.rs | 8 +- crates/perry-wpfacts/src/solve/apply.rs | 4 +- .../src/solve/call_and_construct.rs | 48 ++- crates/perry-wpfacts/src/solve/store.rs | 4 +- crates/perry-wpfacts/src/tests/unit_mode.rs | 3 + .../src/tests/unit_mode/fn_transfer.rs | 332 ++++++++++++++++++ 11 files changed, 452 insertions(+), 15 deletions(-) create mode 100644 changelog.d/13610-ueb-forwarded-call-args.md create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/fn_transfer.rs diff --git a/changelog.d/13610-ueb-forwarded-call-args.md b/changelog.d/13610-ueb-forwarded-call-args.md new file mode 100644 index 00000000000..46a38031d63 --- /dev/null +++ b/changelog.d/13610-ueb-forwarded-call-args.md @@ -0,0 +1 @@ +Per-unit facts: a call that a stand-in replays from its callee contract belongs to the stand-in run, so a function value forwarded through one unit to a third unit's function that calls it stays described relative to the passing unit instead of escaping. Dump-only census records name the operation that first escaped each parameter marker. diff --git a/crates/perry-wpfacts/src/diagnostics.rs b/crates/perry-wpfacts/src/diagnostics.rs index d66b1a4b1a6..0a5f24e3d10 100644 --- a/crates/perry-wpfacts/src/diagnostics.rs +++ b/crates/perry-wpfacts/src/diagnostics.rs @@ -35,6 +35,13 @@ pub(crate) struct Trace { /// that slot: what an instance holds without a recorded predecessor it /// holds as its summary does. pub inst_orig: BTreeMap, + /// UEB census: the operation each parameter marker first escaped by + /// (`Model::mark_escape_why`), and the stack of those running. + pub esc_why: BTreeMap)>, + pub esc_ctx: Vec, + /// UEB census: a stand-in function's parameter node its contract sinks + /// (node, callee, index, reach key: `Some("")` the whole reach). + pub stand_in_params: Vec<(NodeId, String, u32, Option)>, /// `Model::top_predecessors`, built when a chain needs it. pub top_in: std::cell::OnceCell>>, } @@ -59,6 +66,7 @@ pub(crate) struct EscFirst { pub reason: u32, pub cause: Cause, pub call: Option, + pub node: Option, } #[derive(Default)] @@ -323,6 +331,7 @@ impl Model { reason, cause, call: d.cur_call, + node: d.cause_node, }); d.escaping.push(t); cand @@ -337,6 +346,17 @@ impl Model { d.esc_first.entry(t).or_insert(c); } } + /// Marks `t` escaped (`mark_use`) by operation `why`; the dump names + /// the first one per parameter marker (UEB census, dump only). + pub(crate) fn mark_escape_why(&mut self, t: Tok, why: &dyn Fn(&Self) -> String) { + if self.diagnostic.is_none() { + return self.mark_use(t, crate::model::MARK_ESCAPE); + } + let w = why(self); + self.diagnostic.as_mut().unwrap().esc_ctx.push(w); + self.mark_use(t, crate::model::MARK_ESCAPE); + self.diagnostic.as_mut().unwrap().esc_ctx.pop(); + } pub(crate) fn diagnostic_sink(&mut self, n: NodeId) { let gen_fn = self.cur_fn; if let Some(d) = self.diagnostic.as_mut() { @@ -873,8 +893,18 @@ pub(crate) fn collect(g: &Gen) -> Vec { let src_call = |c: Option| c.map(|c| m.diagnostic_source_call(c).unwrap_or(c)); if let Some(d) = m.diagnostic.as_ref() { for (t, e) in &d.esc_first { - out.push(format!("{{\"record\":\"escape-first\",\"token\":{t},\"reason\":{},\"cause\":{},\"call\":{}}}", - quote(m.reasons.get(e.reason as usize).map_or("", |s| s)), cause(&e.cause), opt(src_call(e.call)))); + out.push(format!("{{\"record\":\"escape-first\",\"token\":{t},\"reason\":{},\"cause\":{},\"call\":{},\"node\":{}}}", + quote(m.reasons.get(e.reason as usize).map_or("", |s| s)), cause(&e.cause), opt(src_call(e.call)), opt(e.node))); + } + for (t, (w, n)) in &d.esc_why { + if let Some(&(f, i)) = m.param_marker.get(t) { + out.push(format!("{{\"record\":\"param-escape\",\"token\":{t},\"function\":{f},\"index\":{i},\"marker\":{},\"why\":{},\"node\":{}}}", + quote(&m.tok_name(*t)), quote(w), opt(*n))); + } + } + for (n, name, i, k) in &d.stand_in_params { + out.push(format!("{{\"record\":\"stand-in-param\",\"node\":{n},\"callee\":{},\"index\":{i},\"key\":{}}}", + quote(name), k.as_deref().map_or("null".into(), quote))); } for (t, c) in &d.esc_causes { out.push(format!( diff --git a/crates/perry-wpfacts/src/gen/export.rs b/crates/perry-wpfacts/src/gen/export.rs index c4ec70bbd5b..95a55e583a3 100644 --- a/crates/perry-wpfacts/src/gen/export.rs +++ b/crates/perry-wpfacts/src/gen/export.rs @@ -890,7 +890,16 @@ impl<'a> Gen<'a> { let ru = self .m .reason("passed to a call of an unknown function value"); + if let Some(d) = self.m.diagnostic.as_mut() { + d.esc_ctx.push( + "contract: undescribed argument of a call through a parameter" + .into(), + ); + } self.m.sink_node(a, ru); + if let Some(d) = self.m.diagnostic.as_mut() { + d.esc_ctx.pop(); + } changed = true; } } diff --git a/crates/perry-wpfacts/src/gen/import_values.rs b/crates/perry-wpfacts/src/gen/import_values.rs index 7f1be614eaa..1406e19f18a 100644 --- a/crates/perry-wpfacts/src/gen/import_values.rs +++ b/crates/perry-wpfacts/src/gen/import_values.rs @@ -784,6 +784,9 @@ impl<'a> Gen<'a> { let u = c.params[i]; let ru = c.reach[i]; if u & MARK_ESCAPE != 0 { + if let Some(d) = self.m.diagnostic.as_mut() { + d.stand_in_params.push((p, c.name.clone(), i as u32, None)); + } self.m.sink_node(p, r); continue; } @@ -880,6 +883,14 @@ impl<'a> Gen<'a> { } for (key, ru) in buckets { if ru & MARK_ESCAPE != 0 { + if let Some(d) = self.m.diagnostic.as_mut() { + let k = match key { + crate::model::Key::Named(a) => self.m.atoms[a as usize].clone(), + crate::model::Key::Any => String::new(), + }; + d.stand_in_params + .push((p, c.name.clone(), i as u32, Some(k))); + } let q = match self.reach_escape { Some(q) => q, None => { @@ -999,6 +1010,14 @@ impl<'a> Gen<'a> { .m .node_named("arg", || format!("called{i}-arg{j}({})", c.name)); self.m.own(a); + // UEB: the call is the run's (DECISIONS 44 (b)): its + // arguments are temporaries of the run, as its result is, so + // what the run's parameters hand them stays relative to each + // call (the argument markers of this unit's values passed to + // the run) and the owner of those values describes them in + // its own contract (a function forwarded to a third unit's + // function that calls it, DECISIONS 140). + self.m.nodes[a as usize].frame = run; let why = "argument from another unit's function"; self.fill_described(mi, c, d, &d.value, this, ¶ms, a, why); args.push(a); diff --git a/crates/perry-wpfacts/src/model/operations.rs b/crates/perry-wpfacts/src/model/operations.rs index c342d9f8eab..70e3013986a 100644 --- a/crates/perry-wpfacts/src/model/operations.rs +++ b/crates/perry-wpfacts/src/model/operations.rs @@ -576,6 +576,11 @@ impl Model { .and_then(|d| d.get(i as usize)) .is_some_and(|&d| d); if new & MARK_ESCAPE != 0 { + if let Some(d) = self.diagnostic.as_mut() { + let w = d.esc_ctx.last().cloned().unwrap_or_else(|| "other".into()); + let n = d.cause_node; + d.esc_why.entry(t).or_insert((w, n)); + } self.virt_revoke(t, None); } if new & MARK_ESCAPE != 0 && !described { diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index c045700e3be..ffdb7a11d90 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1558,7 +1558,9 @@ impl Model { } Some((g, j)) if g == f && j != u32::MAX => from.push(j), _ => match own { - Some(at) => self.mark_use(at, crate::model::MARK_ESCAPE), + Some(at) => { + self.mark_escape_why(at, &|_| "store: flow of another value".into()) + } None => { top = true; self.escape(s, reason); @@ -1644,12 +1646,12 @@ impl Model { match &keys { None => { let r = self.reach_of(t); - self.mark_use(r, crate::model::MARK_ESCAPE); + self.mark_escape_why(r, &|_| "protocol: on a marker".into()); } Some(ks) => { for &a in ks { let m = self.marker_load(t, Some(a)); - self.mark_use(m, crate::model::MARK_ESCAPE); + self.mark_escape_why(m, &|_| "protocol: on a marker".into()); } } } diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index 43ef6edf4df..ba84f47c720 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -201,7 +201,9 @@ impl Model { // put into another function's marker or ⊤ goes there // as before: ⊤ to the reader, and it escapes (below). _ if dst == u32::MAX && reach && to == at => { - self.mark_use(at, crate::model::MARK_ESCAPE); + self.mark_escape_why(at, &|_| { + "store: into the receiver's reach".into() + }); } // Another function's marker: another unit's value // the reader cannot follow (DECISIONS 149b). diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index 824a6e900e8..a79b0ea947f 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -28,7 +28,18 @@ impl Model { } return; } - self.mark_use(t, crate::model::MARK_ESCAPE); + let spread = !cs.spread.is_empty(); + self.mark_escape_why(t, &|m: &Self| { + if spread { + "call: spread arguments".into() + } else if !plain { + "call: with a receiver".into() + } else if m.reach_marker.contains(&t) { + "call: of a reach".into() + } else { + "call: of a marker that is no own parameter".into() + } + }); return self.call_tok(call, TOP, this_tok); } if matches!(self.toks[t as usize], TokKind::Top | TokKind::BuiltinFn) { @@ -91,7 +102,7 @@ impl Model { // that is a function (Proxy over a function etc., ⊤ anyway). // A parameter marker stands for the caller's values: one may // be a function. - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|_| "call: of a non-callable".into()); } } } @@ -488,7 +499,7 @@ impl Model { } // `new` of a bare marker: of an unknown constructor (DECISIONS 119). if self.bare_marker(t) { - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|_| "new: of a marker".into()); return self.construct(call, TOP); } let c = self.calls[call as usize].clone(); @@ -791,7 +802,16 @@ impl Model { Key::Named(a) => self.marker_load(t, Some(a)), Key::Any => self.reach_of(t), }; - self.mark_use(m, crate::model::MARK_ESCAPE); + self.mark_escape_why(m, &|mm: &Self| match key { + Key::Named(a) + if matches!(mm.atoms[a as usize].as_str(), "call" | "apply" | "bind") => + { + format!("method: .{}", mm.atoms[a as usize]) + } + Key::Named(_) if mm.reach_marker.contains(&t) => "method: of a reach".into(), + Key::Named(_) => "method: of a marker that is no own parameter".into(), + Key::Any => "method: computed key".into(), + }); // That method is an unknown function, as a plain call through a // marker is (`call_tok`): the arguments escape, the result is // unknown (DECISIONS 202). @@ -1629,7 +1649,14 @@ impl Model { // beyond any contract too (DECISIONS 203), its contract functions // included (`escape`). let reason = self.beyond_contract(reason); - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|m: &Self| match m + .diagnostic + .as_ref() + .and_then(|d| d.esc_ctx.last()) + { + Some(o) => format!("{o} / escape: {}", m.reasons[reason as usize]), + None => format!("escape: {}", m.reasons[reason as usize]), + }); self.virt_revoke(t, None); if let Some(is) = self.contract_recv.get(&t).cloned() { self.recv_log @@ -1645,7 +1672,14 @@ impl Model { // it and escape there (DECISIONS 111). if self.is_marker(t) { let r = self.reach_of(t); - self.mark_use(r, crate::model::MARK_ESCAPE); + self.mark_escape_why(r, &|m: &Self| match m + .diagnostic + .as_ref() + .and_then(|d| d.esc_ctx.last()) + { + Some(o) => format!("{o} / escape: {}", m.reasons[reason as usize]), + None => format!("escape: {}", m.reasons[reason as usize]), + }); self.mark_escaped(t, reason); return; } @@ -1799,7 +1833,7 @@ impl Model { self.mark_hard(i, kind, reason); } } - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|_| "hard: reflective operation".into()); // `Object.setPrototypeOf(Object.prototype, p)` throws unless `p` is // null (its [[Prototype]] already): nothing changes (immutable // prototype exotic object). A ⊤ target that may be it is no E6 on it. diff --git a/crates/perry-wpfacts/src/solve/store.rs b/crates/perry-wpfacts/src/solve/store.rs index 229a38f8f98..26e31c6af5b 100644 --- a/crates/perry-wpfacts/src/solve/store.rs +++ b/crates/perry-wpfacts/src/solve/store.rs @@ -419,7 +419,7 @@ impl Model { return; } if key == Key::Any { - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|_| "store: under a computed key".into()); } if self.arg_store_done.insert((t, key, src)) { // DECISIONS 187/193: kept on `t` alone, where it was made, @@ -514,7 +514,7 @@ impl Model { return; } } - self.mark_use(t, crate::model::MARK_ESCAPE); + self.mark_escape_why(t, &|_| "store: into a marker".into()); // A parameter marker stands for another unit's value (design §11.2): // it has no fields in this unit. The store is its contract's escape // (above), and the stored value goes where the receiver's other diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index 3975a219ddd..32c19ba08fa 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -604,3 +604,6 @@ mod pub_callable; #[path = "unit_mode/method_on_read.rs"] mod method_on_read; + +#[path = "unit_mode/fn_transfer.rs"] +mod fn_transfer; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/fn_transfer.rs b/crates/perry-wpfacts/src/tests/unit_mode/fn_transfer.rs new file mode 100644 index 00000000000..f3b153fcd50 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/fn_transfer.rs @@ -0,0 +1,332 @@ +//! UEB: a function value passed to another unit's function is a relative +//! transfer, not an escape. The callee's contract describes what its body +//! does with the function (calls it, through a reach, a receiver, `.call`, +//! a third unit's function), relative to its parameters; the passing unit, +//! which holds the function, replays that on it (DECISIONS 140/144). +//! Only a use that hands the function to code no contract describes escapes. +//! +//! Every fixture: `a` passes its own `cb` to `b`'s function; `cb` reads `x.v` +//! of what it is called with (the `P` instance `a` also passes); `cb` is a +//! closure of `main`, so it is in the passing unit. Per unit +//! never proves a site the whole program does not. + +use super::*; + +const A_PLAIN: &str = r#"import { use } from "./b"; +class P { v = 1; } +export function main() { + function cb(x: any) { return x.v; } + return use(cb, new P()); +} +console.log(main());"#; + +const A_LIT: &str = r#"import { use } from "./b"; +class P { v = 1; } +export function main() { + function cb(x: any) { return x.v; } + return use({ cb, o: new P() }); +} +console.log(main());"#; + +/// The `x.v` sites of `a.ts`. +fn cb_proven(pf: &ProgramFacts) -> bool { + let v: Vec<_> = pf + .sites + .iter() + .filter(|s| s.key == "v" && pf.module_paths[s.module as usize] == "a.ts") + .collect(); + assert!(!v.is_empty(), "no a.ts .v site\n{}", pf.dump_text()); + v.iter() + .all(|s| matches!(s.verdict, Verdict::T0Mono | Verdict::T0Set)) +} + +/// Per unit never stronger than whole; whole proves `x.v` (`want_whole`). +fn never_stronger(mods: &[(&str, &str)], want_whole: bool) -> (ProgramFacts, ProgramFacts) { + let (u, w) = (units_of(mods), whole_of(mods)); + let stronger: Vec<_> = proven_set(&u) + .difference(&proven_set(&w)) + .cloned() + .collect(); + assert!( + stronger.is_empty(), + "per unit stronger than whole: {stronger:?}\n{}", + u.dump_text() + ); + assert_eq!(cb_proven(&w), want_whole, "whole\n{}", w.dump_text()); + (u, w) +} + +/// Per unit proves `x.v` as whole does: the transfer is relative. +fn transfers(mods: &[(&str, &str)]) { + let (u, _) = never_stronger(mods, true); + assert!(cb_proven(&u), "per unit\n{}", u.dump_text()); +} + +/// Per unit as whole on `x.v` (whatever whole says), never stronger. +fn same_as_whole(mods: &[(&str, &str)]) { + let (u, w) = (units_of(mods), whole_of(mods)); + let stronger: Vec<_> = proven_set(&u) + .difference(&proven_set(&w)) + .cloned() + .collect(); + assert!( + stronger.is_empty(), + "per unit stronger than whole: {stronger:?}\n{}", + u.dump_text() + ); + assert_eq!( + cb_proven(&u), + cb_proven(&w), + "per unit vs whole\n{}", + u.dump_text() + ); +} + +/// Open shape (UEB census: no mass on tssrc/fastify_inject): pinned at its +/// current per-unit verdict, never stronger than whole; a fix flips it. +fn gap(mods: &[(&str, &str)]) { + let (u, _) = never_stronger(mods, true); + assert!( + !cb_proven(&u), + "gap closed: make it a `transfers` test\n{}", + u.dump_text() + ); +} + +/// Per unit leaves `x.v` unproven, as whole does: unknown code calls `cb`. +fn escapes(mods: &[(&str, &str)]) { + let (u, _) = never_stronger(mods, false); + assert!(!cb_proven(&u), "per unit\n{}", u.dump_text()); +} + +// ------------------------------------------------------------ use kinds + +/// Control (DECISIONS 140): `f(o)`; green on main. +#[test] +fn a_direct_call_of_the_parameter_runs_in_the_passing_unit() { + let b = "export function use(f: any, o: any) { return f(o); }"; + transfers(&[("a", A_PLAIN), ("b", b)]); +} + +/// Control (DECISIONS 144): `opts.cb(o)`; green on main. +#[test] +fn a_method_call_through_the_parameter_runs_in_the_passing_unit() { + let b = "export function use(opts: any) { return opts.cb(opts.o); }"; + transfers(&[("a", A_LIT), ("b", b)]); +} + +/// A call of what the parameter reaches, read out first: `const g = opts.cb; g(o)`. +#[test] +fn a_call_through_a_reach_of_the_parameter_is_an_open_gap() { + let b = "export function use(opts: any) { const g = opts.cb; return g(opts.o); }"; + gap(&[("a", A_LIT), ("b", b)]); +} + +/// Forwarded to a third unit's function, which calls it: the third unit's +/// contract hands the call back to `b` (DECISIONS 140), whose replay is the +/// stand-in's run, so `b` describes the argument relative to its own +/// parameter and `a` runs `cb` on its `P`. Red on main ("passed to a call +/// of an unknown function value": the replayed call's argument read as +/// another run's marker). +#[test] +fn a_function_forwarded_to_a_third_units_function_runs_in_the_passing_unit() { + let b = r#"import { run } from "./c"; +export function use(f: any, o: any) { return run(f, o); }"#; + let c = "export function run(g: any, o: any) { return g(o); }"; + transfers(&[("a", A_PLAIN), ("b", b), ("c", c)]); +} + +/// `f.call(thisArg, o)` and `f.apply(thisArg, [o])`: per unit as whole. +#[test] +fn a_call_or_apply_of_the_parameter_is_as_whole() { + let b = "export function use(f: any, o: any) { return f.call(undefined, o); }"; + same_as_whole(&[("a", A_PLAIN), ("b", b)]); + let b = "export function use(f: any, o: any) { return f.apply(undefined, [o]); }"; + same_as_whole(&[("a", A_PLAIN), ("b", b)]); +} + +/// Stored into the callee's own object and called from there. +#[test] +fn a_function_stored_into_the_callees_object_is_an_open_gap() { + let b = "export function use(f: any, o: any) { const h = { f }; return h.f(o); }"; + gap(&[("a", A_PLAIN), ("b", b)]); +} + +/// Stored into an instance of the callee's class (a constructor parameter +/// kept as a field), then called through it. +#[test] +fn a_function_kept_by_the_callees_instance_is_an_open_gap() { + let b = r#"class H { constructor(public f: any) {} go(o: any) { return this.f(o); } } +export function use(f: any, o: any) { return new H(f).go(o); }"#; + gap(&[("a", A_PLAIN), ("b", b)]); +} + +/// Put into an array (a literal element and a spread), called as an element. +#[test] +fn a_function_put_into_the_callees_array_is_an_open_gap() { + let b = "export function use(f: any, o: any) { const fs = [f]; return fs[0](o); }"; + gap(&[("a", A_PLAIN), ("b", b)]); +} + +// ------------------------------------------------------------ soundness + +/// The callee keeps the function where its own later calls, from unknown +/// code, run it: `fire` escapes, so `cb` runs on unknown values. +#[test] +fn a_function_the_callee_keeps_for_an_unknown_caller_escapes() { + let b = r#"let saved: any; +export function use(f: any, o: any) { saved = f; return f(o); } +export function fire(x: any) { return saved(x); } +(globalThis as any).fire = fire;"#; + escapes(&[("a", A_PLAIN), ("b", b)]); +} + +/// The callee stores the function where unknown code reads it. +#[test] +fn a_function_the_callee_hands_to_unknown_code_escapes() { + let b = r#"export function use(f: any, o: any) { (globalThis as any).hook = f; return f(o); }"#; + escapes(&[("a", A_PLAIN), ("b", b)]); + let b = r#"export function use(f: any, o: any) { (globalThis as any).keep({ f }); return 0; }"#; + escapes(&[("a", A_PLAIN), ("b", b)]); +} + +/// Forwarded to a third unit that hands it to unknown code. +#[test] +fn a_function_forwarded_to_a_unit_that_lets_it_go_escapes() { + let b = r#"import { run } from "./c"; +export function use(f: any, o: any) { return run(f, o); }"#; + let c = + r#"export function run(g: any, o: any) { (globalThis as any).register(g); return g(o); }"#; + escapes(&[("a", A_PLAIN), ("b", b), ("c", c)]); +} + +/// Recursion between two units, each passing the function back. +#[test] +fn a_function_passed_back_and_forth_between_two_units_runs_in_the_passing_unit() { + let a = r#"import { pb } from "./b"; +class P { v = 1; } +export function main() { + function cb(x: any) { return x.v; } + return pb(cb, new P(), 3); +} +console.log(main());"#; + let b = r#"import { pc } from "./c"; +export function pb(f: any, o: any, n: number): any { return n > 0 ? pc(f, o, n - 1) : f(o); }"#; + let c = r#"import { pb } from "./b"; +export function pc(f: any, o: any, n: number): any { return pb(f, o, n); }"#; + let (u, _) = never_stronger(&[("a", a), ("b", b), ("c", c)], true); + let _ = u; +} + +/// The recursion where one side lets the function go: unknown. +#[test] +fn a_function_passed_back_and_forth_and_let_go_escapes() { + let a = r#"import { pb } from "./b"; +class P { v = 1; } +export function main() { + function cb(x: any) { return x.v; } + return pb(cb, new P(), 3); +} +console.log(main());"#; + let b = r#"import { pc } from "./c"; +export function pb(f: any, o: any, n: number): any { return n > 0 ? pc(f, o, n - 1) : f(o); }"#; + let c = r#"import { pb } from "./b"; +export function pc(f: any, o: any, n: number): any { (globalThis as any).g = f; return pb(f, o, n); }"#; + escapes(&[("a", a), ("b", b), ("c", c)]); +} + +/// `arguments` and a rest parameter: the callee calls the function it +/// reads from them with an unknown value. +#[test] +fn a_function_read_from_arguments_or_rest_stays_sound() { + let b = + r#"export function use(f: any, o: any) { return arguments[0]((globalThis as any).u); }"#; + escapes(&[("a", A_PLAIN), ("b", b)]); + let b = r#"export function use(...fs: any[]) { return fs[0]((globalThis as any).u); }"#; + escapes(&[("a", A_PLAIN), ("b", b)]); + let b = r#"export function use(...fs: any[]) { return fs[0](fs[1]); }"#; + same_as_whole(&[("a", A_PLAIN), ("b", b)]); +} + +/// The passing unit itself lets the function go: unknown whatever the +/// callee does. +#[test] +fn a_function_its_own_unit_lets_go_escapes() { + let a = r#"import { use } from "./b"; +class P { v = 1; } +export function main() { + function cb(x: any) { return x.v; } + (globalThis as any).k = cb; + return use(cb, new P()); +} +console.log(main());"#; + let b = "export function use(f: any, o: any) { const h = { f }; return h.f(o); }"; + escapes(&[("a", a), ("b", b)]); +} + +/// A third unit over the work bound runs the forwarded function: it reports +/// what it consumed, so the function is unknown there; never stronger. +#[test] +fn a_function_forwarded_to_a_unit_over_the_bound_stays_sound() { + let b = r#"import { run } from "./c"; +export function use(f: any, o: any) { return run(f, o); }"#; + let mut c = String::from("export function run(g: any, o: any) { let s = 0;\n"); + for i in 0..300 { + c.push_str(&format!( + " const q{i} = {{ k{i}: {i}, f: g }}; s += q{i}.k{i};\n" + )); + } + c.push_str(" return g(o) + s; }"); + let mods = [("a", A_PLAIN), ("b", b), ("c", c.as_str())]; + let hirs = hirs_of(&mods); + let list: Vec<(String, &perry_hir::Module)> = + hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let free = crate::compute_units(&list, &FactsConfig::default()); + let wk = |p: &str| -> u64 { + free.units + .iter() + .filter(|(q, _)| crate::decl_units::module_of(q) == p) + .map(|(_, s)| s.work) + .sum() + }; + let bound = 2 * wk("a.ts").max(wk("b.ts")); + assert!(wk("c.ts") > bound, "{:?}", free.units); + let u = crate::compute_units( + &list, + &FactsConfig { + max_work: Some(bound), + ..FactsConfig::default() + }, + ); + assert!( + u.units + .iter() + .any(|(q, s)| crate::decl_units::module_of(q) == "c.ts" && s.aborted), + "{:?}", + u.units + ); + let w = whole_of(&mods); + let stronger: Vec<_> = proven_set(&u) + .difference(&proven_set(&w)) + .cloned() + .collect(); + assert!( + stronger.is_empty(), + "per unit stronger than whole: {stronger:?}\n{}", + u.dump_text() + ); + assert!(!cb_proven(&u), "per unit over the bound\n{}", u.dump_text()); +} + +/// Two functions of `b` forward to the same third-unit function, one of +/// them run by unknown code with unknown values: the stand-in's one body +/// joins both calls, never stronger than whole. +#[test] +fn two_forwarders_one_run_by_unknown_code_stay_sound() { + let b = r#"import { run } from "./c"; +export function use(f: any, o: any) { return run(f, o); } +export function use2(g: any, p: any) { return run(g, p); } +(globalThis as any).use2 = use2;"#; + let c = "export function run(g: any, o: any) { return g(o); }"; + same_as_whole(&[("a", A_PLAIN), ("b", b), ("c", c)]); +} From 2afcb01f6385f405061543055ea49803b1491210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 09:21:27 +0100 Subject: [PATCH 060/144] codegen: holder facts: a read of a field holding only prefix-stable classes is a proven receiver (depends on #13601) (#13606) --- changelog.d/13606-cnsi-holder-facts.md | 1 + .../src/codegen/program_facts_view.rs | 10 + .../perry-codegen/src/expr/wpfacts_access.rs | 41 +- .../numeric/number_result_tests.rs | 2 + crates/perry-wpfacts/src/lib.rs | 24 + .../src/commands/compile/program_facts.rs | 30 + crates/perry/tests/holder_facts_receivers.rs | 515 ++++++++++++++++++ 7 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 changelog.d/13606-cnsi-holder-facts.md create mode 100644 crates/perry/tests/holder_facts_receivers.rs diff --git a/changelog.d/13606-cnsi-holder-facts.md b/changelog.d/13606-cnsi-holder-facts.md new file mode 100644 index 00000000000..8c714a509cb --- /dev/null +++ b/changelog.d/13606-cnsi-holder-facts.md @@ -0,0 +1 @@ +A read of a field that holds only instances of prefix-stable classes (or null/undefined) is now a proven receiver when the object it is read from is one: `this.current.run(n)`, `this.current.id` lower through the facts (a slot load, the class-id switch of direct calls) with no probe. The field facts carry the classes of every object any write stores there (`FactsField::classes`, `nullable`); a field that ever holds a thrown value, a function, a builtin, another primitive, interpreted values, a declared or reserved key gives no fact, and a nullable field keeps the receiver test. Per unit never proves more than the whole program. diff --git a/crates/perry-codegen/src/codegen/program_facts_view.rs b/crates/perry-codegen/src/codegen/program_facts_view.rs index ae6c1cec163..14e14b4f0b5 100644 --- a/crates/perry-codegen/src/codegen/program_facts_view.rs +++ b/crates/perry-codegen/src/codegen/program_facts_view.rs @@ -69,6 +69,16 @@ pub struct FactsField { /// The field holds only these bodies of this module (FVA): a call /// through it is a call of them. Empty when it may hold anything else. pub fns: Vec, + /// Holder facts: every value any write of any unit stores into the field + /// is an instance of one of these prefix-stable classes, or (when + /// `nullable`) null/undefined. A read of the field is then a receiver the + /// facts prove (`wpfacts_access::receiver`): the field's proof travels + /// with its holder. Empty when the field can hold anything else (⊤, a + /// function, a builtin, another primitive, an instance of a class that + /// is not prefix-stable), when its presence is not known (`declared`), + /// or when interpreted code can run. + pub classes: Vec, + pub nullable: bool, } /// A plain compiled body of this module that a binding or a field holds diff --git a/crates/perry-codegen/src/expr/wpfacts_access.rs b/crates/perry-codegen/src/expr/wpfacts_access.rs index eeb7a58ea34..3c963815311 100644 --- a/crates/perry-codegen/src/expr/wpfacts_access.rs +++ b/crates/perry-codegen/src/expr/wpfacts_access.rs @@ -13,7 +13,8 @@ //! which throws), no ShapeId compare, no miss call. //! //! The receiver facts come from the whole-program solve (`locals`, -//! `this_of`). +//! `this_of`, and, for a receiver that is itself a field read, the field's +//! holder facts `FactsField::classes`). //! The GC write barrier stays for stores that can carry a pointer (F2 decides //! which can); the raw-f64 lane discipline stays for stores into an F64 lane. //! @@ -166,6 +167,44 @@ fn receiver(ctx: &FnCtx<'_>, object: &Expr) -> Option<(crate::codegen::FactsRecv (r.clone(), format!("this of fn {fid}")) }) } + // Holder facts: a read of a field of an unchecked proven receiver + // holds, in every class of that receiver, only instances of the + // field's classes (or null/undefined): their union is this value's + // receiver fact. No guard: an unchecked receiver needs none, and a + // field with ⊤ or interpreted writers has no classes. + Expr::PropertyGet { + object: inner, + property, + .. + } => { + if property.starts_with('#') || ctx.wpfacts.interp_guard { + return None; + } + let (r, binding) = receiver(ctx, inner)?; + if r.checked || r.class_ids.is_empty() { + return None; + } + let mut class_ids: Vec = Vec::new(); + let mut nullable = false; + for &cid in &r.class_ids { + let f = ctx.wpfacts.field(cid, property)?; + if f.reserved || f.classes.is_empty() { + return None; + } + class_ids.extend_from_slice(&f.classes); + nullable |= f.nullable; + } + class_ids.sort_unstable(); + class_ids.dedup(); + Some(( + crate::codegen::FactsRecv { + class_ids, + nullable, + checked: false, + }, + format!("field {property} of {binding}"), + )) + } _ => None, } } diff --git a/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs b/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs index 49c17db15f5..cf6034db594 100644 --- a/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs +++ b/crates/perry-codegen/src/type_analysis/numeric/number_result_tests.rs @@ -30,6 +30,8 @@ fn body(with_facts: bool, checked: bool, interp: bool, kind: u16) -> String { display: c.name.clone(), fixed_layout: true, fields: vec![FactsField { + classes: Vec::new(), + nullable: false, key: "x".into(), slot: 0, readonly: false, diff --git a/crates/perry-wpfacts/src/lib.rs b/crates/perry-wpfacts/src/lib.rs index f2bfce191c6..8d29a2432f3 100644 --- a/crates/perry-wpfacts/src/lib.rs +++ b/crates/perry-wpfacts/src/lib.rs @@ -1748,6 +1748,30 @@ impl ProgramFacts { .collect() } + /// The class ids of the objects a field's kind names (its `objs`, by + /// display), sorted. `None` when one of them is no class instance (a + /// literal or function-instance template) or its display does not name + /// exactly one class. Says nothing about the kind's other members (⊤, + /// functions, builtins, primitives): the caller decides on those. + pub fn classes_of(&self, k: &derive::KindFact) -> Option> { + let mut out = Vec::with_capacity(k.objs.len()); + for d in &k.objs { + let mut ids = self + .templates + .iter() + .filter(|t| t.display == *d) + .map(|t| t.class_id); + let cid = ids.next()??; + if !ids.all(|c| c == Some(cid)) { + return None; + } + out.push(cid); + } + out.sort_unstable(); + out.dedup(); + Some(out) + } + /// Receiver facts of module `path`'s bindings (empty when unknown). pub fn bindings_for(&self, path: &str) -> Option<&ModuleBindings> { self.bindings.get(self.module_index(path)?) diff --git a/crates/perry/src/commands/compile/program_facts.rs b/crates/perry/src/commands/compile/program_facts.rs index 43f575c9307..8b9abc3a3f1 100644 --- a/crates/perry/src/commands/compile/program_facts.rs +++ b/crates/perry/src/commands/compile/program_facts.rs @@ -428,6 +428,33 @@ fn elision_for(pf: &perry_wpfacts::ProgramFacts, path: &str) -> perry_codegen::E out.this_of.insert(*id, r); } } + // Holder facts: the prefix-stable classes a definite data field holds + // (a reserved key may still be absent: no field-class fact), + // when it holds nothing else but null/undefined (no ⊤, function, + // builtin, other primitive, literal or class without stable facts). + // The field kind is the join over every unit's stores, so per unit it + // is never stronger than whole. Interpreted code can store anything. + let holder_classes = |f: &perry_wpfacts::FieldFacts| -> Option<(Vec, bool)> { + let k = &f.kind; + if interp + || f.declared + || f.reserved + || !f.definite + || !f.data_field + || k.top + || k.funcs != 0 + || k.builtin_objs != 0 + || k.objs.is_empty() + || k.prims & !(prim::NULL | prim::UNDEF) != 0 + { + return None; + } + let ids = pf.classes_of(k)?; + if ids.is_empty() || !ids.iter().all(|c| stable.contains_key(c)) { + return None; + } + Some((ids, k.prims != 0)) + }; // Step 5b: a typed body's entry check proves a class set for a // parameter no binding fact covers, so every prefix-stable class's // layout facts are visible, not only those of classes some binding holds. @@ -437,7 +464,10 @@ fn elision_for(pf: &perry_wpfacts::ProgramFacts, path: &str) -> perry_codegen::E .iter() .filter(|f| (f.definite || f.declared || f.reserved) && f.data_field) .filter_map(|f| { + let (classes, nullable) = holder_classes(f).unwrap_or_default(); Some(FactsField { + classes, + nullable, fns: bodies(&f.fns), key: f.key.clone(), slot: f.slot?, diff --git a/crates/perry/tests/holder_facts_receivers.rs b/crates/perry/tests/holder_facts_receivers.rs new file mode 100644 index 00000000000..4182a78da22 --- /dev/null +++ b/crates/perry/tests/holder_facts_receivers.rs @@ -0,0 +1,515 @@ +//! Holder facts: a receiver that is itself a field read (`this.current.run()`, +//! `this.current.id`) is proven when the field's facts name the classes of +//! every object any write stores there (`FactsField::classes`), over every +//! class of the field's holder. The read lowers through the facts (a slot +//! load, the class-id switch of direct calls), with no probe. +//! +//! No fact when the field can hold anything else: ⊤ (a thrown value), a +//! function, or when interpreted code can run. A nullable field keeps the +//! receiver test (null throws). A key the constructor assigns after code +//! that observes `this` can be read unset (reserved): no field-class fact; a declared field reads undefined: nullable. Per unit never +//! proves more than the whole program. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn perry_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_perry")) +} + +fn clean(cmd: &mut Command) -> &mut Command { + for (key, _) in std::env::vars() { + if key.starts_with("PERRY_") || key == "NODE_OPTIONS" { + cmd.env_remove(key); + } + } + cmd +} + +struct Built { + /// Saved LLVM IR of module `_ts.ll`. + ll: PathBuf, + /// The `.elisions` record: one line per elided field access. + elisions: String, + /// The facts dump (`PERRY_FACTS_DUMP`). + facts: String, + stdout: String, +} + +impl Built { + fn ir(&self, module: &str) -> String { + std::fs::read_to_string(self.ll.join(format!("{module}_ts.ll"))).expect("saved ll") + } + + /// Elided accesses in `Sched.` whose receiver is the field read + /// `this.`. + fn field_elisions(&self, method: &str, field: &str) -> usize { + self.elisions + .lines() + .filter(|l| { + l.contains(&format!("__Sched__{method}")) + && l.contains(&format!("recv=field {field} of ")) + }) + .count() + } + + fn field_receiver_elisions(&self, method: &str) -> usize { + self.field_elisions(method, "current") + } +} + +/// Write `files` (the first is the entry), compile (whole program when +/// `whole`, else per unit), run. +fn build(dir: &Path, files: &[(&str, &str)], whole: bool) -> Built { + for (name, text) in files { + std::fs::write(dir.join(name), text).unwrap(); + } + let tag = if whole { "w" } else { "u" }; + let exe = dir.join(format!("main_{tag}")); + let ll = dir.join(format!("ll_{tag}")); + std::fs::create_dir_all(&ll).unwrap(); + let dump = dir.join(format!("facts_{tag}")); + let mut cmd = Command::new(perry_bin()); + clean(&mut cmd) + .current_dir(dir) + .env("PERRY_NO_CACHE", "1") + .env("PERRY_SAVE_LL", &ll) + .env("PERRY_FACTS_DUMP", &dump); + if whole { + cmd.env("PERRY_FACTS_UNITS", "0"); + } + let out = cmd + .arg("compile") + .arg(dir.join(files[0].0)) + .arg("-o") + .arg(&exe) + .output() + .expect("compile"); + assert!( + out.status.success(), + "compile failed (whole={whole})\n{}", + String::from_utf8_lossy(&out.stderr) + ); + let mut run = Command::new(&exe); + let ran = clean(&mut run).output().expect("run"); + assert!( + ran.status.success(), + "run failed (whole={whole}): {:?}", + ran.status + ); + let facts = std::fs::read_to_string(&dump).unwrap_or_default(); + let mut el = dump.into_os_string(); + el.push(".elisions"); + Built { + ll, + elisions: std::fs::read_to_string(el).unwrap_or_default(), + facts, + stdout: String::from_utf8_lossy(&ran.stdout).into_owned(), + } +} + +/// The body of the function defined as `@(`. +fn body<'a>(ir: &'a str, symbol: &str) -> &'a str { + let head = format!("@{symbol}("); + let at = ir + .match_indices("define ") + .map(|(i, _)| i) + .find(|&i| { + ir[i..] + .lines() + .next() + .is_some_and(|line| line.contains(&head)) + }) + .unwrap_or_else(|| panic!("no function {symbol}")); + let end = ir[at..].find("\n}\n").map_or(ir.len(), |e| at + e); + &ir[at..end] +} + +/// Both modes: the program prints `expected`; return (per unit, whole). +fn both(files: &[(&str, &str)], expected: &str) -> (Built, Built, tempfile::TempDir) { + let dir = tempfile::tempdir().unwrap(); + let u = build(dir.path(), files, false); + let w = build(dir.path(), files, true); + assert_eq!(u.stdout.trim(), expected, "per unit output"); + assert_eq!(w.stdout.trim(), expected, "whole output"); + (u, w, dir) +} + +const PROBES: [&str; 3] = [ + "@perry_probe_get(", + "@perry_probe_family_method(", + "@js_method_call_slow(", +]; + +fn assert_no_probe(ir: &str, symbol: &str) { + let b = body(ir, symbol); + for p in PROBES { + assert!(!b.contains(p), "{symbol} keeps {p}:\n{b}"); + } +} + +fn assert_probe(ir: &str, symbol: &str) { + let b = body(ir, symbol); + assert!( + PROBES.iter().any(|p| b.contains(p)), + "{symbol} has no generic site:\n{b}" + ); +} + +const SINGLE: &str = r#" +class Task { + id: number; link: Task | null; + constructor(id: number) { this.id = id; this.link = null; } + run(n: number): number { return this.id * 2 + n; } +} +class Sched { + current: Task; + constructor() { this.current = new Task(3); } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i); +console.log(acc); +"#; +const UNION: &str = r#" +class Worker { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Device { + id: number; pad: number; + constructor(id: number) { this.pad = 7; this.id = id; } + run(n: number): number { return this.id * n; } +} +class Sched { + current: Worker | Device; + constructor(d: boolean) { this.current = d ? new Device(2) : new Worker(5); } + swap(): void { this.current = this.current instanceof Worker ? new Device(3) : new Worker(4); } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(false); +let acc = 0; +for (let i = 0; i < 1000; i++) { acc += s.step(i); if (i % 3 === 0) s.swap(); } +console.log(acc); +"#; +const NULLABLE: &str = r#" +class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Sched { + current: Task | null; + constructor() { this.current = null; } + set(t: Task | null): void { this.current = t; } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) { s.set(new Task(i)); acc += s.step(i); } +s.set(null); +try { s.step(1); } catch (e) { console.log(e instanceof TypeError); } +console.log(acc); +"#; +/// `current` holds Task instances, undefined and ⊤, and nothing else: ⊤ +/// is the only reason for no fact. The ⊤ is a property load from a thrown +/// value (`e.inner`): ⊤ with no primitive kind beside it, so the primitive +/// guard does not reject the field first (a thrown value itself is ⊤ of +/// every primitive kind). Every class has `id` and `run`, so even a class +/// set grown to every class lowers if ⊤ leaks. +const TOP: &str = r#" +class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Wrap { + id: number; + inner: Task; + constructor(inner: Task) { this.id = 0; this.inner = inner; } + run(n: number): number { return n; } +} +class Sched { + id: number; + current: any; + ok: Task; + constructor() { this.id = 0; this.current = new Task(1); this.ok = new Task(2); } + run(n: number): number { return n; } + poison(): void { try { throw new Wrap(new Task(40)); } catch (e) { this.current = e.inner; } } + stepOk(n: number): number { return this.ok.run(n) + this.ok.id; } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i) + s.stepOk(i); +s.poison(); +console.log(s.step(1)); +console.log(acc); +"#; +const FUNC: &str = r#" +class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Box { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Sched { + current: any; + ok: Box; + constructor() { this.current = new Task(1); this.ok = new Box(2); } + fn(): void { const f: any = (n: number) => n; f.id = 9; f.run = (n: number) => n * 3; this.current = f; } + stepOk(n: number): number { return this.ok.run(n) + this.ok.id; } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i) + s.stepOk(i); +s.fn(); +console.log(acc + s.step(2)); +"#; +const INTERP: &str = r#" +class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Sched { + current: Task; + constructor() { this.current = new Task(3); } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i); +console.log(acc + (0, eval)("1 + 1")); +"#; +const EARLY: &str = r#" +class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +class Sched { + current: Task; + constructor(early: boolean) { if (early) this.peek(); this.current = new Task(3); } + peek(): number { return this.current.run(1) + this.current.id; } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +const s = new Sched(false); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i); +console.log(acc); +try { new Sched(true); } catch (e) { console.log(e instanceof TypeError); } +"#; +const RESERVED: &str = r#" +class Task { + constructor(id) { this.id = id; } + run(n) { return this.id + n; } +} +class Sched { + constructor(early) { if (early) this.peek(); this.current = new Task(3); } + peek() { return this.current.run(1) + this.current.id; } + step(n) { return this.current.run(n) + this.current.id; } +} +const s = new Sched(false); +let acc = 0; +for (let i = 0; i < 1000; i++) acc += s.step(i); +console.log(acc); +try { new Sched(true); } catch (e) { console.log(e instanceof TypeError); } +"#; +const XMOD_HOLDER: &str = r#" +export class Task { + id: number; + constructor(id: number) { this.id = id; } + run(n: number): number { return this.id + n; } +} +export class Sched { + current: any; + ok: Task; + constructor() { this.current = new Task(3); this.ok = new Task(2); } + stepOk(n: number): number { return this.ok.run(n) + this.ok.id; } + step(n: number): number { return this.current.run(n) + this.current.id; } +} +"#; +const XMOD_OTHER: &str = r#" +import { Sched } from "./holder"; +export class Other { + pad: number; id: number; + constructor() { this.pad = 1; this.id = 100; } + run(n: number): number { return n * 10; } +} +export function swap(s: Sched): void { s.current = new Other(); } +"#; +const XMOD_MAIN: &str = r#" +import { Sched } from "./holder"; +import { swap } from "./other"; +const s = new Sched(); +let acc = 0; +for (let i = 0; i < 1000; i++) { acc += s.step(i) + s.stepOk(i); if (i === 500) swap(s); } +console.log(acc); +"#; + +#[test] +fn a_field_of_one_class_is_a_proven_receiver() { + let (u, w, _d) = both(&[("main.ts", SINGLE)], "508500"); + for b in [&u, &w] { + assert!(b.field_receiver_elisions("step") > 0, "{}", b.elisions); + let ir = b.ir("main"); + assert_no_probe(&ir, "perry_method_main_ts__Sched__step"); + assert!(body(&ir, "perry_method_main_ts__Sched__step") + .contains("call double @perry_method_main_ts__Task__run(")); + } +} + +#[test] +fn a_field_of_a_union_of_classes_switches_over_them() { + let (u, w, _d) = both(&[("main.ts", UNION)], "1005997"); + for b in [&u, &w] { + assert!(b.field_receiver_elisions("step") > 0, "{}", b.elisions); + let ir = b.ir("main"); + let step = body(&ir, "perry_method_main_ts__Sched__step"); + assert_no_probe(&ir, "perry_method_main_ts__Sched__step"); + assert!( + step.contains("call double @perry_method_main_ts__Worker__run("), + "{step}" + ); + assert!( + step.contains("call double @perry_method_main_ts__Device__run("), + "{step}" + ); + } +} + +#[test] +fn a_nullable_field_keeps_the_receiver_test() { + let (u, w, _d) = both(&[("main.ts", NULLABLE)], "true\n1498500"); + for b in [&u, &w] { + assert!( + b.elisions.lines().any(|l| l.contains("__Sched__step") + && l.contains("recv=field current ") + && l.contains("nullable=1")), + "{}", + b.elisions + ); + assert!( + !b.elisions + .lines() + .any(|l| l.contains("recv=field current ") && l.contains("nullable=0")), + "{}", + b.elisions + ); + } +} + +#[test] +fn a_field_written_top_has_no_fact() { + let (u, w, _d) = both(&[("main.ts", TOP)], "81\n1005000"); + for b in [&u, &w] { + // The guard's input held: the field is ⊤ in the facts. + assert!( + b.facts + .lines() + .any(|l| l.starts_with("FACT F2 kind ") && l.ends_with(":Sched.current = top")), + "Sched.current is not ⊤:\n{}", + b.facts + ); + // The control: a field only one class is ever stored into. + assert!(b.field_elisions("stepOk", "ok") > 0, "{}", b.elisions); + assert_eq!(b.field_receiver_elisions("step"), 0, "{}", b.elisions); + assert_probe(&b.ir("main"), "perry_method_main_ts__Sched__step"); + } +} + +#[test] +fn a_function_in_the_field_has_no_fact() { + let (u, w, _d) = both(&[("main.ts", FUNC)], "1005015"); + for b in [&u, &w] { + assert!(b.field_elisions("stepOk", "ok") > 0, "{}", b.elisions); + assert_eq!(b.field_receiver_elisions("step"), 0, "{}", b.elisions); + assert_probe(&b.ir("main"), "perry_method_main_ts__Sched__step"); + } +} + +/// A negative guard only: with interpreted code no receiver fact exists at +/// all, so there is no control to prove (the interp sabotage makes it fail). +#[test] +fn interpreted_code_leaves_no_fact() { + let (u, w, _d) = both(&[("main.ts", INTERP)], "505502"); + for b in [&u, &w] { + assert_eq!(b.field_receiver_elisions("step"), 0, "{}", b.elisions); + } +} + +#[test] +fn a_declared_field_observed_before_the_constructor_assigns_it_is_nullable() { + // `current: Task;` is declared: the key exists (undefined) from the + // start, so a read before the assignment sees undefined: nullable. + let (u, w, _d) = both(&[("main.ts", EARLY)], "505500\ntrue"); + for b in [&u, &w] { + assert!( + b.elisions.lines().any(|l| l.contains("__Sched__step") + && l.contains("recv=field current ") + && l.contains("nullable=1")), + "{}", + b.elisions + ); + assert!( + !b.elisions + .lines() + .any(|l| l.contains("recv=field current ") && l.contains("nullable=0")), + "{}", + b.elisions + ); + } +} + +#[test] +fn a_reserved_key_gives_no_field_class_fact() { + // No declaration: `current` is stored after `this.peek()` ran, so it can + // be read unset (reserved: known slot, presence unknown, the read goes on + // to the prototype chain). No read of it is a proven receiver. + let (u, w, _d) = both(&[("main.js", RESERVED)], "505500\ntrue"); + for b in [&u, &w] { + assert!( + !b.elisions + .lines() + .any(|l| l.contains("recv=field current ")), + "a reserved key is a proven receiver:\n{}", + b.elisions + ); + } +} + +#[test] +fn another_modules_writer_is_in_the_field_classes() { + let files = [ + ("main.ts", XMOD_MAIN), + ("holder.ts", XMOD_HOLDER), + ("other.ts", XMOD_OTHER), + ]; + let (u, w, _d) = both(&files, "4424156"); + for b in [&u, &w] { + assert!(b.field_elisions("stepOk", "ok") > 0, "{}", b.elisions); + } + assert!( + u.field_receiver_elisions("step") <= w.field_receiver_elisions("step"), + "per unit proves more than whole:\n{}\n--\n{}", + u.elisions, + w.elisions + ); + for b in [&u, &w] { + assert!( + !b.elisions + .lines() + .any(|l| l.contains("recv=field current ") && !l.contains("Other")), + "a field-read receiver proof without the other module's class:\n{}", + b.elisions + ); + } +} From 86c60fc19875b511536b6fe5ef947cc7298c971f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:15:41 +0000 Subject: [PATCH 061/144] wpfacts: a literal birth's chunk functions are in its unit (DGL) codegen's literal splitting moves the tail slots of a giant literal birth into chunk functions that initialize the unpublished object. The cut made each chunk its own unit (the birth calls it, it refers to nothing back), so per unit the birth published its chunked slots as unknown values and tsc's Diagnostics reads were unproven. A call passing the chunk object joins its callee to the birth's unit. --- crates/perry-wpfacts/src/decl_units.rs | 22 +++- .../src/commands/compile/program_facts.rs | 4 + .../program_facts/literal_chunks_tests.rs | 114 ++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 crates/perry/src/commands/compile/program_facts/literal_chunks_tests.rs diff --git a/crates/perry-wpfacts/src/decl_units.rs b/crates/perry-wpfacts/src/decl_units.rs index d224142ce21..4e7db9a1632 100644 --- a/crates/perry-wpfacts/src/decl_units.rs +++ b/crates/perry-wpfacts/src/decl_units.rs @@ -109,6 +109,9 @@ struct Refs { join_names: Vec, join_locals: Vec, join_globals: Vec, + /// The chunk functions of a literal birth it holds (a call passing + /// `Expr::LiteralChunkObject`): joined, they initialize its object. + join_funcs: Vec, /// Locals the item declares (closure parameters here; `declared` adds /// its functions' parameters, `let`s and catch bindings). declared: Vec, @@ -239,6 +242,17 @@ fn expr_refs(e: &Expr, r: &mut Refs) { Expr::GlobalSet(id, _) => r.join_globals.push(*id), Expr::FuncRef(id) => r.funcs.push(*id), Expr::ClassRef(n) | Expr::ExternFuncRef { name: n, .. } => r.names.push(n.clone()), + // A chunk of a literal birth (codegen's literal splitting) is part + // of the birth: its slot inits are the birth's own initializers of + // the unpublished object, which no other unit holds. Cut apart, the + // birth's unit published the chunked slots as unknown values. + Expr::Call { callee, args, .. } + if args.iter().any(|a| matches!(a, Expr::LiteralChunkObject)) => + { + if let Expr::FuncRef(id) = callee.as_ref() { + r.join_funcs.push(*id); + } + } Expr::Closure { captures, mutable_captures, @@ -1299,9 +1313,11 @@ pub(crate) fn decl_units(module: &Module) -> Vec> { free.push((i, *id)); } } - for id in &r.funcs { - if let Some(&j) = by_func.get(id) { - edge(j, false); + for (list, both) in [(&r.funcs, false), (&r.join_funcs, true)] { + for id in list { + if let Some(&j) = by_func.get(id) { + edge(j, both); + } } } for (list, both) in [(&r.globals, false), (&r.join_globals, true)] { diff --git a/crates/perry/src/commands/compile/program_facts.rs b/crates/perry/src/commands/compile/program_facts.rs index 8b9abc3a3f1..8fb822d0718 100644 --- a/crates/perry/src/commands/compile/program_facts.rs +++ b/crates/perry/src/commands/compile/program_facts.rs @@ -526,6 +526,10 @@ mod facts_mode_tests { #[path = "program_facts/outlined_tests.rs"] mod outlined_tests; +#[cfg(test)] +#[path = "program_facts/literal_chunks_tests.rs"] +mod literal_chunks_tests; + /// Borrow ordinary modules; reconstruct only outlined module entries. fn facts_inputs( modules: &BTreeMap, diff --git a/crates/perry/src/commands/compile/program_facts/literal_chunks_tests.rs b/crates/perry/src/commands/compile/program_facts/literal_chunks_tests.rs new file mode 100644 index 00000000000..cd8d6c26b0d --- /dev/null +++ b/crates/perry/src/commands/compile/program_facts/literal_chunks_tests.rs @@ -0,0 +1,114 @@ +//! A generated message table (tsc's `Diagnostics`): one exported literal of +//! many `diag(...)` entries, wide enough that the splitter moves its tail +//! slots into chunk functions (`Expr::LiteralChunks`). The chunks +//! initialize the unpublished birth, so per unit they are part of the +//! birth's unit: another unit's reads of the table prove as in the whole +//! program, and no layout is broken. +use super::*; +use perry_hir::Module; +use perry_wpfacts::{FactsConfig, ProgramFacts, Verdict}; + +const ENTRIES: usize = 800; + +fn lower(path: &str, source: &str) -> Module { + let ast = perry_parser::parse_typescript(source, path).unwrap(); + perry_hir::lower_module(&ast, path, path).unwrap() +} + +fn modules(split: bool) -> BTreeMap { + let types = "export enum Category { Warning, Error }"; + let table = format!( + "import {{ Category }} from \"./types\";\n\ + function diag(code: number, category: Category, key: string, message: string) {{\n\ + \x20 return {{ code, category, key, message }};\n\ + }}\n\ + export const Diagnostics = {{\n{}}};\n", + (0..ENTRIES) + .map(|i| format!(" M{i}: diag({i}, Category.Error, \"M{i}_{i}\", \"message {i}.\"),\n")) + .collect::() + ); + let reader = "import { Diagnostics } from \"./diag\";\n\ + function size(m: { code: number; message: string }) { return m.code + m.message.length; }\n\ + export function run() { return Diagnostics.M5.code + Diagnostics.M700.code + size(Diagnostics.M400); }\n\ + console.log(run());\n"; + let mut out = BTreeMap::new(); + for (p, src) in [("types", types), ("diag", table.as_str()), ("reader", reader)] { + let path = format!("/fixture/{p}.ts"); + let mut m = lower(&path, src); + for imp in &mut m.imports { + imp.resolved_path = Some(format!("/fixture{}.ts", &imp.source[1..])); + } + if split { + perry_codegen::codegen::split_regions::split_large_bodies(&mut m); + } + out.insert(PathBuf::from(path), m); + } + out +} + +fn facts(split: bool, units: bool) -> ProgramFacts { + let modules = modules(split); + if split { + let d = &modules[Path::new("/fixture/diag.ts")]; + assert!( + d.functions.len() > 2, + "the table must be chunked: {} functions", + d.functions.len() + ); + } + let inputs = facts_inputs(&modules); + let list = inputs + .iter() + .map(|(p, m)| (p.clone(), m.as_ref())) + .collect::>(); + let cfg = FactsConfig { + max_work: Some(200_000_000), + ..Default::default() + }; + let pf = if units { + perry_wpfacts::compute_units(&list, &cfg) + } else { + perry_wpfacts::compute(&list, &cfg) + }; + assert!(!pf.stats.aborted, "{}", pf.dump_text()); + pf +} + +/// The reader's sites of `key`, each proven. +fn proven(pf: &ProgramFacts, key: &str) -> bool { + let reader = pf + .module_paths + .iter() + .position(|p| p == "/fixture/reader.ts") + .unwrap() as u32; + let sites: Vec<_> = pf + .sites + .iter() + .filter(|s| s.module == reader && s.key == key) + .collect(); + assert!(!sites.is_empty(), "no reader site {key}\n{}", pf.dump_text()); + sites + .iter() + .all(|s| matches!(s.verdict, Verdict::T0Mono | Verdict::T0Set)) +} + +#[test] +fn a_chunked_table_proves_per_unit_as_whole() { + let whole = facts(true, false); + let units = facts(true, true); + // A head slot, a chunked slot, and fields of their values. + for k in ["M5", "M700", "M400", "code", "message"] { + assert!(proven(&whole, k), "whole {k}\n{}", whole.dump_text()); + assert!(proven(&units, k), "per unit {k}: broken {:?}\n{}", units.broken, units.dump_text()); + } + assert!(units.broken.is_empty(), "{:?}", units.broken); +} + +/// The control: unchunked, per unit already proves the table's reads. +#[test] +fn an_unchunked_table_proves_per_unit() { + let units = facts(false, true); + for k in ["M5", "M700", "M400", "code", "message"] { + assert!(proven(&units, k), "per unit {k}: broken {:?}\n{}", units.broken, units.dump_text()); + } +} From 6204206fe7f0be7eb7940e5b5881905dff4bb3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 09:34:12 +0000 Subject: [PATCH 062/144] wpfacts: explain names the unit that reports a contract A census report breaks a layout for every reader, and once broken the readers no longer report it, so the final outputs cannot say who broke it. The explain line now names the reporting unit. --- crates/perry-wpfacts/src/gen.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index d53c9f225f6..64da7c243ba 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -715,8 +715,9 @@ impl<'a> Gen<'a> { why.push(format!("{d}: {}", rs.join("; "))); } why.sort(); + let unit = self.view.map_or("", |v| v.key.as_str()); for w in why { - eprintln!("perry: wpfacts explain: unit reports {w}"); + eprintln!("perry: wpfacts explain: unit {unit} reports {w}"); } } for ((name, _), &(_, t)) in &self.stand_in_fns { @@ -757,7 +758,8 @@ impl<'a> Gen<'a> { .get(t) .map(|&(_, r)| self.m.reasons[r as usize].clone()); eprintln!( - "perry: wpfacts explain: unit reports {rep}: written {written} escaped {esc:?} hard {hard:?}" + "perry: wpfacts explain: unit {} reports {rep}: written {written} escaped {esc:?} hard {hard:?}", + self.view.map_or("", |v| v.key.as_str()) ); } out.push(rep.clone()); From d98608d579682deafed8e9f7db9788f780258abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 10:17:28 +0000 Subject: [PATCH 063/144] changelog: DGL literal chunks in the birth unit --- changelog.d/0-dgl-literal-chunks.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/0-dgl-literal-chunks.md diff --git a/changelog.d/0-dgl-literal-chunks.md b/changelog.d/0-dgl-literal-chunks.md new file mode 100644 index 00000000000..c25644f4bfe --- /dev/null +++ b/changelog.d/0-dgl-literal-chunks.md @@ -0,0 +1 @@ +Per-unit facts: the chunk functions of a giant literal (codegen literal splitting) are in the birth's unit, so the calls in its chunked slots are decided as in the whole program (tscwork +2,071 proven). From 42873d26a4ba57341f322d3eb8366c926c8cab12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 11:19:33 +0100 Subject: [PATCH 064/144] changelog: number the PR fragment --- .../{0-dgl-literal-chunks.md => 13620-dgl-literal-chunks.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-dgl-literal-chunks.md => 13620-dgl-literal-chunks.md} (100%) diff --git a/changelog.d/0-dgl-literal-chunks.md b/changelog.d/13620-dgl-literal-chunks.md similarity index 100% rename from changelog.d/0-dgl-literal-chunks.md rename to changelog.d/13620-dgl-literal-chunks.md From 44d7b6567c42676d8367da3965bbaa589c732501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 10:31:54 +0000 Subject: [PATCH 065/144] hir: Expr::clone without recursion per nesting level; run-once view copies each part once The derived Expr::clone recursed into every sub-expression with the frame of a match over all 621 variants (15.7 KB per level in a debug build): the 64-operand chain of a_closed_local_called_only_in_its_unit_costs_its_own_stores took about 1 MB to copy in decl_units::flatten and overflowed CI test threads. Expr::clone (walker/expr_clone.rs) now walks in post order with explicit stacks: shallow() copies a node with a hole at each child the walkers visit (one arm per variant), and walk_expr_children_mut fills the holes with the children's copies. It asserts one hole per child and that every visited field is a hole. The run-once view must be a copy (the solve reads it, codegen emits the original), but it no longer copies the module's statements before replacing them, nor the functions it flattens; a flattened literal moves out of its let into the view, and a literal called where it stands is borrowed. --- changelog.d/0-flx-flatten-no-deep-clone.md | 12 + crates/perry-hir/src/ir/expr.rs | 5 +- crates/perry-hir/src/walker/expr_clone.rs | 2555 +++++++++++++++++ crates/perry-hir/src/walker/mod.rs | 8 +- crates/perry-hir/tests/expr_clone.rs | 102 + crates/perry-wpfacts/src/decl_units.rs | 198 +- .../src/tests/run_once_closed.rs | 34 + 7 files changed, 2874 insertions(+), 40 deletions(-) create mode 100644 changelog.d/0-flx-flatten-no-deep-clone.md create mode 100644 crates/perry-hir/src/walker/expr_clone.rs create mode 100644 crates/perry-hir/tests/expr_clone.rs diff --git a/changelog.d/0-flx-flatten-no-deep-clone.md b/changelog.d/0-flx-flatten-no-deep-clone.md new file mode 100644 index 00000000000..7a41119bbc5 --- /dev/null +++ b/changelog.d/0-flx-flatten-no-deep-clone.md @@ -0,0 +1,12 @@ +Copying an HIR expression no longer recurses once per nesting level. The +derived `Expr::clone` recursed into every sub-expression with the frame of a +match over all 621 variants (15.7 KB per level in a debug build), so a +64-operand chain `o.k0 + … + o.k63` took about a megabyte of stack to copy. +The run-once view of per-unit facts copies the bodies it flattens, and the +test `a_closed_local_called_only_in_its_unit_costs_its_own_stores` overflowed +the 2 MB test thread in CI. `Expr::clone` now walks the tree in post order +with explicit stacks: each node is copied once with a hole at each child the +HIR walkers visit, and the holes take the children's copies. The view itself +no longer copies the module's statements before replacing them, nor the +functions it flattens, and a function literal it flattens moves into the view +instead of being copied twice. diff --git a/crates/perry-hir/src/ir/expr.rs b/crates/perry-hir/src/ir/expr.rs index b6fdf8ddc36..635691a53a5 100644 --- a/crates/perry-hir/src/ir/expr.rs +++ b/crates/perry-hir/src/ir/expr.rs @@ -52,8 +52,9 @@ pub enum ClassFreshStaticInit { Block(u32), } -/// Expression -#[derive(Debug, Clone)] +/// Expression. Its `Clone` (`walker/expr_clone.rs`) copies without +/// recursion per nesting level. +#[derive(Debug)] pub enum Expr { // Literals Undefined, diff --git a/crates/perry-hir/src/walker/expr_clone.rs b/crates/perry-hir/src/walker/expr_clone.rs new file mode 100644 index 00000000000..54a0514a8ba --- /dev/null +++ b/crates/perry-hir/src/walker/expr_clone.rs @@ -0,0 +1,2555 @@ +//! `Expr::clone` without recursion per nesting level. +//! +//! A derived clone of `Expr` recursed once per sub-expression, with the +//! frame of a match over every variant at each level (15.7 KB in a debug +//! build), so copying a left-deep chain such as `o.k0 + … + o.k63` took a +//! megabyte of stack. Whole bodies are copied for real: the run-once view of +//! per-unit facts (`perry_wpfacts::decl_units::flatten`) is a module beside +//! the one codegen emits, and lowering and codegen copy closure literals. +//! +//! This clone walks the tree in post order with explicit stacks. Each node +//! is copied once by [`shallow`], which copies its own fields and leaves a +//! hole (`Expr::Undefined`) at every direct sub-expression the walkers visit, +//! and the holes take the copies of its children in the walkers' order +//! ([`walk_expr_children`] to list them, [`walk_expr_children_mut`] to fill +//! them: the two visit the same fields in the same order). A closure's body +//! is a statement list, which the walkers do not enter: `shallow` copies it +//! with `Stmt::clone`, whose expressions come back here, so the stack grows +//! per nested body, not per operand. +//! +//! `shallow` has an arm per variant (no catch-all: a new variant is a +//! compile error here, as in the walkers). A sub-expression field (`Expr`, +//! `Box`, `Option>`, the lists of them, the expressions of +//! `CallArg`, `ArrayElement` and a parameter's default) is a hole; every +//! other field is cloned. Every copy checks that it made one hole per child +//! the walkers visit and that each visited field is a hole. + +use super::*; + +impl Clone for Expr { + fn clone(&self) -> Expr { + let mut kids = 0; + walk_expr_children(self, &mut |_| kids += 1); + if kids == 0 { + return copy_node(self, 0, &mut Vec::new()); + } + // Post order is the reverse of a pre order that takes the children + // last to first; `order` holds each node with its number of children. + let mut todo: Vec<&Expr> = vec![self]; + let mut order: Vec<(&Expr, usize)> = Vec::new(); + while let Some(e) = todo.pop() { + let before = todo.len(); + walk_expr_children(e, &mut |c| todo.push(c)); + order.push((e, todo.len() - before)); + } + // The copies of the nodes done whose parent is not yet: a node's + // children are the last ones, first to last. + let mut done: Vec = Vec::new(); + for &(e, kids) in order.iter().rev() { + let copy = copy_node(e, kids, &mut done); + done.push(copy); + } + debug_assert_eq!(done.len(), 1); + done.pop().expect("the copy of the root") + } +} + +/// The copy of `e`, its `kids` children's copies the last ones of `done` +/// (taken from it). +fn copy_node(e: &Expr, kids: usize, done: &mut Vec) -> Expr { + let mut holes = 0; + let mut copy = shallow(e, &mut holes); + assert_eq!( + holes, kids, + "internal compiler error: Expr::clone: the shallow copy has {holes} holes, the walkers visit {kids} children" + ); + if kids > 0 { + let mut copies = done.drain(done.len() - kids..); + walk_expr_children_mut(&mut copy, &mut |slot| { + assert!( + matches!(slot, Expr::Undefined), + "internal compiler error: Expr::clone: a child the walkers visit is no hole of the shallow copy" + ); + *slot = copies.next().expect("a copy per child"); + }); + } + copy +} + +/// A field of a node in its shallow copy: a hole for each sub-expression +/// (counted in `n`), the rest cloned. +trait Shallow { + fn shallow(&self, n: &mut usize) -> Self; +} + +fn hole(n: &mut usize) -> Expr { + *n += 1; + Expr::Undefined +} + +impl Shallow for Expr { + fn shallow(&self, n: &mut usize) -> Expr { + hole(n) + } +} + +impl Shallow for Box { + fn shallow(&self, n: &mut usize) -> Box { + Box::new(hole(n)) + } +} + +impl Shallow for Option> { + fn shallow(&self, n: &mut usize) -> Option> { + self.as_ref().map(|_| Box::new(hole(n))) + } +} + +impl Shallow for Vec { + fn shallow(&self, n: &mut usize) -> Vec { + self.iter().map(|_| hole(n)).collect() + } +} + +impl Shallow for Vec<(K, Expr)> { + fn shallow(&self, n: &mut usize) -> Vec<(K, Expr)> { + self.iter().map(|(k, _)| (k.clone(), hole(n))).collect() + } +} + +impl Shallow for Vec<(K, Box)> { + fn shallow(&self, n: &mut usize) -> Vec<(K, Box)> { + self.iter() + .map(|(k, _)| (k.clone(), Box::new(hole(n)))) + .collect() + } +} + +impl Shallow for Vec { + fn shallow(&self, n: &mut usize) -> Vec { + self.iter() + .map(|a| match a { + CallArg::Expr(_) => CallArg::Expr(hole(n)), + CallArg::Spread(_) => CallArg::Spread(hole(n)), + }) + .collect() + } +} + +impl Shallow for Vec { + fn shallow(&self, n: &mut usize) -> Vec { + self.iter() + .map(|a| match a { + ArrayElement::Expr(_) => ArrayElement::Expr(hole(n)), + ArrayElement::Hole => ArrayElement::Hole, + ArrayElement::Spread(_) => ArrayElement::Spread(hole(n)), + }) + .collect() + } +} + +/// A closure's parameters: the walkers visit each default. +impl Shallow for Vec { + fn shallow(&self, n: &mut usize) -> Vec { + self.iter() + .map(|p| { + let Param { + id, + name, + ty, + default, + decorators, + is_rest, + arguments_object, + } = p; + Param { + id: *id, + name: name.clone(), + ty: ty.clone(), + default: default.as_ref().map(|_| hole(n)), + decorators: decorators.clone(), + is_rest: *is_rest, + arguments_object: arguments_object.clone(), + } + }) + .collect() + } +} + +/// The copy of `e` with a hole (counted in `n`) at each sub-expression. +#[allow(clippy::clone_on_copy)] +fn shallow(e: &Expr, n: &mut usize) -> Expr { + match e { + Expr::Undefined => Expr::Undefined, + Expr::Null => Expr::Null, + Expr::Bool(a0) => Expr::Bool(a0.clone()), + Expr::Number(a0) => Expr::Number(a0.clone()), + Expr::Integer(a0) => Expr::Integer(a0.clone()), + Expr::BigInt(a0) => Expr::BigInt(a0.clone()), + Expr::String(a0) => Expr::String(a0.clone()), + Expr::WtfString(a0) => Expr::WtfString(a0.clone()), + Expr::I18nString { + key, + string_idx, + params, + plural_forms, + plural_param, + } => Expr::I18nString { + key: key.clone(), + string_idx: string_idx.clone(), + params: Shallow::shallow(params, n), + plural_forms: plural_forms.clone(), + plural_param: plural_param.clone(), + }, + Expr::LocalGet(a0) => Expr::LocalGet(a0.clone()), + Expr::LocalSet(a0, a1) => Expr::LocalSet(a0.clone(), Shallow::shallow(a1, n)), + Expr::ScopedTemp { id, value, body } => Expr::ScopedTemp { + id: id.clone(), + value: Shallow::shallow(value, n), + body: Shallow::shallow(body, n), + }, + Expr::GlobalGet(a0) => Expr::GlobalGet(a0.clone()), + Expr::GlobalSet(a0, a1) => Expr::GlobalSet(a0.clone(), Shallow::shallow(a1, n)), + Expr::WithGet { + object, + property, + fallback, + } => Expr::WithGet { + object: Shallow::shallow(object, n), + property: property.clone(), + fallback: Shallow::shallow(fallback, n), + }, + Expr::WithSet { + object, + property, + value, + fallback, + strict, + } => Expr::WithSet { + object: Shallow::shallow(object, n), + property: property.clone(), + value: Shallow::shallow(value, n), + fallback: fallback.clone(), + strict: strict.clone(), + }, + Expr::Update { id, op, prefix } => Expr::Update { + id: id.clone(), + op: op.clone(), + prefix: prefix.clone(), + }, + Expr::Binary { op, left, right } => Expr::Binary { + op: op.clone(), + left: Shallow::shallow(left, n), + right: Shallow::shallow(right, n), + }, + Expr::Unary { op, operand } => Expr::Unary { + op: op.clone(), + operand: Shallow::shallow(operand, n), + }, + Expr::Compare { op, left, right } => Expr::Compare { + op: op.clone(), + left: Shallow::shallow(left, n), + right: Shallow::shallow(right, n), + }, + Expr::Logical { op, left, right } => Expr::Logical { + op: op.clone(), + left: Shallow::shallow(left, n), + right: Shallow::shallow(right, n), + }, + Expr::Call { + callee, + args, + type_args, + byte_offset, + byte_end, + } => Expr::Call { + callee: Shallow::shallow(callee, n), + args: Shallow::shallow(args, n), + type_args: type_args.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::CallSpread { + callee, + args, + type_args, + byte_offset, + byte_end, + } => Expr::CallSpread { + callee: Shallow::shallow(callee, n), + args: Shallow::shallow(args, n), + type_args: type_args.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::SuperCallSpread(a0) => Expr::SuperCallSpread(Shallow::shallow(a0, n)), + Expr::FuncRef(a0) => Expr::FuncRef(a0.clone()), + Expr::ExternFuncRef { + name, + param_types, + return_type, + } => Expr::ExternFuncRef { + name: name.clone(), + param_types: param_types.clone(), + return_type: return_type.clone(), + }, + Expr::NativeModuleRef(a0) => Expr::NativeModuleRef(a0.clone()), + Expr::NativeMethodCall { + module, + class_name, + object, + method, + args, + } => Expr::NativeMethodCall { + module: module.clone(), + class_name: class_name.clone(), + object: Shallow::shallow(object, n), + method: method.clone(), + args: Shallow::shallow(args, n), + }, + Expr::PropertyGet { + object, + property, + byte_offset, + byte_end, + } => Expr::PropertyGet { + object: Shallow::shallow(object, n), + property: property.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::PropertySet { + object, + property, + value, + byte_offset, + byte_end, + } => Expr::PropertySet { + object: Shallow::shallow(object, n), + property: property.clone(), + value: Shallow::shallow(value, n), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::PropertyUpdate { + object, + property, + op, + prefix, + strict, + byte_offset, + byte_end, + } => Expr::PropertyUpdate { + object: Shallow::shallow(object, n), + property: property.clone(), + op: op.clone(), + prefix: prefix.clone(), + strict: strict.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::IndexGet { + object, + index, + byte_offset, + byte_end, + } => Expr::IndexGet { + object: Shallow::shallow(object, n), + index: Shallow::shallow(index, n), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::IndexSet { + object, + index, + value, + byte_offset, + byte_end, + } => Expr::IndexSet { + object: Shallow::shallow(object, n), + index: Shallow::shallow(index, n), + value: Shallow::shallow(value, n), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::IndexUpdate { + object, + index, + op, + prefix, + strict, + byte_offset, + byte_end, + } => Expr::IndexUpdate { + object: Shallow::shallow(object, n), + index: Shallow::shallow(index, n), + op: op.clone(), + prefix: prefix.clone(), + strict: strict.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::Object(a0) => Expr::Object(Shallow::shallow(a0, n)), + Expr::LiteralChunks(a0) => Expr::LiteralChunks(Shallow::shallow(a0, n)), + Expr::LiteralChunkObject => Expr::LiteralChunkObject, + Expr::LiteralSlotInit { + object, + key, + slot, + value, + } => Expr::LiteralSlotInit { + object: Shallow::shallow(object, n), + key: key.clone(), + slot: slot.clone(), + value: Shallow::shallow(value, n), + }, + Expr::ObjectSpread { parts } => Expr::ObjectSpread { + parts: Shallow::shallow(parts, n), + }, + Expr::ObjectAssign { target, sources } => Expr::ObjectAssign { + target: Shallow::shallow(target, n), + sources: Shallow::shallow(sources, n), + }, + Expr::Array(a0, a1) => Expr::Array(Shallow::shallow(a0, n), a1.clone()), + Expr::ArraySpread(a0) => Expr::ArraySpread(Shallow::shallow(a0, n)), + Expr::Conditional { + condition, + then_expr, + else_expr, + } => Expr::Conditional { + condition: Shallow::shallow(condition, n), + then_expr: Shallow::shallow(then_expr, n), + else_expr: Shallow::shallow(else_expr, n), + }, + Expr::TypeOf(a0) => Expr::TypeOf(Shallow::shallow(a0, n)), + Expr::Void(a0) => Expr::Void(Shallow::shallow(a0, n)), + Expr::InstanceOf { expr, ty, ty_expr } => Expr::InstanceOf { + expr: Shallow::shallow(expr, n), + ty: ty.clone(), + ty_expr: Shallow::shallow(ty_expr, n), + }, + Expr::In { property, object } => Expr::In { + property: Shallow::shallow(property, n), + object: Shallow::shallow(object, n), + }, + Expr::PrivateLexicalBrand(a0) => Expr::PrivateLexicalBrand(Shallow::shallow(a0, n)), + Expr::PrivateLexicalBrandPush(a0) => Expr::PrivateLexicalBrandPush(Shallow::shallow(a0, n)), + Expr::PrivateLexicalBrandPop => Expr::PrivateLexicalBrandPop, + Expr::PrivateBrandCheck { + class_name, + class_id, + field_name, + kind, + is_static, + receiver_is_brand_owner, + object, + } => Expr::PrivateBrandCheck { + class_name: class_name.clone(), + class_id: class_id.clone(), + field_name: field_name.clone(), + kind: kind.clone(), + is_static: is_static.clone(), + receiver_is_brand_owner: receiver_is_brand_owner.clone(), + object: Shallow::shallow(object, n), + }, + Expr::PrivateGuard { + class_name, + class_id, + field_name, + kind, + op, + receiver_is_brand_owner, + object, + } => Expr::PrivateGuard { + class_name: class_name.clone(), + class_id: class_id.clone(), + field_name: field_name.clone(), + kind: kind.clone(), + op: op.clone(), + receiver_is_brand_owner: receiver_is_brand_owner.clone(), + object: Shallow::shallow(object, n), + }, + Expr::Await(a0) => Expr::Await(Shallow::shallow(a0, n)), + Expr::Yield { value, delegate } => Expr::Yield { + value: Shallow::shallow(value, n), + delegate: delegate.clone(), + }, + Expr::New { + class_name, + args, + type_args, + byte_offset, + cap_args_appended, + byte_end, + } => Expr::New { + class_name: class_name.clone(), + args: Shallow::shallow(args, n), + type_args: type_args.clone(), + byte_offset: byte_offset.clone(), + cap_args_appended: cap_args_appended.clone(), + byte_end: byte_end.clone(), + }, + Expr::NewDynamic { + callee, + args, + byte_offset, + byte_end, + } => Expr::NewDynamic { + callee: Shallow::shallow(callee, n), + args: Shallow::shallow(args, n), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::NewDynamicSpread { + callee, + args, + byte_offset, + byte_end, + } => Expr::NewDynamicSpread { + callee: Shallow::shallow(callee, n), + args: Shallow::shallow(args, n), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::NewTarget => Expr::NewTarget, + Expr::ClassRef(a0) => Expr::ClassRef(a0.clone()), + Expr::ClassMethodHome { + class_name, + is_static, + } => Expr::ClassMethodHome { + class_name: class_name.clone(), + is_static: is_static.clone(), + }, + Expr::EnumMember { + enum_name, + member_name, + } => Expr::EnumMember { + enum_name: enum_name.clone(), + member_name: member_name.clone(), + }, + Expr::EnumImage { + class_name, + stores, + strict, + } => Expr::EnumImage { + class_name: class_name.clone(), + stores: stores.clone(), + strict: strict.clone(), + }, + Expr::StaticFieldGet { + class_name, + field_name, + } => Expr::StaticFieldGet { + class_name: class_name.clone(), + field_name: field_name.clone(), + }, + Expr::StaticFieldSet { + class_name, + field_name, + value, + } => Expr::StaticFieldSet { + class_name: class_name.clone(), + field_name: field_name.clone(), + value: Shallow::shallow(value, n), + }, + Expr::ClassStaticSymbolSet { + class_name, + key, + value, + } => Expr::ClassStaticSymbolSet { + class_name: class_name.clone(), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + }, + Expr::RegisterClassParentDynamic { + class_name, + parent_expr, + } => Expr::RegisterClassParentDynamic { + class_name: class_name.clone(), + parent_expr: Shallow::shallow(parent_expr, n), + }, + Expr::RegisterClassCaptures { + class_name, + captures, + } => Expr::RegisterClassCaptures { + class_name: class_name.clone(), + captures: Shallow::shallow(captures, n), + }, + Expr::RefreshClassExprCaptures { + class_value, + captures, + env_class, + } => Expr::RefreshClassExprCaptures { + class_value: Shallow::shallow(class_value, n), + captures: Shallow::shallow(captures, n), + env_class: env_class.clone(), + }, + Expr::ClassEnvGet { + class_name, + index, + guarded, + } => Expr::ClassEnvGet { + class_name: class_name.clone(), + index: index.clone(), + guarded: guarded.clone(), + }, + Expr::ClassEnvSet { + class_name, + index, + value, + guarded, + publish, + } => Expr::ClassEnvSet { + class_name: class_name.clone(), + index: index.clone(), + value: Shallow::shallow(value, n), + guarded: guarded.clone(), + publish: publish.clone(), + }, + Expr::ClassEnvStamp { + class_name, + instance, + evaluation, + } => Expr::ClassEnvStamp { + class_name: class_name.clone(), + instance: Shallow::shallow(instance, n), + evaluation: Shallow::shallow(evaluation, n), + }, + Expr::ClassEnvCurrent { class_name } => Expr::ClassEnvCurrent { + class_name: class_name.clone(), + }, + Expr::ClassCaptureValue { + class_name, + index, + fallback, + prefer_fallback, + } => Expr::ClassCaptureValue { + class_name: class_name.clone(), + index: index.clone(), + fallback: Shallow::shallow(fallback, n), + prefer_fallback: prefer_fallback.clone(), + }, + Expr::RegisterClassStaticSymbol { + class_name, + key_expr, + value_expr, + } => Expr::RegisterClassStaticSymbol { + class_name: class_name.clone(), + key_expr: Shallow::shallow(key_expr, n), + value_expr: Shallow::shallow(value_expr, n), + }, + Expr::RegisterClassComputedMethod { + class_name, + key_expr, + method_name, + is_static, + param_count, + has_rest, + definition_order, + } => Expr::RegisterClassComputedMethod { + class_name: class_name.clone(), + key_expr: Shallow::shallow(key_expr, n), + method_name: method_name.clone(), + is_static: is_static.clone(), + param_count: param_count.clone(), + has_rest: has_rest.clone(), + definition_order: definition_order.clone(), + }, + Expr::RegisterClassComputedAccessor { + class_name, + key_expr, + getter_name, + setter_name, + is_static, + definition_order, + } => Expr::RegisterClassComputedAccessor { + class_name: class_name.clone(), + key_expr: Shallow::shallow(key_expr, n), + getter_name: getter_name.clone(), + setter_name: setter_name.clone(), + is_static: is_static.clone(), + definition_order: definition_order.clone(), + }, + Expr::ClassExprFresh { + template, + evaluation_owner, + named_statics, + computed_keys, + computed_statics, + static_init_order, + captured_args, + shared_first_evaluation, + evaluated_parent, + definition_steps, + } => Expr::ClassExprFresh { + template: template.clone(), + evaluation_owner: evaluation_owner.clone(), + named_statics: Shallow::shallow(named_statics, n), + computed_keys: Shallow::shallow(computed_keys, n), + computed_statics: Shallow::shallow(computed_statics, n), + static_init_order: static_init_order.clone(), + captured_args: Shallow::shallow(captured_args, n), + shared_first_evaluation: Shallow::shallow(shared_first_evaluation, n), + evaluated_parent: Shallow::shallow(evaluated_parent, n), + definition_steps: Shallow::shallow(definition_steps, n), + }, + Expr::ClassIsFirstEvaluation { value, template } => Expr::ClassIsFirstEvaluation { + value: Shallow::shallow(value, n), + template: template.clone(), + }, + Expr::SetFunctionPrototype { + func, + proto, + strict, + } => Expr::SetFunctionPrototype { + func: Shallow::shallow(func, n), + proto: Shallow::shallow(proto, n), + strict: strict.clone(), + }, + Expr::RegisterPrototypeMethod { + class_name, + method_name, + value, + } => Expr::RegisterPrototypeMethod { + class_name: class_name.clone(), + method_name: method_name.clone(), + value: Shallow::shallow(value, n), + }, + Expr::RegisterFunctionPrototypeMethod { + func, + method_name, + value, + } => Expr::RegisterFunctionPrototypeMethod { + func: Shallow::shallow(func, n), + method_name: method_name.clone(), + value: Shallow::shallow(value, n), + }, + Expr::GetFunctionPrototypeMethod { func, method_name } => { + Expr::GetFunctionPrototypeMethod { + func: Shallow::shallow(func, n), + method_name: method_name.clone(), + } + } + Expr::StaticMethodCall { + class_name, + method_name, + args, + } => Expr::StaticMethodCall { + class_name: class_name.clone(), + method_name: method_name.clone(), + args: Shallow::shallow(args, n), + }, + Expr::This => Expr::This, + Expr::SuperCall(a0) => Expr::SuperCall(Shallow::shallow(a0, n)), + Expr::SuperMethodCall { method, args } => Expr::SuperMethodCall { + method: method.clone(), + args: Shallow::shallow(args, n), + }, + Expr::SuperMethodCallSpread { method, args } => Expr::SuperMethodCallSpread { + method: method.clone(), + args: Shallow::shallow(args, n), + }, + Expr::SuperPropertyGet { property } => Expr::SuperPropertyGet { + property: property.clone(), + }, + Expr::SuperPropertySet { + parent_class_id, + parent_class_name, + key, + value, + } => Expr::SuperPropertySet { + parent_class_id: parent_class_id.clone(), + parent_class_name: parent_class_name.clone(), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + }, + Expr::ObjectSuperPropertyGet { + home, + key, + receiver, + } => Expr::ObjectSuperPropertyGet { + home: Shallow::shallow(home, n), + key: Shallow::shallow(key, n), + receiver: Shallow::shallow(receiver, n), + }, + Expr::ObjectSuperPropertySet { + home, + key, + value, + receiver, + } => Expr::ObjectSuperPropertySet { + home: Shallow::shallow(home, n), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + receiver: Shallow::shallow(receiver, n), + }, + Expr::ObjectSuperMethodCall { + home, + key, + receiver, + args, + } => Expr::ObjectSuperMethodCall { + home: Shallow::shallow(home, n), + key: Shallow::shallow(key, n), + receiver: Shallow::shallow(receiver, n), + args: Shallow::shallow(args, n), + }, + Expr::EnvGet(a0) => Expr::EnvGet(a0.clone()), + Expr::EnvGetDynamic(a0) => Expr::EnvGetDynamic(Shallow::shallow(a0, n)), + Expr::ProcessEnv => Expr::ProcessEnv, + Expr::GlobalThisExpr => Expr::GlobalThisExpr, + Expr::NamespacePatched(a0) => Expr::NamespacePatched(a0.clone()), + Expr::ModuleTopThis => Expr::ModuleTopThis, + Expr::ProcessUptime => Expr::ProcessUptime, + Expr::ProcessCwd => Expr::ProcessCwd, + Expr::ProcessArgv => Expr::ProcessArgv, + Expr::ProcessMemoryUsage => Expr::ProcessMemoryUsage, + Expr::ProcessPid => Expr::ProcessPid, + Expr::ProcessPpid => Expr::ProcessPpid, + Expr::ProcessVersion => Expr::ProcessVersion, + Expr::ProcessVersions => Expr::ProcessVersions, + Expr::ProcessHrtimeBigint => Expr::ProcessHrtimeBigint, + Expr::ProcessHrtime(a0) => Expr::ProcessHrtime(Shallow::shallow(a0, n)), + Expr::ProcessNextTick { callback, args } => Expr::ProcessNextTick { + callback: Shallow::shallow(callback, n), + args: Shallow::shallow(args, n), + }, + Expr::ProcessOn { event, handler } => Expr::ProcessOn { + event: Shallow::shallow(event, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProcessOnce { event, handler } => Expr::ProcessOnce { + event: Shallow::shallow(event, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProcessChdir(a0) => Expr::ProcessChdir(Shallow::shallow(a0, n)), + Expr::ProcessKill { pid, signal } => Expr::ProcessKill { + pid: Shallow::shallow(pid, n), + signal: Shallow::shallow(signal, n), + }, + Expr::ProcessExit(a0) => Expr::ProcessExit(Shallow::shallow(a0, n)), + Expr::ProcessAbort => Expr::ProcessAbort, + Expr::ProcessUmask(a0) => Expr::ProcessUmask(Shallow::shallow(a0, n)), + Expr::ProcessThreadCpuUsage(a0) => Expr::ProcessThreadCpuUsage(Shallow::shallow(a0, n)), + Expr::ProcessAvailableMemory => Expr::ProcessAvailableMemory, + Expr::ProcessConstrainedMemory => Expr::ProcessConstrainedMemory, + Expr::ProcessPosixCredential(a0) => Expr::ProcessPosixCredential(a0.clone()), + Expr::ProcessEmitWarning(a0) => Expr::ProcessEmitWarning(Shallow::shallow(a0, n)), + Expr::ProcessCpuUsage(a0) => Expr::ProcessCpuUsage(Shallow::shallow(a0, n)), + Expr::ProcessResourceUsage => Expr::ProcessResourceUsage, + Expr::ProcessActiveResourcesInfo => Expr::ProcessActiveResourcesInfo, + Expr::ProcessTitle => Expr::ProcessTitle, + Expr::ProcessSetTitle(a0) => Expr::ProcessSetTitle(Shallow::shallow(a0, n)), + Expr::ProcessStdin => Expr::ProcessStdin, + Expr::ProcessStdout => Expr::ProcessStdout, + Expr::ProcessStderr => Expr::ProcessStderr, + Expr::ProcessStdinSetRawMode(a0) => Expr::ProcessStdinSetRawMode(Shallow::shallow(a0, n)), + Expr::ProcessStdinOn { event, handler } => Expr::ProcessStdinOn { + event: Shallow::shallow(event, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProcessStdinRemoveListener { event, handler } => Expr::ProcessStdinRemoveListener { + event: Shallow::shallow(event, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProcessStdinLifecycle(a0) => Expr::ProcessStdinLifecycle(a0.clone()), + Expr::ProcessStdoutOn { event, handler } => Expr::ProcessStdoutOn { + event: Shallow::shallow(event, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProcessStdinIsTTY => Expr::ProcessStdinIsTTY, + Expr::ProcessStdoutIsTTY => Expr::ProcessStdoutIsTTY, + Expr::ProcessStderrIsTTY => Expr::ProcessStderrIsTTY, + Expr::ProcessStdoutColumns => Expr::ProcessStdoutColumns, + Expr::ProcessStdoutRows => Expr::ProcessStdoutRows, + Expr::TtyIsAtty(a0) => Expr::TtyIsAtty(Shallow::shallow(a0, n)), + Expr::FsReadFileSync(a0) => Expr::FsReadFileSync(Shallow::shallow(a0, n)), + Expr::FsWriteFileSync(a0, a1) => { + Expr::FsWriteFileSync(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::FsExistsSync(a0) => Expr::FsExistsSync(Shallow::shallow(a0, n)), + Expr::FsMkdirSync(a0) => Expr::FsMkdirSync(Shallow::shallow(a0, n)), + Expr::FsUnlinkSync(a0) => Expr::FsUnlinkSync(Shallow::shallow(a0, n)), + Expr::FsAppendFileSync(a0, a1) => { + Expr::FsAppendFileSync(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::FsReadFileBinary(a0) => Expr::FsReadFileBinary(Shallow::shallow(a0, n)), + Expr::FsRmRecursive(a0) => Expr::FsRmRecursive(Shallow::shallow(a0, n)), + Expr::PathJoin(a0, a1) => Expr::PathJoin(Shallow::shallow(a0, n), Shallow::shallow(a1, n)), + Expr::PathDirname(a0) => Expr::PathDirname(Shallow::shallow(a0, n)), + Expr::PathBasename(a0) => Expr::PathBasename(Shallow::shallow(a0, n)), + Expr::PathBasenameExt(a0, a1) => { + Expr::PathBasenameExt(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::PathExtname(a0) => Expr::PathExtname(Shallow::shallow(a0, n)), + Expr::PathResolve(a0) => Expr::PathResolve(Shallow::shallow(a0, n)), + Expr::PathIsAbsolute(a0) => Expr::PathIsAbsolute(Shallow::shallow(a0, n)), + Expr::PathRelative(a0, a1) => { + Expr::PathRelative(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::PathNormalize(a0) => Expr::PathNormalize(Shallow::shallow(a0, n)), + Expr::PathParse(a0) => Expr::PathParse(Shallow::shallow(a0, n)), + Expr::PathFormat(a0) => Expr::PathFormat(Shallow::shallow(a0, n)), + Expr::PathSep => Expr::PathSep, + Expr::PathDelimiter => Expr::PathDelimiter, + Expr::PathToNamespacedPath(a0) => Expr::PathToNamespacedPath(Shallow::shallow(a0, n)), + Expr::PathMatchesGlob(a0, a1) => { + Expr::PathMatchesGlob(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::PathResolveJoin(a0, a1) => { + Expr::PathResolveJoin(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::PathWin32Join(a0, a1) => { + Expr::PathWin32Join(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::PathWin32 { method, args } => Expr::PathWin32 { + method: method.clone(), + args: Shallow::shallow(args, n), + }, + Expr::WeakRefNew(a0) => Expr::WeakRefNew(Shallow::shallow(a0, n)), + Expr::WeakRefDeref(a0) => Expr::WeakRefDeref(Shallow::shallow(a0, n)), + Expr::FinalizationRegistryNew(a0) => Expr::FinalizationRegistryNew(Shallow::shallow(a0, n)), + Expr::FinalizationRegistryRegister { + registry, + target, + held, + token, + } => Expr::FinalizationRegistryRegister { + registry: Shallow::shallow(registry, n), + target: Shallow::shallow(target, n), + held: Shallow::shallow(held, n), + token: Shallow::shallow(token, n), + }, + Expr::FinalizationRegistryUnregister { registry, token } => { + Expr::FinalizationRegistryUnregister { + registry: Shallow::shallow(registry, n), + token: Shallow::shallow(token, n), + } + } + Expr::ObjectDefineProperty(a0, a1, a2) => Expr::ObjectDefineProperty( + Shallow::shallow(a0, n), + Shallow::shallow(a1, n), + Shallow::shallow(a2, n), + ), + Expr::ObjectGetOwnPropertyDescriptor(a0, a1) => { + Expr::ObjectGetOwnPropertyDescriptor(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::ObjectGetOwnPropertyDescriptors(a0) => { + Expr::ObjectGetOwnPropertyDescriptors(Shallow::shallow(a0, n)) + } + Expr::ObjectGetOwnPropertyNames(a0) => { + Expr::ObjectGetOwnPropertyNames(Shallow::shallow(a0, n)) + } + Expr::ObjectCreate(a0, a1) => { + Expr::ObjectCreate(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::ObjectFreeze(a0) => Expr::ObjectFreeze(Shallow::shallow(a0, n)), + Expr::ObjectSeal(a0) => Expr::ObjectSeal(Shallow::shallow(a0, n)), + Expr::ObjectPreventExtensions(a0) => Expr::ObjectPreventExtensions(Shallow::shallow(a0, n)), + Expr::ObjectIsFrozen(a0) => Expr::ObjectIsFrozen(Shallow::shallow(a0, n)), + Expr::ObjectIsSealed(a0) => Expr::ObjectIsSealed(Shallow::shallow(a0, n)), + Expr::ObjectIsExtensible(a0) => Expr::ObjectIsExtensible(Shallow::shallow(a0, n)), + Expr::ObjectGetPrototypeOf(a0) => Expr::ObjectGetPrototypeOf(Shallow::shallow(a0, n)), + Expr::ObjectSetPrototypeOf(a0, a1) => { + Expr::ObjectSetPrototypeOf(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::ObjectDefineProperties(a0, a1) => { + Expr::ObjectDefineProperties(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::ObjectGetOwnPropertySymbols(a0) => { + Expr::ObjectGetOwnPropertySymbols(Shallow::shallow(a0, n)) + } + Expr::SymbolNew(a0) => Expr::SymbolNew(Shallow::shallow(a0, n)), + Expr::SymbolFor(a0) => Expr::SymbolFor(Shallow::shallow(a0, n)), + Expr::SymbolKeyFor(a0) => Expr::SymbolKeyFor(Shallow::shallow(a0, n)), + Expr::SymbolDescription(a0) => Expr::SymbolDescription(Shallow::shallow(a0, n)), + Expr::RegExpEscape(a0) => Expr::RegExpEscape(Shallow::shallow(a0, n)), + Expr::SymbolToString(a0) => Expr::SymbolToString(Shallow::shallow(a0, n)), + Expr::FileURLToPath(a0) => Expr::FileURLToPath(Shallow::shallow(a0, n)), + Expr::RegExpExecIndex => Expr::RegExpExecIndex, + Expr::RegExpExecGroups => Expr::RegExpExecGroups, + Expr::JsonParse(a0) => Expr::JsonParse(Shallow::shallow(a0, n)), + Expr::JsonParseTyped { + text, + ty, + ordered_keys, + } => Expr::JsonParseTyped { + text: Shallow::shallow(text, n), + ty: ty.clone(), + ordered_keys: ordered_keys.clone(), + }, + Expr::JsonParseReviver { text, reviver } => Expr::JsonParseReviver { + text: Shallow::shallow(text, n), + reviver: Shallow::shallow(reviver, n), + }, + Expr::JsonParseWithReviver(a0, a1) => { + Expr::JsonParseWithReviver(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::JsonStringify(a0) => Expr::JsonStringify(Shallow::shallow(a0, n)), + Expr::JsonStringifyPretty { + value, + replacer, + space, + } => Expr::JsonStringifyPretty { + value: Shallow::shallow(value, n), + replacer: Shallow::shallow(replacer, n), + space: Shallow::shallow(space, n), + }, + Expr::JsonStringifyFull(a0, a1, a2) => Expr::JsonStringifyFull( + Shallow::shallow(a0, n), + Shallow::shallow(a1, n), + Shallow::shallow(a2, n), + ), + Expr::JsonRawJson(a0) => Expr::JsonRawJson(Shallow::shallow(a0, n)), + Expr::JsonIsRawJson(a0) => Expr::JsonIsRawJson(Shallow::shallow(a0, n)), + Expr::MathFloor(a0) => Expr::MathFloor(Shallow::shallow(a0, n)), + Expr::MathCeil(a0) => Expr::MathCeil(Shallow::shallow(a0, n)), + Expr::MathRound(a0) => Expr::MathRound(Shallow::shallow(a0, n)), + Expr::MathTrunc(a0) => Expr::MathTrunc(Shallow::shallow(a0, n)), + Expr::MathSign(a0) => Expr::MathSign(Shallow::shallow(a0, n)), + Expr::MathAbs(a0) => Expr::MathAbs(Shallow::shallow(a0, n)), + Expr::MathSqrt(a0) => Expr::MathSqrt(Shallow::shallow(a0, n)), + Expr::MathLog(a0) => Expr::MathLog(Shallow::shallow(a0, n)), + Expr::MathLog2(a0) => Expr::MathLog2(Shallow::shallow(a0, n)), + Expr::MathLog10(a0) => Expr::MathLog10(Shallow::shallow(a0, n)), + Expr::MathPow(a0, a1) => Expr::MathPow(Shallow::shallow(a0, n), Shallow::shallow(a1, n)), + Expr::MathMin(a0) => Expr::MathMin(Shallow::shallow(a0, n)), + Expr::MathMax(a0) => Expr::MathMax(Shallow::shallow(a0, n)), + Expr::MathMinSpread(a0) => Expr::MathMinSpread(Shallow::shallow(a0, n)), + Expr::MathMaxSpread(a0) => Expr::MathMaxSpread(Shallow::shallow(a0, n)), + Expr::MathImul(a0, a1) => Expr::MathImul(Shallow::shallow(a0, n), Shallow::shallow(a1, n)), + Expr::MathRandom => Expr::MathRandom, + Expr::MathSin(a0) => Expr::MathSin(Shallow::shallow(a0, n)), + Expr::MathCos(a0) => Expr::MathCos(Shallow::shallow(a0, n)), + Expr::MathTan(a0) => Expr::MathTan(Shallow::shallow(a0, n)), + Expr::MathAsin(a0) => Expr::MathAsin(Shallow::shallow(a0, n)), + Expr::MathAcos(a0) => Expr::MathAcos(Shallow::shallow(a0, n)), + Expr::MathAtan(a0) => Expr::MathAtan(Shallow::shallow(a0, n)), + Expr::MathAtan2(a0, a1) => { + Expr::MathAtan2(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::MathCbrt(a0) => Expr::MathCbrt(Shallow::shallow(a0, n)), + Expr::MathHypot(a0) => Expr::MathHypot(Shallow::shallow(a0, n)), + Expr::MathFround(a0) => Expr::MathFround(Shallow::shallow(a0, n)), + Expr::MathF16round(a0) => Expr::MathF16round(Shallow::shallow(a0, n)), + Expr::MathClz32(a0) => Expr::MathClz32(Shallow::shallow(a0, n)), + Expr::MathExpm1(a0) => Expr::MathExpm1(Shallow::shallow(a0, n)), + Expr::MathLog1p(a0) => Expr::MathLog1p(Shallow::shallow(a0, n)), + Expr::MathSinh(a0) => Expr::MathSinh(Shallow::shallow(a0, n)), + Expr::MathCosh(a0) => Expr::MathCosh(Shallow::shallow(a0, n)), + Expr::MathTanh(a0) => Expr::MathTanh(Shallow::shallow(a0, n)), + Expr::MathAsinh(a0) => Expr::MathAsinh(Shallow::shallow(a0, n)), + Expr::MathAcosh(a0) => Expr::MathAcosh(Shallow::shallow(a0, n)), + Expr::MathAtanh(a0) => Expr::MathAtanh(Shallow::shallow(a0, n)), + Expr::MathExp(a0) => Expr::MathExp(Shallow::shallow(a0, n)), + Expr::PerformanceNow => Expr::PerformanceNow, + Expr::WebAssemblyValidate(a0) => Expr::WebAssemblyValidate(Shallow::shallow(a0, n)), + Expr::WebAssemblyCompile(a0) => Expr::WebAssemblyCompile(Shallow::shallow(a0, n)), + Expr::WebAssemblyModuleNew(a0) => Expr::WebAssemblyModuleNew(Shallow::shallow(a0, n)), + Expr::WebAssemblyModuleExports(a0) => { + Expr::WebAssemblyModuleExports(Shallow::shallow(a0, n)) + } + Expr::WebAssemblyModuleImports(a0) => { + Expr::WebAssemblyModuleImports(Shallow::shallow(a0, n)) + } + Expr::WebAssemblyModuleCustomSections { module, name } => { + Expr::WebAssemblyModuleCustomSections { + module: Shallow::shallow(module, n), + name: Shallow::shallow(name, n), + } + } + Expr::WebAssemblyInstantiate { bytes, imports } => Expr::WebAssemblyInstantiate { + bytes: Shallow::shallow(bytes, n), + imports: Shallow::shallow(imports, n), + }, + Expr::WebAssemblyCallExport { + instance, + name, + args, + } => Expr::WebAssemblyCallExport { + instance: Shallow::shallow(instance, n), + name: Shallow::shallow(name, n), + args: Shallow::shallow(args, n), + }, + Expr::Atob(a0) => Expr::Atob(Shallow::shallow(a0, n)), + Expr::Btoa(a0) => Expr::Btoa(Shallow::shallow(a0, n)), + Expr::TextEncoderNew => Expr::TextEncoderNew, + Expr::TextEncoderEncode(a0) => Expr::TextEncoderEncode(Shallow::shallow(a0, n)), + Expr::TextEncoderEncodeInto { source, dest } => Expr::TextEncoderEncodeInto { + source: Shallow::shallow(source, n), + dest: Shallow::shallow(dest, n), + }, + Expr::TextDecoderNew { + label, + fatal, + ignore_bom, + } => Expr::TextDecoderNew { + label: Shallow::shallow(label, n), + fatal: Shallow::shallow(fatal, n), + ignore_bom: Shallow::shallow(ignore_bom, n), + }, + Expr::TextDecoderDecode { decoder, input } => Expr::TextDecoderDecode { + decoder: Shallow::shallow(decoder, n), + input: Shallow::shallow(input, n), + }, + Expr::TextDecoderEncoding(a0) => Expr::TextDecoderEncoding(Shallow::shallow(a0, n)), + Expr::TextDecoderFatal(a0) => Expr::TextDecoderFatal(Shallow::shallow(a0, n)), + Expr::TextDecoderIgnoreBom(a0) => Expr::TextDecoderIgnoreBom(Shallow::shallow(a0, n)), + Expr::EncodeURI(a0) => Expr::EncodeURI(Shallow::shallow(a0, n)), + Expr::DecodeURI(a0) => Expr::DecodeURI(Shallow::shallow(a0, n)), + Expr::EncodeURIComponent(a0) => Expr::EncodeURIComponent(Shallow::shallow(a0, n)), + Expr::DecodeURIComponent(a0) => Expr::DecodeURIComponent(Shallow::shallow(a0, n)), + Expr::StructuredClone { value, options } => Expr::StructuredClone { + value: Shallow::shallow(value, n), + options: Shallow::shallow(options, n), + }, + Expr::LinkGeneratorPrototype { + obj, + is_async, + fixed_state, + i32_controls, + i1_controls, + } => Expr::LinkGeneratorPrototype { + obj: Shallow::shallow(obj, n), + is_async: is_async.clone(), + fixed_state: fixed_state.clone(), + i32_controls: i32_controls.clone(), + i1_controls: i1_controls.clone(), + }, + Expr::QueueMicrotask(a0) => Expr::QueueMicrotask(Shallow::shallow(a0, n)), + Expr::IterResultSet(a0, a1) => Expr::IterResultSet(Shallow::shallow(a0, n), a1.clone()), + Expr::IterResultGetValue => Expr::IterResultGetValue, + Expr::IterResultGetDone => Expr::IterResultGetDone, + Expr::AsyncStepChain { + value, + step_closure, + } => Expr::AsyncStepChain { + value: Shallow::shallow(value, n), + step_closure: Shallow::shallow(step_closure, n), + }, + Expr::AsyncStepResumeNow { + value, + step_closure, + } => Expr::AsyncStepResumeNow { + value: Shallow::shallow(value, n), + step_closure: Shallow::shallow(step_closure, n), + }, + Expr::AsyncStepDeclined(a0) => Expr::AsyncStepDeclined(Shallow::shallow(a0, n)), + Expr::AsyncStepDone { + value, + step_closure, + } => Expr::AsyncStepDone { + value: Shallow::shallow(value, n), + step_closure: Shallow::shallow(step_closure, n), + }, + Expr::CurrentStepClosure => Expr::CurrentStepClosure, + Expr::AsyncFirstCall { step_closure } => Expr::AsyncFirstCall { + step_closure: Shallow::shallow(step_closure, n), + }, + Expr::AsyncGenResume { + step_closure, + value, + is_error, + } => Expr::AsyncGenResume { + step_closure: Shallow::shallow(step_closure, n), + value: Shallow::shallow(value, n), + is_error: is_error.clone(), + }, + Expr::CryptoRandomBytes(a0) => Expr::CryptoRandomBytes(Shallow::shallow(a0, n)), + Expr::CryptoRandomUUID => Expr::CryptoRandomUUID, + Expr::CryptoRandomUUIDv7 => Expr::CryptoRandomUUIDv7, + Expr::CryptoSha256(a0) => Expr::CryptoSha256(Shallow::shallow(a0, n)), + Expr::CryptoMd5(a0) => Expr::CryptoMd5(Shallow::shallow(a0, n)), + Expr::WebCryptoDigest { algo, data } => Expr::WebCryptoDigest { + algo: Shallow::shallow(algo, n), + data: Shallow::shallow(data, n), + }, + Expr::WebCryptoImportKey { + format, + key, + algorithm, + extractable, + usages, + } => Expr::WebCryptoImportKey { + format: Shallow::shallow(format, n), + key: Shallow::shallow(key, n), + algorithm: Shallow::shallow(algorithm, n), + extractable: Shallow::shallow(extractable, n), + usages: Shallow::shallow(usages, n), + }, + Expr::WebCryptoExportKey { format, key } => Expr::WebCryptoExportKey { + format: Shallow::shallow(format, n), + key: Shallow::shallow(key, n), + }, + Expr::WebCryptoSign { + algorithm, + key, + data, + } => Expr::WebCryptoSign { + algorithm: Shallow::shallow(algorithm, n), + key: Shallow::shallow(key, n), + data: Shallow::shallow(data, n), + }, + Expr::WebCryptoVerify { + algorithm, + key, + signature, + data, + } => Expr::WebCryptoVerify { + algorithm: Shallow::shallow(algorithm, n), + key: Shallow::shallow(key, n), + signature: Shallow::shallow(signature, n), + data: Shallow::shallow(data, n), + }, + Expr::WebCryptoDeriveBits { + algorithm, + base_key, + length, + } => Expr::WebCryptoDeriveBits { + algorithm: Shallow::shallow(algorithm, n), + base_key: Shallow::shallow(base_key, n), + length: Shallow::shallow(length, n), + }, + Expr::WebCryptoDeriveKey { + algorithm, + base_key, + derived_key_algorithm, + extractable, + usages, + } => Expr::WebCryptoDeriveKey { + algorithm: Shallow::shallow(algorithm, n), + base_key: Shallow::shallow(base_key, n), + derived_key_algorithm: Shallow::shallow(derived_key_algorithm, n), + extractable: Shallow::shallow(extractable, n), + usages: Shallow::shallow(usages, n), + }, + Expr::WebCryptoEncrypt { + algorithm, + key, + data, + } => Expr::WebCryptoEncrypt { + algorithm: Shallow::shallow(algorithm, n), + key: Shallow::shallow(key, n), + data: Shallow::shallow(data, n), + }, + Expr::WebCryptoDecrypt { + algorithm, + key, + data, + } => Expr::WebCryptoDecrypt { + algorithm: Shallow::shallow(algorithm, n), + key: Shallow::shallow(key, n), + data: Shallow::shallow(data, n), + }, + Expr::WebCryptoGenerateKey { + algorithm, + extractable, + usages, + } => Expr::WebCryptoGenerateKey { + algorithm: Shallow::shallow(algorithm, n), + extractable: Shallow::shallow(extractable, n), + usages: Shallow::shallow(usages, n), + }, + Expr::WebCryptoWrapKey { + format, + key, + wrapping_key, + wrap_algorithm, + } => Expr::WebCryptoWrapKey { + format: Shallow::shallow(format, n), + key: Shallow::shallow(key, n), + wrapping_key: Shallow::shallow(wrapping_key, n), + wrap_algorithm: Shallow::shallow(wrap_algorithm, n), + }, + Expr::WebCryptoUnwrapKey { + format, + wrapped_key, + unwrapping_key, + unwrap_algorithm, + unwrapped_key_algorithm, + extractable, + usages, + } => Expr::WebCryptoUnwrapKey { + format: Shallow::shallow(format, n), + wrapped_key: Shallow::shallow(wrapped_key, n), + unwrapping_key: Shallow::shallow(unwrapping_key, n), + unwrap_algorithm: Shallow::shallow(unwrap_algorithm, n), + unwrapped_key_algorithm: Shallow::shallow(unwrapped_key_algorithm, n), + extractable: Shallow::shallow(extractable, n), + usages: Shallow::shallow(usages, n), + }, + Expr::CryptoRandomFillSync { + buffer, + offset, + size, + } => Expr::CryptoRandomFillSync { + buffer: Shallow::shallow(buffer, n), + offset: Shallow::shallow(offset, n), + size: Shallow::shallow(size, n), + }, + Expr::OsPlatform => Expr::OsPlatform, + Expr::OsArch => Expr::OsArch, + Expr::OsHostname => Expr::OsHostname, + Expr::OsHomedir => Expr::OsHomedir, + Expr::OsTmpdir => Expr::OsTmpdir, + Expr::OsTotalmem => Expr::OsTotalmem, + Expr::OsFreemem => Expr::OsFreemem, + Expr::OsUptime => Expr::OsUptime, + Expr::OsType => Expr::OsType, + Expr::OsRelease => Expr::OsRelease, + Expr::OsCpus => Expr::OsCpus, + Expr::OsNetworkInterfaces => Expr::OsNetworkInterfaces, + Expr::OsUserInfo => Expr::OsUserInfo, + Expr::OsUserInfoBuffer => Expr::OsUserInfoBuffer, + Expr::OsEOL => Expr::OsEOL, + Expr::OsDevNull => Expr::OsDevNull, + Expr::OsAvailableParallelism => Expr::OsAvailableParallelism, + Expr::OsEndianness => Expr::OsEndianness, + Expr::OsLoadavg => Expr::OsLoadavg, + Expr::OsMachine => Expr::OsMachine, + Expr::OsVersion => Expr::OsVersion, + Expr::BufferFrom { data, encoding } => Expr::BufferFrom { + data: Shallow::shallow(data, n), + encoding: Shallow::shallow(encoding, n), + }, + Expr::BufferFromArrayBuffer { + data, + byte_offset, + length, + } => Expr::BufferFromArrayBuffer { + data: Shallow::shallow(data, n), + byte_offset: Shallow::shallow(byte_offset, n), + length: Shallow::shallow(length, n), + }, + Expr::BufferAlloc { + size, + fill, + encoding, + } => Expr::BufferAlloc { + size: Shallow::shallow(size, n), + fill: Shallow::shallow(fill, n), + encoding: Shallow::shallow(encoding, n), + }, + Expr::BufferAllocUnsafe(a0) => Expr::BufferAllocUnsafe(Shallow::shallow(a0, n)), + Expr::BufferConcat(a0) => Expr::BufferConcat(Shallow::shallow(a0, n)), + Expr::BufferConcatWithLength { list, total_length } => Expr::BufferConcatWithLength { + list: Shallow::shallow(list, n), + total_length: Shallow::shallow(total_length, n), + }, + Expr::BufferIsBuffer(a0) => Expr::BufferIsBuffer(Shallow::shallow(a0, n)), + Expr::BufferIsEncoding(a0) => Expr::BufferIsEncoding(Shallow::shallow(a0, n)), + Expr::BufferByteLength { data, encoding } => Expr::BufferByteLength { + data: Shallow::shallow(data, n), + encoding: Shallow::shallow(encoding, n), + }, + Expr::BufferToString { buffer, encoding } => Expr::BufferToString { + buffer: Shallow::shallow(buffer, n), + encoding: Shallow::shallow(encoding, n), + }, + Expr::BufferLength(a0) => Expr::BufferLength(Shallow::shallow(a0, n)), + Expr::BufferSlice { buffer, start, end } => Expr::BufferSlice { + buffer: Shallow::shallow(buffer, n), + start: Shallow::shallow(start, n), + end: Shallow::shallow(end, n), + }, + Expr::BufferCopy { + source, + target, + target_start, + source_start, + source_end, + } => Expr::BufferCopy { + source: Shallow::shallow(source, n), + target: Shallow::shallow(target, n), + target_start: Shallow::shallow(target_start, n), + source_start: Shallow::shallow(source_start, n), + source_end: Shallow::shallow(source_end, n), + }, + Expr::BufferWrite { + buffer, + string, + offset, + encoding, + } => Expr::BufferWrite { + buffer: Shallow::shallow(buffer, n), + string: Shallow::shallow(string, n), + offset: Shallow::shallow(offset, n), + encoding: Shallow::shallow(encoding, n), + }, + Expr::BufferFill { buffer, value } => Expr::BufferFill { + buffer: Shallow::shallow(buffer, n), + value: Shallow::shallow(value, n), + }, + Expr::BufferEquals { buffer, other } => Expr::BufferEquals { + buffer: Shallow::shallow(buffer, n), + other: Shallow::shallow(other, n), + }, + Expr::BufferIndexGet { buffer, index } => Expr::BufferIndexGet { + buffer: Shallow::shallow(buffer, n), + index: Shallow::shallow(index, n), + }, + Expr::BufferIndexSet { + buffer, + index, + value, + } => Expr::BufferIndexSet { + buffer: Shallow::shallow(buffer, n), + index: Shallow::shallow(index, n), + value: Shallow::shallow(value, n), + }, + Expr::Uint8ArrayNew(a0) => Expr::Uint8ArrayNew(Shallow::shallow(a0, n)), + Expr::Uint8ArrayFrom(a0) => Expr::Uint8ArrayFrom(Shallow::shallow(a0, n)), + Expr::Uint8ArrayLength(a0) => Expr::Uint8ArrayLength(Shallow::shallow(a0, n)), + Expr::Uint8ArrayGet { array, index } => Expr::Uint8ArrayGet { + array: Shallow::shallow(array, n), + index: Shallow::shallow(index, n), + }, + Expr::Uint8ArraySet { + array, + index, + value, + } => Expr::Uint8ArraySet { + array: Shallow::shallow(array, n), + index: Shallow::shallow(index, n), + value: Shallow::shallow(value, n), + }, + Expr::TypedArrayNew { kind, arg } => Expr::TypedArrayNew { + kind: kind.clone(), + arg: Shallow::shallow(arg, n), + }, + Expr::NativeArenaAlloc(a0) => Expr::NativeArenaAlloc(Shallow::shallow(a0, n)), + Expr::NativeArenaView { + owner, + kind, + byte_offset, + length, + } => Expr::NativeArenaView { + owner: Shallow::shallow(owner, n), + kind: kind.clone(), + byte_offset: Shallow::shallow(byte_offset, n), + length: Shallow::shallow(length, n), + }, + Expr::NativePodView { + owner, + byte_offset, + count, + view_type, + } => Expr::NativePodView { + owner: Shallow::shallow(owner, n), + byte_offset: Shallow::shallow(byte_offset, n), + count: Shallow::shallow(count, n), + view_type: view_type.clone(), + }, + Expr::PodLayoutSizeOf { ty } => Expr::PodLayoutSizeOf { ty: ty.clone() }, + Expr::PodLayoutAlignOf { ty } => Expr::PodLayoutAlignOf { ty: ty.clone() }, + Expr::PodLayoutOffsetOf { ty, field_path } => Expr::PodLayoutOffsetOf { + ty: ty.clone(), + field_path: field_path.clone(), + }, + Expr::NativeArenaDispose(a0) => Expr::NativeArenaDispose(Shallow::shallow(a0, n)), + Expr::NativeMemoryFillU32 { view, value } => Expr::NativeMemoryFillU32 { + view: Shallow::shallow(view, n), + value: Shallow::shallow(value, n), + }, + Expr::NativeMemoryCopy { dst, src } => Expr::NativeMemoryCopy { + dst: Shallow::shallow(dst, n), + src: Shallow::shallow(src, n), + }, + Expr::ChildProcessExecSync { command, options } => Expr::ChildProcessExecSync { + command: Shallow::shallow(command, n), + options: Shallow::shallow(options, n), + }, + Expr::ChildProcessSpawnSync { + command, + args, + options, + } => Expr::ChildProcessSpawnSync { + command: Shallow::shallow(command, n), + args: Shallow::shallow(args, n), + options: Shallow::shallow(options, n), + }, + Expr::ChildProcessSpawn { + command, + args, + options, + } => Expr::ChildProcessSpawn { + command: Shallow::shallow(command, n), + args: Shallow::shallow(args, n), + options: Shallow::shallow(options, n), + }, + Expr::ChildProcessFork { + module, + args, + options, + } => Expr::ChildProcessFork { + module: Shallow::shallow(module, n), + args: Shallow::shallow(args, n), + options: Shallow::shallow(options, n), + }, + Expr::ChildProcessExec { + command, + options, + callback, + } => Expr::ChildProcessExec { + command: Shallow::shallow(command, n), + options: Shallow::shallow(options, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ChildProcessExecFile { + file, + args, + options, + callback, + } => Expr::ChildProcessExecFile { + file: Shallow::shallow(file, n), + args: Shallow::shallow(args, n), + options: Shallow::shallow(options, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ChildProcessExecFileSync { + file, + args, + options, + } => Expr::ChildProcessExecFileSync { + file: Shallow::shallow(file, n), + args: Shallow::shallow(args, n), + options: Shallow::shallow(options, n), + }, + Expr::ChildProcessSpawnBackground { + command, + args, + log_file, + env_json, + } => Expr::ChildProcessSpawnBackground { + command: Shallow::shallow(command, n), + args: Shallow::shallow(args, n), + log_file: Shallow::shallow(log_file, n), + env_json: Shallow::shallow(env_json, n), + }, + Expr::ChildProcessGetProcessStatus(a0) => { + Expr::ChildProcessGetProcessStatus(Shallow::shallow(a0, n)) + } + Expr::ChildProcessKillProcess(a0) => Expr::ChildProcessKillProcess(Shallow::shallow(a0, n)), + Expr::FetchWithOptions { + url, + method, + body, + headers, + headers_dynamic, + signal, + redirect, + } => Expr::FetchWithOptions { + url: Shallow::shallow(url, n), + method: Shallow::shallow(method, n), + body: Shallow::shallow(body, n), + headers: Shallow::shallow(headers, n), + headers_dynamic: Shallow::shallow(headers_dynamic, n), + signal: Shallow::shallow(signal, n), + redirect: Shallow::shallow(redirect, n), + }, + Expr::FetchGetWithAuth { url, auth_header } => Expr::FetchGetWithAuth { + url: Shallow::shallow(url, n), + auth_header: Shallow::shallow(auth_header, n), + }, + Expr::FetchPostWithAuth { + url, + auth_header, + body, + } => Expr::FetchPostWithAuth { + url: Shallow::shallow(url, n), + auth_header: Shallow::shallow(auth_header, n), + body: Shallow::shallow(body, n), + }, + Expr::NetCreateServer { + options, + connection_listener, + } => Expr::NetCreateServer { + options: Shallow::shallow(options, n), + connection_listener: Shallow::shallow(connection_listener, n), + }, + Expr::NetCreateConnection { + port, + host, + connect_listener, + } => Expr::NetCreateConnection { + port: Shallow::shallow(port, n), + host: Shallow::shallow(host, n), + connect_listener: Shallow::shallow(connect_listener, n), + }, + Expr::NetConnect { + port, + host, + connect_listener, + } => Expr::NetConnect { + port: Shallow::shallow(port, n), + host: Shallow::shallow(host, n), + connect_listener: Shallow::shallow(connect_listener, n), + }, + Expr::ArrayPush { + array_id, + value, + field_writeback, + } => Expr::ArrayPush { + array_id: array_id.clone(), + value: Shallow::shallow(value, n), + field_writeback: field_writeback.clone(), + }, + Expr::ArrayPushSpread { array_id, source } => Expr::ArrayPushSpread { + array_id: array_id.clone(), + source: Shallow::shallow(source, n), + }, + Expr::ArrayPop(a0) => Expr::ArrayPop(a0.clone()), + Expr::ArrayShift(a0) => Expr::ArrayShift(a0.clone()), + Expr::ArrayUnshift { array_id, value } => Expr::ArrayUnshift { + array_id: array_id.clone(), + value: Shallow::shallow(value, n), + }, + Expr::ArrayIndexOf { + array, + value, + from_index, + } => Expr::ArrayIndexOf { + array: Shallow::shallow(array, n), + value: Shallow::shallow(value, n), + from_index: Shallow::shallow(from_index, n), + }, + Expr::ArrayLastIndexOf { + array, + value, + from_index, + } => Expr::ArrayLastIndexOf { + array: Shallow::shallow(array, n), + value: Shallow::shallow(value, n), + from_index: Shallow::shallow(from_index, n), + }, + Expr::ArrayIncludes { + array, + value, + from_index, + } => Expr::ArrayIncludes { + array: Shallow::shallow(array, n), + value: Shallow::shallow(value, n), + from_index: Shallow::shallow(from_index, n), + }, + Expr::ArraySlice { + array, + start, + end, + layout, + } => Expr::ArraySlice { + array: Shallow::shallow(array, n), + start: Shallow::shallow(start, n), + end: Shallow::shallow(end, n), + layout: layout.clone(), + }, + Expr::ArraySplice { + array_id, + start, + delete_count, + items, + } => Expr::ArraySplice { + array_id: array_id.clone(), + start: Shallow::shallow(start, n), + delete_count: Shallow::shallow(delete_count, n), + items: Shallow::shallow(items, n), + }, + Expr::ArrayForEach { array, callback } => Expr::ArrayForEach { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayMap { array, callback } => Expr::ArrayMap { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFilter { array, callback } => Expr::ArrayFilter { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFind { array, callback } => Expr::ArrayFind { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFindIndex { array, callback } => Expr::ArrayFindIndex { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFindLast { array, callback } => Expr::ArrayFindLast { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFindLastIndex { array, callback } => Expr::ArrayFindLastIndex { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayAt { array, index } => Expr::ArrayAt { + array: Shallow::shallow(array, n), + index: Shallow::shallow(index, n), + }, + Expr::ArraySome { array, callback } => Expr::ArraySome { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayEvery { array, callback } => Expr::ArrayEvery { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArrayFlatMap { array, callback } => Expr::ArrayFlatMap { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + }, + Expr::ArraySort { array, comparator } => Expr::ArraySort { + array: Shallow::shallow(array, n), + comparator: Shallow::shallow(comparator, n), + }, + Expr::ArrayReduce { + array, + callback, + initial, + } => Expr::ArrayReduce { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + initial: Shallow::shallow(initial, n), + }, + Expr::ArrayReduceRight { + array, + callback, + initial, + } => Expr::ArrayReduceRight { + array: Shallow::shallow(array, n), + callback: Shallow::shallow(callback, n), + initial: Shallow::shallow(initial, n), + }, + Expr::ArrayJoin { array, separator } => Expr::ArrayJoin { + array: Shallow::shallow(array, n), + separator: Shallow::shallow(separator, n), + }, + Expr::ArrayFlat { array } => Expr::ArrayFlat { + array: Shallow::shallow(array, n), + }, + Expr::ArrayToReversed { array } => Expr::ArrayToReversed { + array: Shallow::shallow(array, n), + }, + Expr::ArrayToSorted { array, comparator } => Expr::ArrayToSorted { + array: Shallow::shallow(array, n), + comparator: Shallow::shallow(comparator, n), + }, + Expr::ArrayToSpliced { + array, + start, + delete_count, + items, + } => Expr::ArrayToSpliced { + array: Shallow::shallow(array, n), + start: Shallow::shallow(start, n), + delete_count: Shallow::shallow(delete_count, n), + items: Shallow::shallow(items, n), + }, + Expr::ArrayWith { + array, + index, + value, + } => Expr::ArrayWith { + array: Shallow::shallow(array, n), + index: Shallow::shallow(index, n), + value: Shallow::shallow(value, n), + }, + Expr::ArrayReverseValue { receiver } => Expr::ArrayReverseValue { + receiver: Shallow::shallow(receiver, n), + }, + Expr::ArrayCopyWithin { + array_id, + target, + start, + end, + } => Expr::ArrayCopyWithin { + array_id: array_id.clone(), + target: Shallow::shallow(target, n), + start: Shallow::shallow(start, n), + end: Shallow::shallow(end, n), + }, + Expr::ArrayCopyWithinValue { + receiver, + target, + start, + end, + } => Expr::ArrayCopyWithinValue { + receiver: Shallow::shallow(receiver, n), + target: Shallow::shallow(target, n), + start: Shallow::shallow(start, n), + end: Shallow::shallow(end, n), + }, + Expr::ArrayEntries(a0) => Expr::ArrayEntries(Shallow::shallow(a0, n)), + Expr::ArrayKeys(a0) => Expr::ArrayKeys(Shallow::shallow(a0, n)), + Expr::ArrayValues(a0) => Expr::ArrayValues(Shallow::shallow(a0, n)), + Expr::ArrayLikeMethod { + method, + receiver, + args, + source, + } => Expr::ArrayLikeMethod { + method: method.clone(), + receiver: Shallow::shallow(receiver, n), + args: Shallow::shallow(args, n), + source: source.clone(), + }, + Expr::StringSplit(a0, a1) => { + Expr::StringSplit(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::StringFromCharCode(a0) => Expr::StringFromCharCode(Shallow::shallow(a0, n)), + Expr::StringFromCharCodeSpread(a0) => { + Expr::StringFromCharCodeSpread(Shallow::shallow(a0, n)) + } + Expr::StringFromCodePoint(a0) => Expr::StringFromCodePoint(Shallow::shallow(a0, n)), + Expr::StringRaw { + call_site, + substitutions, + } => Expr::StringRaw { + call_site: Shallow::shallow(call_site, n), + substitutions: Shallow::shallow(substitutions, n), + }, + Expr::StringAt { string, index } => Expr::StringAt { + string: Shallow::shallow(string, n), + index: Shallow::shallow(index, n), + }, + Expr::StringCodePointAt { string, index } => Expr::StringCodePointAt { + string: Shallow::shallow(string, n), + index: Shallow::shallow(index, n), + }, + Expr::MapNew => Expr::MapNew, + Expr::MapNewFromArray(a0) => Expr::MapNewFromArray(Shallow::shallow(a0, n)), + Expr::MapSet { map, key, value } => Expr::MapSet { + map: Shallow::shallow(map, n), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + }, + Expr::MapGet { map, key } => Expr::MapGet { + map: Shallow::shallow(map, n), + key: Shallow::shallow(key, n), + }, + Expr::MapHas { map, key } => Expr::MapHas { + map: Shallow::shallow(map, n), + key: Shallow::shallow(key, n), + }, + Expr::MapDelete { map, key } => Expr::MapDelete { + map: Shallow::shallow(map, n), + key: Shallow::shallow(key, n), + }, + Expr::MapSize(a0) => Expr::MapSize(Shallow::shallow(a0, n)), + Expr::MapClear(a0) => Expr::MapClear(Shallow::shallow(a0, n)), + Expr::MapEntries(a0) => Expr::MapEntries(Shallow::shallow(a0, n)), + Expr::MapKeys(a0) => Expr::MapKeys(Shallow::shallow(a0, n)), + Expr::MapValues(a0) => Expr::MapValues(Shallow::shallow(a0, n)), + Expr::MapEntryKeyAt { map, idx } => Expr::MapEntryKeyAt { + map: Shallow::shallow(map, n), + idx: Shallow::shallow(idx, n), + }, + Expr::MapEntryValueAt { map, idx } => Expr::MapEntryValueAt { + map: Shallow::shallow(map, n), + idx: Shallow::shallow(idx, n), + }, + Expr::SetNew => Expr::SetNew, + Expr::SetNewFromArray(a0) => Expr::SetNewFromArray(Shallow::shallow(a0, n)), + Expr::SetAdd { set_id, value } => Expr::SetAdd { + set_id: set_id.clone(), + value: Shallow::shallow(value, n), + }, + Expr::SetHas { set, value } => Expr::SetHas { + set: Shallow::shallow(set, n), + value: Shallow::shallow(value, n), + }, + Expr::SetDelete { set, value } => Expr::SetDelete { + set: Shallow::shallow(set, n), + value: Shallow::shallow(value, n), + }, + Expr::SetSize(a0) => Expr::SetSize(Shallow::shallow(a0, n)), + Expr::SetClear(a0) => Expr::SetClear(Shallow::shallow(a0, n)), + Expr::SetValues(a0) => Expr::SetValues(Shallow::shallow(a0, n)), + Expr::SetValueAt { set, idx } => Expr::SetValueAt { + set: Shallow::shallow(set, n), + idx: Shallow::shallow(idx, n), + }, + Expr::Sequence(a0) => Expr::Sequence(Shallow::shallow(a0, n)), + Expr::BoxedPrimitiveNew { + kind, + arg, + arg_present, + } => Expr::BoxedPrimitiveNew { + kind: kind.clone(), + arg: Shallow::shallow(arg, n), + arg_present: arg_present.clone(), + }, + Expr::DateNow => Expr::DateNow, + Expr::DateNew(a0) => Expr::DateNew(Shallow::shallow(a0, n)), + Expr::DateGetTime(a0) => Expr::DateGetTime(Shallow::shallow(a0, n)), + Expr::DateToISOString(a0) => Expr::DateToISOString(Shallow::shallow(a0, n)), + Expr::DateGetFullYear(a0) => Expr::DateGetFullYear(Shallow::shallow(a0, n)), + Expr::DateGetMonth(a0) => Expr::DateGetMonth(Shallow::shallow(a0, n)), + Expr::DateGetDate(a0) => Expr::DateGetDate(Shallow::shallow(a0, n)), + Expr::DateGetDay(a0) => Expr::DateGetDay(Shallow::shallow(a0, n)), + Expr::DateGetHours(a0) => Expr::DateGetHours(Shallow::shallow(a0, n)), + Expr::DateGetMinutes(a0) => Expr::DateGetMinutes(Shallow::shallow(a0, n)), + Expr::DateGetSeconds(a0) => Expr::DateGetSeconds(Shallow::shallow(a0, n)), + Expr::DateGetMilliseconds(a0) => Expr::DateGetMilliseconds(Shallow::shallow(a0, n)), + Expr::DateParse(a0) => Expr::DateParse(Shallow::shallow(a0, n)), + Expr::DateUtc(a0) => Expr::DateUtc(Shallow::shallow(a0, n)), + Expr::DateGetUtcDay(a0) => Expr::DateGetUtcDay(Shallow::shallow(a0, n)), + Expr::DateGetUtcFullYear(a0) => Expr::DateGetUtcFullYear(Shallow::shallow(a0, n)), + Expr::DateGetUtcMonth(a0) => Expr::DateGetUtcMonth(Shallow::shallow(a0, n)), + Expr::DateGetUtcDate(a0) => Expr::DateGetUtcDate(Shallow::shallow(a0, n)), + Expr::DateGetUtcHours(a0) => Expr::DateGetUtcHours(Shallow::shallow(a0, n)), + Expr::DateGetUtcMinutes(a0) => Expr::DateGetUtcMinutes(Shallow::shallow(a0, n)), + Expr::DateGetUtcSeconds(a0) => Expr::DateGetUtcSeconds(Shallow::shallow(a0, n)), + Expr::DateGetUtcMilliseconds(a0) => Expr::DateGetUtcMilliseconds(Shallow::shallow(a0, n)), + Expr::DateSetUtcFullYear { date, args } => Expr::DateSetUtcFullYear { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcMonth { date, args } => Expr::DateSetUtcMonth { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcDate { date, args } => Expr::DateSetUtcDate { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcHours { date, args } => Expr::DateSetUtcHours { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcMinutes { date, args } => Expr::DateSetUtcMinutes { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcSeconds { date, args } => Expr::DateSetUtcSeconds { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetUtcMilliseconds { date, args } => Expr::DateSetUtcMilliseconds { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetFullYear { date, args } => Expr::DateSetFullYear { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetMonth { date, args } => Expr::DateSetMonth { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetDate { date, args } => Expr::DateSetDate { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetHours { date, args } => Expr::DateSetHours { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetMinutes { date, args } => Expr::DateSetMinutes { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetSeconds { date, args } => Expr::DateSetSeconds { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetMilliseconds { date, args } => Expr::DateSetMilliseconds { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateSetTime { date, args } => Expr::DateSetTime { + date: Shallow::shallow(date, n), + args: Shallow::shallow(args, n), + }, + Expr::DateValueOf(a0) => Expr::DateValueOf(Shallow::shallow(a0, n)), + Expr::DateToString(a0) => Expr::DateToString(Shallow::shallow(a0, n)), + Expr::DateToDateString(a0) => Expr::DateToDateString(Shallow::shallow(a0, n)), + Expr::DateToTimeString(a0) => Expr::DateToTimeString(Shallow::shallow(a0, n)), + Expr::DateToUTCString(a0) => Expr::DateToUTCString(Shallow::shallow(a0, n)), + Expr::DateToLocaleDateString(a0) => Expr::DateToLocaleDateString(Shallow::shallow(a0, n)), + Expr::DateToLocaleTimeString(a0) => Expr::DateToLocaleTimeString(Shallow::shallow(a0, n)), + Expr::DateToLocaleString(a0) => Expr::DateToLocaleString(Shallow::shallow(a0, n)), + Expr::DateGetTimezoneOffset(a0) => Expr::DateGetTimezoneOffset(Shallow::shallow(a0, n)), + Expr::DateToJSON(a0) => Expr::DateToJSON(Shallow::shallow(a0, n)), + Expr::ErrorNew(a0) => Expr::ErrorNew(Shallow::shallow(a0, n)), + Expr::ErrorMessage(a0) => Expr::ErrorMessage(Shallow::shallow(a0, n)), + Expr::ErrorNewWithCause { message, cause } => Expr::ErrorNewWithCause { + message: Shallow::shallow(message, n), + cause: Shallow::shallow(cause, n), + }, + Expr::ErrorNewWithOptions { + kind, + message, + options, + } => Expr::ErrorNewWithOptions { + kind: kind.clone(), + message: Shallow::shallow(message, n), + options: Shallow::shallow(options, n), + }, + Expr::TypeErrorNew(a0) => Expr::TypeErrorNew(Shallow::shallow(a0, n)), + Expr::RangeErrorNew(a0) => Expr::RangeErrorNew(Shallow::shallow(a0, n)), + Expr::ReferenceErrorNew(a0) => Expr::ReferenceErrorNew(Shallow::shallow(a0, n)), + Expr::SyntaxErrorNew(a0) => Expr::SyntaxErrorNew(Shallow::shallow(a0, n)), + Expr::AggregateErrorNew { + errors, + message, + options, + } => Expr::AggregateErrorNew { + errors: Shallow::shallow(errors, n), + message: Shallow::shallow(message, n), + options: Shallow::shallow(options, n), + }, + Expr::UrlNew { url, base } => Expr::UrlNew { + url: Shallow::shallow(url, n), + base: Shallow::shallow(base, n), + }, + Expr::UrlPatternNew { input, base } => Expr::UrlPatternNew { + input: Shallow::shallow(input, n), + base: Shallow::shallow(base, n), + }, + Expr::UrlGetHref(a0) => Expr::UrlGetHref(Shallow::shallow(a0, n)), + Expr::UrlGetPathname(a0) => Expr::UrlGetPathname(Shallow::shallow(a0, n)), + Expr::UrlGetProtocol(a0) => Expr::UrlGetProtocol(Shallow::shallow(a0, n)), + Expr::UrlGetHost(a0) => Expr::UrlGetHost(Shallow::shallow(a0, n)), + Expr::UrlGetHostname(a0) => Expr::UrlGetHostname(Shallow::shallow(a0, n)), + Expr::UrlGetPort(a0) => Expr::UrlGetPort(Shallow::shallow(a0, n)), + Expr::UrlGetSearch(a0) => Expr::UrlGetSearch(Shallow::shallow(a0, n)), + Expr::UrlGetHash(a0) => Expr::UrlGetHash(Shallow::shallow(a0, n)), + Expr::UrlGetOrigin(a0) => Expr::UrlGetOrigin(Shallow::shallow(a0, n)), + Expr::UrlGetSearchParams(a0) => Expr::UrlGetSearchParams(Shallow::shallow(a0, n)), + Expr::UrlCanParse(a0) => Expr::UrlCanParse(Shallow::shallow(a0, n)), + Expr::UrlCanParseWithBase { input, base } => Expr::UrlCanParseWithBase { + input: Shallow::shallow(input, n), + base: Shallow::shallow(base, n), + }, + Expr::UrlParse(a0) => Expr::UrlParse(Shallow::shallow(a0, n)), + Expr::UrlParseWithBase { input, base } => Expr::UrlParseWithBase { + input: Shallow::shallow(input, n), + base: Shallow::shallow(base, n), + }, + Expr::UrlInstanceToString(a0) => Expr::UrlInstanceToString(Shallow::shallow(a0, n)), + Expr::UrlInstanceToJSON(a0) => Expr::UrlInstanceToJSON(Shallow::shallow(a0, n)), + Expr::UrlSetPathname { url, value } => Expr::UrlSetPathname { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetSearch { url, value } => Expr::UrlSetSearch { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetHash { url, value } => Expr::UrlSetHash { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetProtocol { url, value } => Expr::UrlSetProtocol { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetHostname { url, value } => Expr::UrlSetHostname { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetPort { url, value } => Expr::UrlSetPort { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetUsername { url, value } => Expr::UrlSetUsername { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetPassword { url, value } => Expr::UrlSetPassword { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSetHref { url, value } => Expr::UrlSetHref { + url: Shallow::shallow(url, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSearchParamsNew(a0) => Expr::UrlSearchParamsNew(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsMissingArgs { + params, + args, + name_and_value, + } => Expr::UrlSearchParamsMissingArgs { + params: Shallow::shallow(params, n), + args: Shallow::shallow(args, n), + name_and_value: name_and_value.clone(), + }, + Expr::UrlSearchParamsGet { params, name } => Expr::UrlSearchParamsGet { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + }, + Expr::UrlSearchParamsHas { + params, + name, + value, + } => Expr::UrlSearchParamsHas { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSearchParamsSet { + params, + name, + value, + } => Expr::UrlSearchParamsSet { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSearchParamsAppend { + params, + name, + value, + } => Expr::UrlSearchParamsAppend { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSearchParamsDelete { + params, + name, + value, + } => Expr::UrlSearchParamsDelete { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + value: Shallow::shallow(value, n), + }, + Expr::UrlSearchParamsToString(a0) => Expr::UrlSearchParamsToString(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsGetAll { params, name } => Expr::UrlSearchParamsGetAll { + params: Shallow::shallow(params, n), + name: Shallow::shallow(name, n), + }, + Expr::UrlSearchParamsEntries(a0) => Expr::UrlSearchParamsEntries(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsKeys(a0) => Expr::UrlSearchParamsKeys(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsValues(a0) => Expr::UrlSearchParamsValues(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsSort(a0) => Expr::UrlSearchParamsSort(Shallow::shallow(a0, n)), + Expr::UrlSearchParamsForEach { + params, + callback, + this_arg, + } => Expr::UrlSearchParamsForEach { + params: Shallow::shallow(params, n), + callback: Shallow::shallow(callback, n), + this_arg: Shallow::shallow(this_arg, n), + }, + Expr::Delete(a0) => Expr::Delete(Shallow::shallow(a0, n)), + Expr::Closure { + func_id, + params, + return_type, + body, + captures, + mutable_captures, + captures_this, + captures_new_target, + enclosing_class, + is_arrow, + is_async, + is_generator, + is_strict, + } => Expr::Closure { + func_id: func_id.clone(), + params: Shallow::shallow(params, n), + return_type: return_type.clone(), + body: body.clone(), + captures: captures.clone(), + mutable_captures: mutable_captures.clone(), + captures_this: captures_this.clone(), + captures_new_target: captures_new_target.clone(), + enclosing_class: enclosing_class.clone(), + is_arrow: is_arrow.clone(), + is_async: is_async.clone(), + is_generator: is_generator.clone(), + is_strict: is_strict.clone(), + }, + Expr::RegExp { pattern, flags } => Expr::RegExp { + pattern: pattern.clone(), + flags: flags.clone(), + }, + Expr::RegExpDynamic { + pattern, + flags, + is_call, + } => Expr::RegExpDynamic { + pattern: Shallow::shallow(pattern, n), + flags: Shallow::shallow(flags, n), + is_call: is_call.clone(), + }, + Expr::StringMatch { string, regex } => Expr::StringMatch { + string: Shallow::shallow(string, n), + regex: Shallow::shallow(regex, n), + }, + Expr::StringMatchAll { string, regex } => Expr::StringMatchAll { + string: Shallow::shallow(string, n), + regex: Shallow::shallow(regex, n), + }, + Expr::StringReplace { + string, + pattern, + replacement, + } => Expr::StringReplace { + string: Shallow::shallow(string, n), + pattern: Shallow::shallow(pattern, n), + replacement: Shallow::shallow(replacement, n), + }, + Expr::ObjectFromEntries(a0) => Expr::ObjectFromEntries(Shallow::shallow(a0, n)), + Expr::ObjectIs(a0, a1) => Expr::ObjectIs(Shallow::shallow(a0, n), Shallow::shallow(a1, n)), + Expr::ObjectHasOwn(a0, a1) => { + Expr::ObjectHasOwn(Shallow::shallow(a0, n), Shallow::shallow(a1, n)) + } + Expr::ObjectKeys(a0) => Expr::ObjectKeys(Shallow::shallow(a0, n)), + Expr::ForInKeys(a0) => Expr::ForInKeys(Shallow::shallow(a0, n)), + Expr::ObjectValues(a0) => Expr::ObjectValues(Shallow::shallow(a0, n)), + Expr::ObjectEntries(a0) => Expr::ObjectEntries(Shallow::shallow(a0, n)), + Expr::ObjectGroupBy { items, key_fn } => Expr::ObjectGroupBy { + items: Shallow::shallow(items, n), + key_fn: Shallow::shallow(key_fn, n), + }, + Expr::MapGroupBy { items, key_fn } => Expr::MapGroupBy { + items: Shallow::shallow(items, n), + key_fn: Shallow::shallow(key_fn, n), + }, + Expr::ObjectRest { + object, + exclude_keys, + } => Expr::ObjectRest { + object: Shallow::shallow(object, n), + exclude_keys: exclude_keys.clone(), + }, + Expr::ArrayIsArray(a0) => Expr::ArrayIsArray(Shallow::shallow(a0, n)), + Expr::ArrayFrom(a0, a1) => Expr::ArrayFrom(Shallow::shallow(a0, n), a1.clone()), + Expr::ArrayFromArrayLikeHoley(a0) => Expr::ArrayFromArrayLikeHoley(Shallow::shallow(a0, n)), + Expr::IteratorFrom(a0) => Expr::IteratorFrom(Shallow::shallow(a0, n)), + Expr::TaggedTemplateStrings { + site_id, + cooked, + raw, + } => Expr::TaggedTemplateStrings { + site_id: site_id.clone(), + cooked: Shallow::shallow(cooked, n), + raw: raw.clone(), + }, + Expr::TemplateRaw(a0) => Expr::TemplateRaw(Shallow::shallow(a0, n)), + Expr::IteratorToArray(a0) => Expr::IteratorToArray(Shallow::shallow(a0, n)), + Expr::GetIterator(a0) => Expr::GetIterator(Shallow::shallow(a0, n)), + Expr::ArrayIterationPatched => Expr::ArrayIterationPatched, + Expr::GetAsyncIterator(a0) => Expr::GetAsyncIterator(Shallow::shallow(a0, n)), + Expr::ForOfToArray(a0) => Expr::ForOfToArray(Shallow::shallow(a0, n)), + Expr::ForAwaitToArray(a0) => Expr::ForAwaitToArray(Shallow::shallow(a0, n)), + Expr::ArrayFromMapped { + iterable, + map_fn, + this_arg, + } => Expr::ArrayFromMapped { + iterable: Shallow::shallow(iterable, n), + map_fn: Shallow::shallow(map_fn, n), + this_arg: Shallow::shallow(this_arg, n), + }, + Expr::ParseInt { string, radix } => Expr::ParseInt { + string: Shallow::shallow(string, n), + radix: Shallow::shallow(radix, n), + }, + Expr::ParseFloat(a0) => Expr::ParseFloat(Shallow::shallow(a0, n)), + Expr::NumberCoerce(a0) => Expr::NumberCoerce(Shallow::shallow(a0, n)), + Expr::BigIntCoerce(a0) => Expr::BigIntCoerce(Shallow::shallow(a0, n)), + Expr::StringCoerce(a0) => Expr::StringCoerce(Shallow::shallow(a0, n)), + Expr::TemplateStringCoerce(a0) => Expr::TemplateStringCoerce(Shallow::shallow(a0, n)), + Expr::ObjectCoerce(a0) => Expr::ObjectCoerce(Shallow::shallow(a0, n)), + Expr::BooleanCoerce(a0) => Expr::BooleanCoerce(Shallow::shallow(a0, n)), + Expr::IsNaN(a0) => Expr::IsNaN(Shallow::shallow(a0, n)), + Expr::IsUndefinedOrBareNan(a0) => Expr::IsUndefinedOrBareNan(Shallow::shallow(a0, n)), + Expr::IsFinite(a0) => Expr::IsFinite(Shallow::shallow(a0, n)), + Expr::NumberIsNaN(a0) => Expr::NumberIsNaN(Shallow::shallow(a0, n)), + Expr::NumberIsFinite(a0) => Expr::NumberIsFinite(Shallow::shallow(a0, n)), + Expr::NumberIsInteger(a0) => Expr::NumberIsInteger(Shallow::shallow(a0, n)), + Expr::NumberIsSafeInteger(a0) => Expr::NumberIsSafeInteger(Shallow::shallow(a0, n)), + Expr::StaticPluginResolve(a0) => Expr::StaticPluginResolve(Shallow::shallow(a0, n)), + Expr::JsLoadModule { path } => Expr::JsLoadModule { path: path.clone() }, + Expr::JsGetExport { + module_handle, + export_name, + } => Expr::JsGetExport { + module_handle: Shallow::shallow(module_handle, n), + export_name: export_name.clone(), + }, + Expr::JsCallFunction { + module_handle, + func_name, + args, + } => Expr::JsCallFunction { + module_handle: Shallow::shallow(module_handle, n), + func_name: func_name.clone(), + args: Shallow::shallow(args, n), + }, + Expr::JsCallMethod { + object, + method_name, + args, + } => Expr::JsCallMethod { + object: Shallow::shallow(object, n), + method_name: method_name.clone(), + args: Shallow::shallow(args, n), + }, + Expr::JsCallValue { callee, args } => Expr::JsCallValue { + callee: Shallow::shallow(callee, n), + args: Shallow::shallow(args, n), + }, + Expr::JsGetProperty { + object, + property_name, + } => Expr::JsGetProperty { + object: Shallow::shallow(object, n), + property_name: property_name.clone(), + }, + Expr::JsSetProperty { + object, + property_name, + value, + } => Expr::JsSetProperty { + object: Shallow::shallow(object, n), + property_name: property_name.clone(), + value: Shallow::shallow(value, n), + }, + Expr::JsNew { + module_handle, + class_name, + args, + } => Expr::JsNew { + module_handle: Shallow::shallow(module_handle, n), + class_name: class_name.clone(), + args: Shallow::shallow(args, n), + }, + Expr::JsNewFromHandle { constructor, args } => Expr::JsNewFromHandle { + constructor: Shallow::shallow(constructor, n), + args: Shallow::shallow(args, n), + }, + Expr::JsCreateCallback { + closure, + param_count, + } => Expr::JsCreateCallback { + closure: Shallow::shallow(closure, n), + param_count: param_count.clone(), + }, + Expr::ImportMetaUrl(a0) => Expr::ImportMetaUrl(a0.clone()), + Expr::ProxyNew { target, handler } => Expr::ProxyNew { + target: Shallow::shallow(target, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProxyGet { proxy, key } => Expr::ProxyGet { + proxy: Shallow::shallow(proxy, n), + key: Shallow::shallow(key, n), + }, + Expr::ProxySet { proxy, key, value } => Expr::ProxySet { + proxy: Shallow::shallow(proxy, n), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + }, + Expr::ProxyHas { proxy, key } => Expr::ProxyHas { + proxy: Shallow::shallow(proxy, n), + key: Shallow::shallow(key, n), + }, + Expr::ProxyDelete { proxy, key } => Expr::ProxyDelete { + proxy: Shallow::shallow(proxy, n), + key: Shallow::shallow(key, n), + }, + Expr::ProxyApply { proxy, args } => Expr::ProxyApply { + proxy: Shallow::shallow(proxy, n), + args: Shallow::shallow(args, n), + }, + Expr::ProxyConstruct { proxy, args } => Expr::ProxyConstruct { + proxy: Shallow::shallow(proxy, n), + args: Shallow::shallow(args, n), + }, + Expr::ProxyRevocable { target, handler } => Expr::ProxyRevocable { + target: Shallow::shallow(target, n), + handler: Shallow::shallow(handler, n), + }, + Expr::ProxyRevoke(a0) => Expr::ProxyRevoke(Shallow::shallow(a0, n)), + Expr::ReflectGet { + target, + key, + receiver, + } => Expr::ReflectGet { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + receiver: Shallow::shallow(receiver, n), + }, + Expr::ReflectSet { + target, + key, + value, + receiver, + } => Expr::ReflectSet { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + receiver: Shallow::shallow(receiver, n), + }, + Expr::PutValueSet { + target, + key, + value, + receiver, + strict, + byte_offset, + byte_end, + } => Expr::PutValueSet { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + receiver: Shallow::shallow(receiver, n), + strict: strict.clone(), + byte_offset: byte_offset.clone(), + byte_end: byte_end.clone(), + }, + Expr::ReflectHas { target, key } => Expr::ReflectHas { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + }, + Expr::ReflectDelete { target, key } => Expr::ReflectDelete { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + }, + Expr::ReflectOwnKeys(a0) => Expr::ReflectOwnKeys(Shallow::shallow(a0, n)), + Expr::ReflectApply { + func, + this_arg, + args, + } => Expr::ReflectApply { + func: Shallow::shallow(func, n), + this_arg: Shallow::shallow(this_arg, n), + args: Shallow::shallow(args, n), + }, + Expr::ReflectConstruct { + target, + args, + new_target, + } => Expr::ReflectConstruct { + target: Shallow::shallow(target, n), + args: Shallow::shallow(args, n), + new_target: Shallow::shallow(new_target, n), + }, + Expr::ReflectDefineProperty { + target, + key, + descriptor, + } => Expr::ReflectDefineProperty { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + descriptor: Shallow::shallow(descriptor, n), + }, + Expr::ReflectGetOwnPropertyDescriptor { target, key } => { + Expr::ReflectGetOwnPropertyDescriptor { + target: Shallow::shallow(target, n), + key: Shallow::shallow(key, n), + } + } + Expr::ReflectGetPrototypeOf(a0) => Expr::ReflectGetPrototypeOf(Shallow::shallow(a0, n)), + Expr::ReflectSetPrototypeOf { target, proto } => Expr::ReflectSetPrototypeOf { + target: Shallow::shallow(target, n), + proto: Shallow::shallow(proto, n), + }, + Expr::ReflectIsExtensible(a0) => Expr::ReflectIsExtensible(Shallow::shallow(a0, n)), + Expr::ReflectPreventExtensions(a0) => { + Expr::ReflectPreventExtensions(Shallow::shallow(a0, n)) + } + Expr::ReflectDefineMetadata { + key, + value, + target, + property_key, + } => Expr::ReflectDefineMetadata { + key: Shallow::shallow(key, n), + value: Shallow::shallow(value, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectGetMetadata { + key, + target, + property_key, + } => Expr::ReflectGetMetadata { + key: Shallow::shallow(key, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectGetOwnMetadata { + key, + target, + property_key, + } => Expr::ReflectGetOwnMetadata { + key: Shallow::shallow(key, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectHasMetadata { + key, + target, + property_key, + } => Expr::ReflectHasMetadata { + key: Shallow::shallow(key, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectHasOwnMetadata { + key, + target, + property_key, + } => Expr::ReflectHasOwnMetadata { + key: Shallow::shallow(key, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectGetMetadataKeys { + target, + property_key, + } => Expr::ReflectGetMetadataKeys { + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectGetOwnMetadataKeys { + target, + property_key, + } => Expr::ReflectGetOwnMetadataKeys { + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::ReflectDeleteMetadata { + key, + target, + property_key, + } => Expr::ReflectDeleteMetadata { + key: Shallow::shallow(key, n), + target: Shallow::shallow(target, n), + property_key: Shallow::shallow(property_key, n), + }, + Expr::DynamicImport { + paths, + arg, + options, + byte_offset, + deferred_error, + synchronous, + } => Expr::DynamicImport { + paths: paths.clone(), + arg: Shallow::shallow(arg, n), + options: Shallow::shallow(options, n), + byte_offset: byte_offset.clone(), + deferred_error: deferred_error.clone(), + synchronous: synchronous.clone(), + }, + Expr::WorkerNew { + paths, + partial, + filename, + options, + is_eval, + } => Expr::WorkerNew { + paths: paths.clone(), + partial: partial.clone(), + filename: Shallow::shallow(filename, n), + options: Shallow::shallow(options, n), + is_eval: is_eval.clone(), + }, + } +} diff --git a/crates/perry-hir/src/walker/mod.rs b/crates/perry-hir/src/walker/mod.rs index 87282e61ce2..076c583400c 100644 --- a/crates/perry-hir/src/walker/mod.rs +++ b/crates/perry-hir/src/walker/mod.rs @@ -39,7 +39,12 @@ //! to compile. Add an arm that `f`s every `Expr`-bearing field. If the //! variant carries no `Expr` children (e.g. a new `Math.tau` constant) the //! arm is `=> {}` — group it with the existing leaf arm. -//! 3. **If the variant carries a `LocalId` field** (a recurring source of +//! `Expr::clone` (`expr_clone.rs`) relies on both walkers visiting the +//! same fields in the same order: it lists the children with one and fills +//! the holes of a node's shallow copy with the other. +//! 3. Add the variant's arm to `shallow` in `expr_clone.rs` (a compile +//! error too): a hole for each field the walkers visit, the rest cloned. +//! 4. **If the variant carries a `LocalId` field** (a recurring source of //! bug reports — see #167, #169, #212, #214), also add explicit handling //! to: //! - `perry_transform::inline::substitute_locals` @@ -49,6 +54,7 @@ use crate::ir::*; +mod expr_clone; mod expr_mut; mod expr_ref; diff --git a/crates/perry-hir/tests/expr_clone.rs b/crates/perry-hir/tests/expr_clone.rs new file mode 100644 index 00000000000..db17576bf94 --- /dev/null +++ b/crates/perry-hir/tests/expr_clone.rs @@ -0,0 +1,102 @@ +//! `Expr::clone` copies without recursion per nesting level +//! (`walker/expr_clone.rs`): a deep chain copies on a test thread's stack, +//! and every copy is the original. + +use perry_diagnostics::SourceCache; +use perry_hir::walker::walk_expr_children_mut; +use perry_hir::{lower_module, BinaryOp, Expr}; +use perry_parser::parse_typescript_with_cache; + +fn lower_src(src: &str) -> perry_hir::Module { + let mut cache = SourceCache::new(); + let parsed = parse_typescript_with_cache(src, "expr_clone.ts", &mut cache).expect("parses"); + lower_module(&parsed.module, "test", "expr_clone.ts").expect("lowers") +} + +/// `o.k0 + o.k1 + … + o.k{n-1}`, left-deep. +fn chain(n: usize) -> Expr { + let key = |i: usize| Expr::PropertyGet { + object: Box::new(Expr::LocalGet(0)), + property: format!("k{i}"), + byte_offset: 0, + byte_end: 0, + }; + let mut e = key(0); + for i in 1..n { + e = Expr::Binary { + op: BinaryOp::Add, + left: Box::new(e), + right: Box::new(key(i)), + }; + } + e +} + +/// Drop `e` a node at a time (the derived drop recurses per level). +fn dismantle(e: Expr) { + let mut todo = vec![e]; + while let Some(mut e) = todo.pop() { + walk_expr_children_mut(&mut e, &mut |c| { + todo.push(std::mem::replace(c, Expr::Undefined)) + }); + } +} + +#[test] +fn a_deep_chain_copies_on_a_test_thread_stack() { + // A derived clone took a frame of about 16 KB per level in a debug build: + // 10,000 levels are 160 MB, far beyond the 2 MB of a test thread. + let n = 10_000; + let e = chain(n); + let copy = e.clone(); + // The copy is the chain: its left spine, the key read at each level. + let mut at = © + for i in (1..n).rev() { + let Expr::Binary { + op: BinaryOp::Add, + left, + right, + } = at + else { + panic!("level {i}: {at:?}") + }; + assert_eq!( + format!("{right:?}"), + format!("{:?}", chain(1)).replace("k0", &format!("k{i}")) + ); + at = left; + } + assert_eq!(format!("{at:?}"), format!("{:?}", chain(1))); + dismantle(copy); + dismantle(e); +} + +#[test] +fn a_copy_is_the_original() { + // Every kind of child the walkers visit: boxed and listed operands, + // call and spread arguments, array holes and spreads, object values, + // parameter defaults, closure bodies (statements), class members, + // templates, optional chains. + let src = r#" + function f(a: number, b = a + 1, ...rest: number[]) { return a + b + rest.length; } + const xs = [1, , 3, ...[4, 5]]; + const o: any = { a: 1, ["b" + 1]: 2, ...{ c: 3 }, m(x: number) { return x * 2; } }; + const g = (x = () => o.a, y = xs[0]) => x() + y; + class C { + s = 1; + static t = f(1); + constructor(public p: number = 2) {} + get q() { return this.p ?? this.s; } + m(...a: number[]) { return Math.max(...a, this.s); } + } + const c = new C(); + const t = `x${c.q}y${o?.a?.b}z`; + const r = f(...xs.map((v) => v ?? 0), g(), c.m(1, 2)); + let k = 0; + for (const v of xs) { k += v ? v : -1; } + console.log(typeof r === "number" && !(r instanceof C) ? t : [r, k], delete o.a); + "#; + let m = lower_src(src); + let copy = m.clone(); + assert_eq!(format!("{copy:#?}"), format!("{m:#?}")); +} diff --git a/crates/perry-wpfacts/src/decl_units.rs b/crates/perry-wpfacts/src/decl_units.rs index 4e7db9a1632..89e9d445d10 100644 --- a/crates/perry-wpfacts/src/decl_units.rs +++ b/crates/perry-wpfacts/src/decl_units.rs @@ -7,6 +7,7 @@ use crate::pts::{FxHashMap, FxHashSet}; use perry_hir::tdz_check::{for_each_class_expr, for_each_expr_in, for_each_function_expr}; use perry_hir::{Export, Expr, Import, ImportSpecifier, Module, Stmt}; +use std::borrow::Cow; /// A declaration unit's view of its module: the HIR ids of the other /// units' declarations it refers to, each bound to the import named by the @@ -389,18 +390,104 @@ pub(crate) fn flatten(module: &Module, closed: &Closed) -> Option { changed: false, }; for s in &module.init { - flat.stmt(s); + flat.stmt(Cow::Borrowed(s)); } if !flat.changed { return None; } - let mut m = module.clone(); - m.functions.retain(|f| !flat.inlined.contains(&f.id)); - m.init = flat.out.into_iter().flatten().collect(); + let init = flat.out.into_iter().flatten().collect(); + let m = beside(module, init, &flat.inlined); owns_its_copies(module, &m, &flat.copies); Some(m) } +/// The view: a module of its own beside `module` (which codegen emits), so +/// a copy, made once per part: its statements are `init`, the flat view's, +/// and of `module`'s functions it keeps those not copied into them +/// (`inlined`). +fn beside(module: &Module, init: Vec, inlined: &FxHashSet) -> Module { + let Module { + name, + class_family_syntax, + imports, + exports, + classes, + interfaces, + type_aliases, + enums, + globals, + functions, + script_global_functions, + references_global_this, + annexb_global_undefined_names, + init_is_strict, + init: _, + classic_for_lexical_bindings, + exported_native_instances, + exported_func_return_native_instances, + exported_objects, + exported_functions, + widgets, + uses_fetch, + uses_webassembly, + extern_funcs, + init_was_unrolled, + has_top_level_await, + init_kind, + async_step_closures, + closure_display_names, + class_display_names, + closure_source_text, + class_source_text, + async_generator_funcs, + local_source_spans, + gen_param_prologue_len, + requested_modules, + } = module; + Module { + name: name.clone(), + class_family_syntax: class_family_syntax.clone(), + imports: imports.clone(), + exports: exports.clone(), + classes: classes.clone(), + interfaces: interfaces.clone(), + type_aliases: type_aliases.clone(), + enums: enums.clone(), + globals: globals.clone(), + functions: functions + .iter() + .filter(|f| !inlined.contains(&f.id)) + .cloned() + .collect(), + script_global_functions: script_global_functions.clone(), + references_global_this: *references_global_this, + annexb_global_undefined_names: annexb_global_undefined_names.clone(), + init_is_strict: *init_is_strict, + init, + classic_for_lexical_bindings: classic_for_lexical_bindings.clone(), + exported_native_instances: exported_native_instances.clone(), + exported_func_return_native_instances: exported_func_return_native_instances.clone(), + exported_objects: exported_objects.clone(), + exported_functions: exported_functions.clone(), + widgets: widgets.clone(), + uses_fetch: *uses_fetch, + uses_webassembly: *uses_webassembly, + extern_funcs: extern_funcs.clone(), + init_was_unrolled: *init_was_unrolled, + has_top_level_await: *has_top_level_await, + init_kind: *init_kind, + async_step_closures: async_step_closures.clone(), + closure_display_names: closure_display_names.clone(), + class_display_names: class_display_names.clone(), + closure_source_text: closure_source_text.clone(), + class_source_text: class_source_text.clone(), + async_generator_funcs: async_generator_funcs.clone(), + local_source_spans: local_source_spans.clone(), + gen_param_prologue_len: gen_param_prologue_len.clone(), + requested_modules: requested_modules.clone(), + } +} + /// The references in a module: per module function its `FuncRef`s, per /// local its uses (reads, writes), and per local the CommonJS module /// records holding it (the factory argument of the wrapper's @@ -520,8 +607,10 @@ struct Flat<'a> { type Dest<'s> = Option<(u32, &'s str, &'s perry_hir::types::Type, bool)>; impl Flat<'_> { - fn stmt(&mut self, s: &Stmt) { - match s { + /// Emit `s` into the view, flat. A statement the view owns already (of + /// a literal moved out of its `let`) moves; one of the module is copied. + fn stmt(&mut self, s: Cow<'_, Stmt>) { + match &*s { Stmt::Expr(Expr::Call { callee, args, .. }) if self.call(callee, args, None) => return, Stmt::Let { id, @@ -553,7 +642,7 @@ impl Flat<'_> { } _ => {} } - self.out.push(Some(s.clone())); + self.out.push(Some(s.into_owned())); } /// Inline the call if its callee's body runs once here (true). @@ -571,7 +660,7 @@ impl Flat<'_> { } self.inlined.insert(*id); self.copies.push(Copy::Func(*id, f.name.clone())); - self.body(&f.params, args, &f.body, dest); + self.body(&f.params, args, Cow::Borrowed(&f.body), dest); return true; } Expr::LocalGet(id) => { @@ -610,8 +699,7 @@ impl Flat<'_> { if !once { return false; } - let lit = lit.clone(); - if !inline_literal(&lit) { + if !inline_literal(lit) { return false; } let top = self @@ -623,35 +711,52 @@ impl Flat<'_> { true => Copy::Factory(*id), false => Copy::Let(*id, top.then(|| name.clone())), }); - self.out[k] = match self.out[k].take() { - Some(Stmt::Let { - id, - name, - ty, - mutable, - .. - }) if factory => Some(Stmt::Let { + // The literal moves out of its `let` into the view's + // statements; the `let` goes, or (the factory) stays bound + // to `undefined`. + let Some(Stmt::Let { + id, + name, + ty, + mutable, + init: Some(lit), + }) = self.out[k].take() + else { + unreachable!("the `let` of the literal"); + }; + if factory { + self.out[k] = Some(Stmt::Let { id, name, ty, mutable, init: Some(Expr::Undefined), - }), - _ => None, - }; - lit + }); + } + Cow::Owned(lit) } - Expr::Closure { .. } if inline_literal(callee) => callee.clone(), + Expr::Closure { .. } if inline_literal(callee) => Cow::Borrowed(callee), _ => return false, }; - let Expr::Closure { params, body, .. } = &lit else { - return false; - }; - self.body(params, args, body, dest); + match lit { + Cow::Owned(Expr::Closure { params, body, .. }) => { + self.body(¶ms, args, Cow::Owned(body), dest) + } + Cow::Borrowed(Expr::Closure { params, body, .. }) => { + self.body(params, args, Cow::Borrowed(body), dest) + } + _ => return false, + } true } - fn body(&mut self, params: &[perry_hir::Param], args: &[Expr], body: &[Stmt], dest: Dest) { + fn body( + &mut self, + params: &[perry_hir::Param], + args: &[Expr], + body: Cow<'_, [Stmt]>, + dest: Dest, + ) { self.changed = true; for (i, p) in params.iter().enumerate() { let init = args.get(i).cloned().unwrap_or(Expr::Undefined); @@ -666,27 +771,46 @@ impl Flat<'_> { for a in args.iter().skip(params.len()) { self.out.push(Some(Stmt::Expr(a.clone()))); } - let (stmts, result) = match body.split_last() { - Some((Stmt::Return(e), rest)) => (rest, e.clone()), - _ => (body, None), + // The body's statements and its final `return`'s value. + let result = match body { + Cow::Borrowed(body) => { + let (stmts, result) = match body.split_last() { + Some((Stmt::Return(e), rest)) => (rest, e.clone()), + _ => (body, None), + }; + for s in stmts { + self.stmt(Cow::Borrowed(s)); + } + result + } + Cow::Owned(mut body) => { + let result = match body.pop() { + Some(Stmt::Return(e)) => e, + last => { + body.extend(last); + None + } + }; + for s in body { + self.stmt(Cow::Owned(s)); + } + result + } }; - for s in stmts { - self.stmt(s); - } match dest { Some((id, name, ty, mutable)) => { let init = Some(result.unwrap_or(Expr::Undefined)); - self.stmt(&Stmt::Let { + self.stmt(Cow::Owned(Stmt::Let { id, name: name.to_string(), ty: ty.clone(), mutable, init, - }); + })); } None => { if let Some(e) = result { - self.stmt(&Stmt::Expr(e)); + self.stmt(Cow::Owned(Stmt::Expr(e))); } } } diff --git a/crates/perry-wpfacts/src/tests/run_once_closed.rs b/crates/perry-wpfacts/src/tests/run_once_closed.rs index c830d085cce..75263fc7024 100644 --- a/crates/perry-wpfacts/src/tests/run_once_closed.rs +++ b/crates/perry-wpfacts/src/tests/run_once_closed.rs @@ -321,3 +321,37 @@ fn a_copy_of_a_published_body_is_an_internal_compiler_error() { .collect(); crate::decl_units::flatten(m, &all); } + +#[test] +fn a_deep_chain_flattens_on_a_test_thread_stack() { + // The run-once view copies the statements of the bodies it flattens. A + // derived `Expr::clone` recursed per operand, about 16 KB a level in a + // debug build: the 150 operands below took about 2.4 MB, beyond the 2 MB + // of a test thread (the 64 of + // `a_closed_local_called_only_in_its_unit_costs_its_own_stores` took 1.1). + let n = 150; + let sum: Vec = (0..n).map(|i| format!("o.k{i}")).collect(); + let src = format!( + "function run(o: any) {{ return {}; }}\nconst r = run({{}});\nconsole.log(r);\n", + sum.join(" + ") + ); + let h = hirs(&[("a", src.as_str())]); + let flat = crate::decl_units::run_once_flat(&h[0].1).expect("`run` runs once"); + assert!(flat.functions.iter().all(|f| f.name != "run"), "`run` is flat"); + // `r` is bound to the chain: n - 1 additions down its left spine. + let r = flat + .init + .iter() + .find_map(|s| match s { + Stmt::Let { name, init: Some(e), .. } if name == "r" => Some(e), + _ => None, + }) + .expect("the `let` of `r`"); + let mut depth = 0; + let mut at = r; + while let Expr::Binary { left, .. } = at { + depth += 1; + at = left; + } + assert_eq!(depth, n - 1, "{at:?}"); +} From 8a8c0a416989d896addbd2ad495b482572d3e412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 11:35:12 +0100 Subject: [PATCH 066/144] changelog: number the PR fragment --- ...latten-no-deep-clone.md => 13621-flx-flatten-no-deep-clone.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-flx-flatten-no-deep-clone.md => 13621-flx-flatten-no-deep-clone.md} (100%) diff --git a/changelog.d/0-flx-flatten-no-deep-clone.md b/changelog.d/13621-flx-flatten-no-deep-clone.md similarity index 100% rename from changelog.d/0-flx-flatten-no-deep-clone.md rename to changelog.d/13621-flx-flatten-no-deep-clone.md From c7331f1bb0480461f4a3c80cfbf35a8d1cc1bd22 Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:10:20 +0100 Subject: [PATCH 067/144] fix(worker_threads): reuse canonical messaging constructor exports --- changelog.d/0-worker-constructor-exports.md | 1 + .../src/object/global_this/populate.rs | 7 +- .../perry-runtime/src/object/native_module.rs | 36 ++++++++ .../src/object/native_module/constants.rs | 16 +--- .../object/native_module/static_exports.rs | 89 +++++++++++++++++++ docs/src/threading/overview.md | 4 + .../constructor-export-sync.ts | 47 ++++++++++ .../message-channel/constructor-exports.ts | 44 +++++++++ 8 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 changelog.d/0-worker-constructor-exports.md create mode 100644 test-parity/node-suite/worker_threads/message-channel/constructor-export-sync.ts create mode 100644 test-parity/node-suite/worker_threads/message-channel/constructor-exports.ts diff --git a/changelog.d/0-worker-constructor-exports.md b/changelog.d/0-worker-constructor-exports.md new file mode 100644 index 00000000000..f6f9de06e5f --- /dev/null +++ b/changelog.d/0-worker-constructor-exports.md @@ -0,0 +1 @@ +Fix worker messaging constructor exports to share their original thread-realm objects across import forms and captured construction. Keep mutable global replacements separate from core-module exports while preserving explicit CommonJS overrides and named-export synchronization. diff --git a/crates/perry-runtime/src/object/global_this/populate.rs b/crates/perry-runtime/src/object/global_this/populate.rs index d0b14edc177..cbc902d779c 100644 --- a/crates/perry-runtime/src/object/global_this/populate.rs +++ b/crates/perry-runtime/src/object/global_this/populate.rs @@ -442,7 +442,7 @@ fn populate_global_this_builtins_inner(singleton_at_entry: *mut ObjectHeader) { ); } } - if matches!(name, "MessageChannel" | "MessagePort" | "BroadcastChannel") { + if super::super::native_module::is_worker_messaging_constructor(name) { crate::messaging_hook_slots::populate_messaging_prototype( name, proto_obj, ctor_value, ); @@ -583,6 +583,11 @@ fn populate_global_this_builtins_inner(singleton_at_entry: *mut ObjectHeader) { name.to_string(), super::super::PropertyAttrs::new(true, false, true), ); + super::super::native_module::publish_worker_messaging_constructor( + singleton(), + name, + ctor_value, + ); } // Interface inheritance carries prototype methods. DOMException has // Error.prototype as its instance parent, but no Error constructor parent. diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index 842e74d575c..682c4541605 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -262,6 +262,42 @@ pub extern "C" fn js_vm_create_script_branded(code: f64, options: f64) -> f64 { ) } +pub(crate) fn is_worker_messaging_constructor(property: &str) -> bool { + matches!( + property, + "MessageChannel" | "MessagePort" | "BroadcastChannel" + ) +} + +pub(crate) fn publish_worker_messaging_constructor( + global: *mut ObjectHeader, + property: &str, + value: f64, +) { + if !is_worker_messaging_constructor(property) + || !super::global_this::fetch_globals::is_thread_realm_global(global) + { + return; + } + crate::gc::ensure_gc_initialized(); + let key = format!("worker_threads\0{property}"); + NATIVE_CALLABLE_EXPORTS.with(|cache| { + cache.borrow_mut().insert(key, value.to_bits()); + }); + crate::gc::runtime_write_barrier_root_nanbox(value.to_bits()); +} + +pub(super) fn worker_messaging_constructor_value(module: &str, property: &str) -> Option { + if module != "worker_threads" || !is_worker_messaging_constructor(property) { + return None; + } + // Birth publishes the original value; reading a mutable global here would + // let reassignment or an accessor redefine a core-module export. + crate::object::js_get_global_this(); + let key = format!("worker_threads\0{property}"); + NATIVE_CALLABLE_EXPORTS.with(|cache| cache.borrow().get(&key).copied().map(f64::from_bits)) +} + pub fn scan_native_callable_export_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { NATIVE_CALLABLE_EXPORTS.with(|cache| { let mut cache = cache.borrow_mut(); diff --git a/crates/perry-runtime/src/object/native_module/constants.rs b/crates/perry-runtime/src/object/native_module/constants.rs index 4211cfb5a5e..d3e53104f45 100644 --- a/crates/perry-runtime/src/object/native_module/constants.rs +++ b/crates/perry-runtime/src/object/native_module/constants.rs @@ -835,20 +835,8 @@ pub(crate) unsafe fn selected_native_module_constant( // ~8 process tests in the node-core radar (#2135) were "skipping" // when they should have been running. (#2135) "worker_threads" => match property { - "MessageChannel" | "MessagePort" | "BroadcastChannel" => { - let global = crate::object::js_get_global_this(); - let global_obj = crate::value::js_nanbox_get_pointer(global) as *const ObjectHeader; - if global_obj.is_null() { - Some(f64::from_bits(JSValue::undefined().bits())) - } else { - let key = crate::string::js_string_from_bytes( - property.as_ptr(), - property.len() as u32, - ); - Some(f64::from_bits( - js_object_get_field_by_name(global_obj, key).bits(), - )) - } + _ if is_worker_messaging_constructor(property) => { + worker_messaging_constructor_value(module_name, property) } "isMainThread" => Some(call_worker_threads_getter( &WORKER_THREADS_IS_MAIN_THREAD_GETTER, diff --git a/crates/perry-runtime/src/object/native_module/static_exports.rs b/crates/perry-runtime/src/object/native_module/static_exports.rs index 7909ea29041..111970f8e3f 100644 --- a/crates/perry-runtime/src/object/native_module/static_exports.rs +++ b/crates/perry-runtime/src/object/native_module/static_exports.rs @@ -189,6 +189,9 @@ include!(concat!(env!("OUT_DIR"), "/native_export_values.rs")); /// them a fresh callable would break Buffer/URL/TextDecoder identity. #[inline(always)] fn intrinsic_export(module: &str, property: &str) -> Option { + if let Some(value) = worker_messaging_constructor_value(module, property) { + return Some(value); + } if module == "buffer" && property == "Buffer" { return Some(callable_exports::buffer_constructor_value()); } @@ -281,3 +284,89 @@ pub(crate) unsafe fn call_minted( len, )) } + +#[cfg(test)] +mod messaging_constructor_ownership_tests { + use super::*; + use crate::gc::RuntimeHandleScope; + use crate::value::{js_nanbox_get_pointer, JSValue}; + + #[test] + fn messaging_constructor_ownership_survives_global_reassignment() { + crate::gc::gc_init(); + let scope = RuntimeHandleScope::new(); + let global = scope.root_nanbox_f64(crate::object::js_get_global_this()); + for name in ["MessageChannel", "MessagePort", "BroadcastChannel"] { + let canonical = scope.root_nanbox_f64( + worker_messaging_constructor_value("worker_threads", name).unwrap(), + ); + assert!(!JSValue::from_bits(canonical.get_nanbox_u64()).is_undefined()); + let key = scope.root_string_ptr(crate::string::js_string_from_bytes( + name.as_ptr(), + name.len() as u32, + )); + let previous = + scope.root_nanbox_f64(key.with_const_ptr::(|key| { + crate::object::js_object_get_field_by_name_f64( + js_nanbox_get_pointer(global.get_nanbox_f64()) as *const _, + key, + ) + })); + key.with_const_ptr::(|key| { + crate::object::js_object_set_field_by_name( + js_nanbox_get_pointer(global.get_nanbox_f64()) as *mut _, + key, + 123.0, + ) + }); + let selected = scope.root_nanbox_f64( + intrinsic_export("worker_threads", name) + .unwrap_or(f64::from_bits(JSValue::undefined().bits())), + ); + let generic = scope.root_nanbox_f64( + unsafe { get_native_module_constant("worker_threads", name, 0.0) } + .unwrap_or(f64::from_bits(JSValue::undefined().bits())), + ); + key.with_const_ptr::(|key| { + crate::object::js_object_set_field_by_name( + js_nanbox_get_pointer(global.get_nanbox_f64()) as *mut _, + key, + previous.get_nanbox_f64(), + ) + }); + assert_eq!(selected.get_nanbox_u64(), canonical.get_nanbox_u64()); + assert_eq!(generic.get_nanbox_u64(), canonical.get_nanbox_u64()); + } + assert!(intrinsic_export("worker_threads", "Worker").is_none()); + assert!(intrinsic_export("unrelated", "MessageChannel").is_none()); + } + + #[test] + fn messaging_constructor_ownership_excludes_foreign_realm_publication() { + crate::gc::gc_init(); + let scope = RuntimeHandleScope::new(); + let global = scope.root_nanbox_f64(crate::object::js_get_global_this()); + let canonical = scope.root_nanbox_f64( + worker_messaging_constructor_value("worker_threads", "MessageChannel").unwrap(), + ); + assert!(!JSValue::from_bits(canonical.get_nanbox_u64()).is_undefined()); + let foreign = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); + let other = scope.root_nanbox_f64( + worker_messaging_constructor_value("worker_threads", "MessagePort").unwrap(), + ); + publish_worker_messaging_constructor( + foreign.get_raw_mut_ptr::(), + "MessageChannel", + other.get_nanbox_f64(), + ); + let observed = scope.root_nanbox_f64( + worker_messaging_constructor_value("worker_threads", "MessageChannel").unwrap(), + ); + publish_worker_messaging_constructor( + js_nanbox_get_pointer(global.get_nanbox_f64()) as *mut _, + "MessageChannel", + canonical.get_nanbox_f64(), + ); + assert_eq!(observed.get_nanbox_u64(), canonical.get_nanbox_u64()); + } +} diff --git a/docs/src/threading/overview.md b/docs/src/threading/overview.md index 0786ddc9a05..97fb48cb2c9 100644 --- a/docs/src/threading/overview.md +++ b/docs/src/threading/overview.md @@ -238,6 +238,10 @@ Main-thread `postMessageToThread` rejects missing or stopped destinations before cloning the payload or detaching transfer buffers. Worker-origin messages retain Node's forwarding order: cloning precedes destination rejection. Admission does not guarantee rollback if a destination exits concurrently after it was admitted. +Core-module messaging constructor exports retain the original thread-realm +MessageChannel, MessagePort and BroadcastChannel objects even after global +reassignment. Explicit CommonJS export overrides and named-export synchronization +remain separate; vm/eval global population does not replace these thread-core values. Other worker API parity gaps remain. ## Next Steps diff --git a/test-parity/node-suite/worker_threads/message-channel/constructor-export-sync.ts b/test-parity/node-suite/worker_threads/message-channel/constructor-export-sync.ts new file mode 100644 index 00000000000..e88142bcea7 --- /dev/null +++ b/test-parity/node-suite/worker_threads/message-channel/constructor-export-sync.ts @@ -0,0 +1,47 @@ +import * as wt from "node:worker_threads"; +import { MessageChannel as MC, MessagePort as MP, BroadcastChannel as BC } from "node:worker_threads"; +import { syncBuiltinESMExports } from "node:module"; + +const originals = [globalThis.MessageChannel, globalThis.MessagePort, globalThis.BroadcastChannel]; +const names = ["MessageChannel", "MessagePort", "BroadcastChannel"]; +console.log("named", MC === originals[0], MP === originals[1], BC === originals[2]); +try { + const channel: any = Reflect.construct(MC, []); + channel.port1.on("message", (value: any) => { + console.log("named delivery", value); + channel.port1.close(); + channel.port2.close(); + }); + channel.port2.postMessage("without CJS initialization"); +} catch (error: any) { + console.log("named construction", error.name, error.code ?? ""); +} +function globalReplacement() {} +let reads = 0; +for (const name of names) { + Object.defineProperty(globalThis, name, { + configurable: true, + get() { reads++; return globalReplacement; }, + }); +} +try { + syncBuiltinESMExports(); + console.log("global sync", MC === originals[0], MP === originals[1], BC === originals[2], reads); +} finally { + names.forEach((name, index) => Object.defineProperty(globalThis, name, { + value: originals[index], writable: true, configurable: true, + })); + syncBuiltinESMExports(); +} +const cjs: any = process.getBuiltinModule("worker_threads"); +function moduleReplacement() {} +cjs.MessageChannel = moduleReplacement; +try { + console.log("before CJS sync", MC === originals[0], cjs.MessageChannel === moduleReplacement); + syncBuiltinESMExports(); + console.log("after CJS sync", MC === moduleReplacement, globalThis.MessageChannel === originals[0]); +} finally { + cjs.MessageChannel = originals[0]; + syncBuiltinESMExports(); +} +console.log("restored", MC === originals[0], wt.MessagePort === originals[1]); diff --git a/test-parity/node-suite/worker_threads/message-channel/constructor-exports.ts b/test-parity/node-suite/worker_threads/message-channel/constructor-exports.ts new file mode 100644 index 00000000000..78d79c8b788 --- /dev/null +++ b/test-parity/node-suite/worker_threads/message-channel/constructor-exports.ts @@ -0,0 +1,44 @@ +import * as wt from "node:worker_threads"; + +const originals = [globalThis.MessageChannel, globalThis.MessagePort, globalThis.BroadcastChannel]; +const names = ["MessageChannel", "MessagePort", "BroadcastChannel"]; +function globalReplacement() {} +let reads = 0; +globalThis.MessageChannel = globalReplacement as any; +for (const name of ["MessagePort", "BroadcastChannel"]) { + Object.defineProperty(globalThis, name, { + configurable: true, + get() { reads++; return globalReplacement; }, + }); +} +let constructors: any[] = []; +try { + constructors = [wt.MessageChannel, wt.MessagePort, wt.BroadcastChannel]; + const cjs: any = process.getBuiltinModule("worker_threads"); + console.log("cold namespace", constructors.map((value, index) => value === originals[index]).join(",")); + console.log("cold CJS", names.map((name, index) => cjs[name] === originals[index]).join(",")); + console.log("global getter reads", reads); +} finally { + names.forEach((name, index) => Object.defineProperty(globalThis, name, { + value: originals[index], writable: true, configurable: true, + })); +} +console.log("prototypes", constructors.map((value, index) => value.prototype === originals[index].prototype).join(",")); +try { + const channel: any = Reflect.construct(constructors[0], []); + channel.port1.on("message", (value: any) => { + console.log("delivery", value); + channel.port1.close(); + channel.port2.close(); + }); + channel.port2.postMessage("captured"); +} catch (error: any) { + console.log("construct", error.name, error.code ?? ""); +} +try { + const broadcast: any = Reflect.construct(constructors[2], ["constructor-exports"]); + console.log("broadcast", broadcast.name); + broadcast.close(); +} catch (error: any) { + console.log("broadcast error", error.name, error.code ?? ""); +} From efabfe1273948a7903fdda232fd4f108b3c0cabf Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:13:30 +0100 Subject: [PATCH 068/144] docs(changelog): number constructor export fragment --- ...constructor-exports.md => 13576-worker-constructor-exports.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-worker-constructor-exports.md => 13576-worker-constructor-exports.md} (100%) diff --git a/changelog.d/0-worker-constructor-exports.md b/changelog.d/13576-worker-constructor-exports.md similarity index 100% rename from changelog.d/0-worker-constructor-exports.md rename to changelog.d/13576-worker-constructor-exports.md From 70a57fe4dfe0744af32650f748cb1e46f4ec1343 Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:00:00 +0100 Subject: [PATCH 069/144] fix(codegen): namespace-importing yourself populates before the body runs A module that namespace-imports ITSELF (import * as Self from "./this-module.ts") is its own namespace consumer: ESM guarantees the namespace object exists before evaluation with live bindings, so the body may read it. trails' activesupport does so at top level, via include(), which enumerates the namespace with Object.getOwnPropertyNames. Perry evaluated that as undefined and then threw 'Cannot convert undefined or null to object' inside the mixin. Two things had to hold and neither did: the driver never put a self-importing module in dyn_target_paths, so no @__perry_ns_ global was emitted; and the populator ran at the tail of __init, after the body that reads it. The driver now detects the shape from the HIR (a non-type-only, non-erased ImportSpecifier::Namespace whose resolved_path is this module's own path), marks the module as a namespace target, and sets CompileOptions:: namespace_self_import, which keys the object cache because it changes emitted IR while the HIR does not. Both emitters - entry.rs for the entry module, whose init lives inside main, and module_init.rs::compile_module_init for non-entry modules - now populate before the body and skip the tail emission. test-files/test_gap_self_namespace_import.ts covers member reads, the whole namespace during the module's own init, typeof, and the activesupport mixin shape. Before: 'UNDEFINED' + TypeError; after: 'object ["C","K","f"]' and 'mixin keys = ["C","K","f"]', identical to Node. --- crates/perry-codegen/src/codegen/entry.rs | 24 +++++++++- .../src/codegen/entry/module_init.rs | 19 +++++++- crates/perry-codegen/src/codegen/mod.rs | 1 + crates/perry-codegen/src/codegen/opts.rs | 10 +++++ .../src/commands/compile/object_cache.rs | 7 +++ .../src/commands/compile/run_pipeline.rs | 44 ++++++++++++++++++ test-files/test_gap_self_namespace_import.ts | 45 +++++++++++++++++++ 7 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 test-files/test_gap_self_namespace_import.ts diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index c39242600c1..eef149834d7 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -1187,6 +1187,26 @@ pub(super) fn compile_module_entry( if !is_esm_entry { emit_annexb_global_undefined_decls(&mut ctx, hir); } + // A module that namespace-imports ITSELF is its own namespace + // consumer: its namespace object must exist before the body runs, + // because the body may hand the whole namespace to a function that + // enumerates it (trails' activesupport does this through + // `include()`). Populating at the tail left that read seeing the + // global's initial NaN-boxed `undefined`. + let self_namespace_populated = cross_module.namespace_self_import + && (!cross_module.namespace_entries.is_empty() + || cross_module.is_dynamic_import_target) + && !ctx.block().is_terminated(); + if self_namespace_populated { + emit_namespace_populator( + &mut ctx, + &cross_module.namespace_entries, + namespace_key_globals, + module_prefix, + output_type, + cross_module.namespace_adopts_cjs_exports, + ); + } stmt::lower_top_level_stmts(&mut ctx, main_init) .with_context(|| format!("lowering init statements of module '{}'", hir.name))?; init_static_fields_late(&mut ctx, hir)?; @@ -1204,7 +1224,9 @@ pub(super) fn compile_module_entry( // is a target). The populator emits `js_create_namespace(0, ...)` // → an empty NaN-boxed object → stored into `@__perry_ns_`, // satisfying the consumer-side extern reference. - if (!cross_module.namespace_entries.is_empty() || cross_module.is_dynamic_import_target) + if !self_namespace_populated + && (!cross_module.namespace_entries.is_empty() + || cross_module.is_dynamic_import_target) && !ctx.block().is_terminated() { emit_namespace_populator( diff --git a/crates/perry-codegen/src/codegen/entry/module_init.rs b/crates/perry-codegen/src/codegen/entry/module_init.rs index eaa06b21d72..dd4d477094e 100644 --- a/crates/perry-codegen/src/codegen/entry/module_init.rs +++ b/crates/perry-codegen/src/codegen/entry/module_init.rs @@ -644,6 +644,22 @@ pub(super) fn compile_module_init( } } init_static_fields_early(&mut ctx, hir)?; + // See the same reasoning in the entry emitter: a module that + // namespace-imports itself must have its namespace populated before its + // own body runs. + let self_namespace_populated = cross_module.namespace_self_import + && (!cross_module.namespace_entries.is_empty() || cross_module.is_dynamic_import_target) + && !ctx.block().is_terminated(); + if self_namespace_populated { + emit_namespace_populator( + &mut ctx, + &cross_module.namespace_entries, + namespace_key_globals, + module_prefix, + output_type, + cross_module.namespace_adopts_cjs_exports, + ); + } stmt::lower_top_level_stmts(&mut ctx, &hir.init) .with_context(|| format!("lowering init statements of module '{}'", hir.name))?; init_static_fields_late(&mut ctx, hir)?; @@ -661,7 +677,8 @@ pub(super) fn compile_module_init( // is a target). The populator emits `js_create_namespace(0, ...)` // → an empty NaN-boxed object → stored into `@__perry_ns_`, // satisfying the consumer-side extern reference. - if (!cross_module.namespace_entries.is_empty() || cross_module.is_dynamic_import_target) + if !self_namespace_populated + && (!cross_module.namespace_entries.is_empty() || cross_module.is_dynamic_import_target) && !ctx.block().is_terminated() { emit_namespace_populator( diff --git a/crates/perry-codegen/src/codegen/mod.rs b/crates/perry-codegen/src/codegen/mod.rs index 501667e86e8..6e47715abbe 100644 --- a/crates/perry-codegen/src/codegen/mod.rs +++ b/crates/perry-codegen/src/codegen/mod.rs @@ -3036,6 +3036,7 @@ fn lower_module( program_has_thread_agents: thread_agents, class_hierarchy: class_hierarchy::ClassHierarchy::default(), namespace_imports: opts.namespace_imports.iter().cloned().collect(), + namespace_self_import: opts.namespace_self_import, namespace_source_prefixes: opts.namespace_source_prefixes, namespace_member_nested: opts.namespace_member_nested.iter().cloned().collect(), cjs_interop_namespaces: opts.cjs_interop_namespaces.iter().cloned().collect(), diff --git a/crates/perry-codegen/src/codegen/opts.rs b/crates/perry-codegen/src/codegen/opts.rs index add4f0d1c8e..26a60a72262 100644 --- a/crates/perry-codegen/src/codegen/opts.rs +++ b/crates/perry-codegen/src/codegen/opts.rs @@ -340,6 +340,14 @@ pub struct CompileOptions { /// Locals that are namespace imports (`import * as X from "./mod"`). /// Codegen uses this to know that `X.foo()` should be dispatched as /// a cross-module call rather than an object method call. + /// A module that namespace-imports ITSELF (`import * as Mod from + /// "./this-module.js"`). Its `@__perry_ns_` populator must run + /// BEFORE the top-level body, because the body is itself a namespace + /// consumer: trails' activesupport does `import * as C from + /// "./calculations.js"` and then, at top level, `include(RubyTime, C)`, + /// which reads `Object.getOwnPropertyNames(C)`. Populating at the tail left + /// that read seeing the global's initial NaN-boxed `undefined`. + pub namespace_self_import: bool, pub namespace_imports: Vec, /// Namespace local -> owning compiled module prefix, before following re-exports. pub namespace_source_prefixes: std::collections::HashMap, @@ -986,6 +994,8 @@ pub(crate) struct CrossModuleCtx<'a> { /// The class and method tables inverted (`class_hierarchy`). Set once /// both tables are final, before any function body is lowered. pub class_hierarchy: super::class_hierarchy::ClassHierarchy, + /// See `CompileOptions::namespace_self_import`. + pub namespace_self_import: bool, pub namespace_imports: std::collections::HashSet, pub namespace_source_prefixes: std::collections::HashMap, /// #7189: `(namespace local, member)` pairs whose member is itself a MODULE diff --git a/crates/perry/src/commands/compile/object_cache.rs b/crates/perry/src/commands/compile/object_cache.rs index 77c3d9f4543..d99ff1ae6ff 100644 --- a/crates/perry/src/commands/compile/object_cache.rs +++ b/crates/perry/src/commands/compile/object_cache.rs @@ -638,6 +638,13 @@ fn compute_object_cache_key_with_env( ); } + // The self-namespace flag moves the populator from the tail of `__init` to + // before the body, so it changes the emitted IR and must key the cache. + h.field( + "self_ns", + if opts.namespace_self_import { "1" } else { "0" }, + ); + // The owning namespace module can differ from all member origins (barrels). { let mut values: Vec<_> = opts.namespace_source_prefixes.iter().collect(); diff --git a/crates/perry/src/commands/compile/run_pipeline.rs b/crates/perry/src/commands/compile/run_pipeline.rs index 38dbf77b94e..51e8e150377 100644 --- a/crates/perry/src/commands/compile/run_pipeline.rs +++ b/crates/perry/src/commands/compile/run_pipeline.rs @@ -3090,6 +3090,30 @@ pub fn run_with_parse_cache( } } } + + // A module that namespace-imports ITSELF (`import * as Self from + // "./this-module.ts"`) is its own namespace consumer: it needs its own + // `@__perry_ns_` global emitted and populated, which is what + // membership in this set means. The populator runs before the module's + // top-level body (see `CompileOptions::namespace_self_import`), because + // that body may hand the whole namespace to a function that enumerates it + // — trails' activesupport does exactly that through `include()`. + for (path, hir_module) in &ctx.native_modules { + let self_namespace_import = hir_module.imports.iter().any(|import| { + !import.type_only + && !import.runtime_erased + && import.specifiers.iter().any(|spec| { + matches!(spec, perry_hir::ImportSpecifier::Namespace { .. }) + }) + && import + .resolved_path + .as_deref() + .is_some_and(|resolved| Path::new(resolved) == path.as_path()) + }); + if self_namespace_import { + dyn_target_paths.insert(path.clone()); + } + } // #7189: `export * as ns from "./m.ts"` names a member whose VALUE is // another module's namespace object. That object is exactly what // `@__perry_ns_` already holds for dynamic-import targets, and @@ -6166,6 +6190,26 @@ pub fn run_with_parse_cache( disable_buffer_fast_path, namespace_imports, namespace_source_prefixes, + // A module that namespace-imports ITSELF (`import * as Self from + // "./this-module.ts"`) is its own namespace consumer: its + // `@__perry_ns_` populator must run before its top-level + // body, because that body may pass the whole namespace to a + // function that enumerates it. Per-member reads already worked, + // which is why the undefined value stayed silent. + namespace_self_import: ctx.native_modules.get(path).is_some_and(|hir_module| { + + hir_module.imports.iter().any(|import| { + !import.type_only + && !import.runtime_erased + && import.specifiers.iter().any(|spec| { + matches!(spec, perry_hir::ImportSpecifier::Namespace { .. }) + }) + && import + .resolved_path + .as_deref() + .is_some_and(|resolved| Path::new(resolved) == path.as_path()) + }) + }), namespace_member_nested: namespace_member_nested.into_iter().collect(), cjs_interop_namespaces: cjs_interop_namespaces.into_iter().collect(), imported_classes, diff --git a/test-files/test_gap_self_namespace_import.ts b/test-files/test_gap_self_namespace_import.ts new file mode 100644 index 00000000000..215356817ea --- /dev/null +++ b/test-files/test_gap_self_namespace_import.ts @@ -0,0 +1,45 @@ +// A module that imports its OWN namespace. Valid ESM: the namespace object +// exists before evaluation with live bindings, so the module's own body can +// read it. trails' activesupport relies on this to hand a module its own +// export set to a mixin: +// +// import * as DateAndTimeCalculations from "./calculations.js"; +// include(RubyTime, DateAndTimeCalculations); +// +// Before this fix the whole-namespace value was `undefined` during the +// module's own init -- `@__perry_ns_` was emitted only for modules +// some OTHER module namespace-imported, and its populator ran at the tail of +// `__init`, after the body that reads it. Per-member reads already worked, +// which is why this stayed silent: no error, no warning, exit code 0. +import * as self from "./test_gap_self_namespace_import.ts"; + +export function f(): number { + return 1; +} +export const C = 7; +export class K {} + +// Per-member reads: resolve to this module's own symbols. +console.log("member call =", self.f()); +console.log("member const =", self.C); +console.log("typeof member fn =", typeof self.f); +console.log("typeof member cls=", typeof self.K); + +// Whole-namespace reads DURING this module's own init -- the regression. +function describe(ns: unknown): string { + if (ns === undefined) return "UNDEFINED"; + if (ns === null) return "NULL"; + return typeof ns + " " + JSON.stringify(Object.getOwnPropertyNames(ns as object).sort()); +} +console.log("whole namespace =", describe(self)); +console.log("typeof namespace =", typeof self); + +// The activesupport shape: the namespace passed to a function that enumerates +// it, at top level, exactly as `include()` does. +function mixin(target: Record, mod: object): string { + for (const key of Object.getOwnPropertyNames(mod)) { + target[key] = (mod as Record)[key]; + } + return JSON.stringify(Object.keys(target).sort()); +} +console.log("mixin keys =", mixin({}, self)); From 935405bf6e1cd1ec2f5b2bd6885e025f56c31efd Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:00:23 +0100 Subject: [PATCH 070/144] changelog: record the self-namespace import fix --- changelog.d/13622-self-namespace-import.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13622-self-namespace-import.md diff --git a/changelog.d/13622-self-namespace-import.md b/changelog.d/13622-self-namespace-import.md new file mode 100644 index 00000000000..518eef89a47 --- /dev/null +++ b/changelog.d/13622-self-namespace-import.md @@ -0,0 +1 @@ +**A module that namespace-imports itself now reads its own namespace during its own init.** ESM guarantees the namespace object exists before evaluation with live bindings, so `import * as Self from "./this-module.ts"` followed by a top-level `include(RubyTime, Self)` — the activesupport shape, which enumerates the namespace with `Object.getOwnPropertyNames` — is legal and works on Node. Perry evaluated it as `undefined` and threw `TypeError: Cannot convert undefined or null to object` at module-init time; per-member reads (`Self.f()`) already worked, which is why the failure stayed silent. Two things were missing: a self-importing module is a namespace consumer of itself, so the driver now puts it in the `dyn_target_paths` set that decides which modules get a `@__perry_ns_` global at all; and the populator now runs *before* the module's top-level body instead of at the tail. Both emitters needed the reordering — `entry.rs` for the entry module, whose init lives inside `main`, and `module_init.rs::compile_module_init` for non-entry modules — because which one runs depends on the module, and changing one alone leaves the failing case untouched. The flag is derived in the driver from the HIR (a non-type-only `ImportSpecifier::Namespace` whose `resolved_path` is the module's own path) and keys the object cache, since it changes emitted IR while the HIR does not. From 7c01c1062e9a6950389984bdc671cab9e03a6fc4 Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:34:29 +0100 Subject: [PATCH 071/144] feat(wpfacts): summarize the builtin constructor families the model was missing ns_by_name now maps AggregateError, Date, RegExp, the typed arrays and ArrayBuffer/SharedArrayBuffer, which is what makes BOTH spellings resolve: construct_named consults it before its own table. - AggregateError shares Error's summary and models its errors field from the iterated first argument. - Date as a call is ToString-shaped; as new it is an object summary of kind Date. - RegExp as a call may return the regexp it was handed, so that edge is added. - Typed arrays and ArrayBuffer get the TypedArray summary with the numeric element field, a ShareBuffer edge from their source, and B4Resizable when an ArrayBuffer is built with a second argument. - A summary that reads fixed argument positions cannot be trusted when the call spreads: new_builtin_ns rematches the site with every fixed and spread value in each position (at most three). The branches for those names in construct_named are now dead and removed, and ns_statics covers the new variants (AggregateError inherits Error's statics; Date/RegExp/TypedArray/ArrayBuffer are deliberately not described, the same precision as before, when they were not builtin tokens at all). Most of #12858 had already landed elsewhere; this is its missing builtins half. cargo test -p perry-wpfacts --lib: 679 passed, 0 failed, 6 ignored. The facts dump over a program constructing Date/Uint8Array/ArrayBuffer/RegExp/ AggregateError shows what:Date, what:RegExp, what:TypedArray and what:Error plus the [errors] field, and the binary matches Node byte for byte. --- crates/perry-wpfacts/src/builtins.rs | 102 +++++++++---- crates/perry-wpfacts/src/builtins/members.rs | 9 +- crates/perry-wpfacts/src/model.rs | 5 + crates/perry-wpfacts/src/tests.rs | 1 + .../src/tests/constructor_values.rs | 143 ++++++++++++++++++ 5 files changed, 232 insertions(+), 28 deletions(-) create mode 100644 crates/perry-wpfacts/src/tests/constructor_values.rs diff --git a/crates/perry-wpfacts/src/builtins.rs b/crates/perry-wpfacts/src/builtins.rs index d718ca3772e..e831540fb0c 100644 --- a/crates/perry-wpfacts/src/builtins.rs +++ b/crates/perry-wpfacts/src/builtins.rs @@ -33,7 +33,14 @@ fn ns_by_name(name: &str) -> Option { "globalThis" | "global" | "window" | "self" => BuiltinNs::GlobalThis, "process" => BuiltinNs::Process, "Error" | "TypeError" | "RangeError" | "SyntaxError" | "ReferenceError" | "EvalError" - | "URIError" | "AggregateError" => BuiltinNs::Error, + | "URIError" => BuiltinNs::Error, + "AggregateError" => BuiltinNs::AggregateError, + "Date" => BuiltinNs::Date, + "RegExp" => BuiltinNs::RegExp, + "Uint8Array" | "Int8Array" | "Uint16Array" | "Int16Array" | "Uint32Array" + | "Int32Array" | "Float16Array" | "Float32Array" | "Float64Array" | "BigInt64Array" + | "BigUint64Array" | "Uint8ClampedArray" | "DataView" => BuiltinNs::TypedArray, + "ArrayBuffer" | "SharedArrayBuffer" => BuiltinNs::ArrayBuffer, "Symbol" => BuiltinNs::Symbol, "Number" | "BigInt" => BuiltinNs::Number, "String" => BuiltinNs::String, @@ -1812,11 +1819,17 @@ pub fn call_builtin_ns(m: &mut Model, ns: BuiltinNs, call: CallId) { unknown_method(m, call, "E14 dynamic code"); } BuiltinNs::ObjectProtoMethod => object_proto_method_call(m, call), - BuiltinNs::Error => { - implicit_args(m, call, "Error message", Protocol::Any); - let e = error_tok(m, call); - ret_tok(m, call, e); + BuiltinNs::Error | BuiltinNs::AggregateError | BuiltinNs::RegExp => { + new_builtin_ns(m, ns, call); + if ns == BuiltinNs::RegExp { + // `RegExp(re)` can return the existing regexp object. + if let Some(src) = arg(m, call, 0) { + let ret = m.calls[call as usize].ret; + m.edge(src, ret); + } + } } + BuiltinNs::Date => ret_prim(m, call, prim::STR), BuiltinNs::String | BuiltinNs::Number | BuiltinNs::Boolean | BuiltinNs::Symbol => { implicit_args(m, call, "ToPrimitive", Protocol::ToPrimitive); ret_prim(m, call, prim::ALL); @@ -1859,6 +1872,23 @@ fn error_tok(m: &mut Model, call: CallId) -> Tok { pub fn new_builtin_ns(m: &mut Model, ns: BuiltinNs, call: CallId) { m.diagnostic_builtin(call, |_| format!("new {ns:?}")); + // A spread shifts argument positions, so a summary that reads fixed + // positions cannot be trusted. Every fixed and spread value may land in + // each position such a summary consumes (at most three), so rematch the + // same site with that widened argument list instead of guessing. + let c = m.calls[call as usize].clone(); + if !c.spread.is_empty() && ns != BuiltinNs::Array { + let vals = all_arg_values(m, call); + let c2 = m.calls.len() as CallId; + m.calls.push(CallSite { + this: ThisArg::Undef, + args: vec![vals; c.args.len().max(3)], + spread: vec![], + ret: c.ret, + site: c.site, + }); + return new_builtin_ns(m, ns, c2); + } match ns { BuiltinNs::Map | BuiltinNs::Set => { let c = site_tmpl(m, call, "Collection"); @@ -1902,10 +1932,24 @@ pub fn new_builtin_ns(m: &mut Model, ns: BuiltinNs, call: CallId) { } ret_tok(m, call, p); } - BuiltinNs::Error => { + BuiltinNs::Error | BuiltinNs::AggregateError => { implicit_args(m, call, "Error message", Protocol::Any); let e = error_tok(m, call); - if let Some(o) = arg(m, call, 1) { + let options = if ns == BuiltinNs::AggregateError { + if let Some(src) = arg(m, call, 0) { + let vals = iterate(m, call, src); + let arr = site_tmpl(m, call, "Array"); + let elems = m.field_any(arr); + m.edge(vals, elems); + let errors = m.atom("errors"); + let f = m.field(e, errors); + m.add_tok(f, arr); + } + 2 + } else { + 1 + }; + if let Some(o) = arg(m, call, options) { let ca = m.atom("cause"); let f = m.field(e, ca); m.cons( @@ -1918,6 +1962,28 @@ pub fn new_builtin_ns(m: &mut Model, ns: BuiltinNs, call: CallId) { } ret_tok(m, call, e); } + BuiltinNs::Date | BuiltinNs::RegExp => { + implicit_args(m, call, "builtin constructor argument", Protocol::Any); + let what = if ns == BuiltinNs::Date { "Date" } else { "RegExp" }; + let o = site_tmpl(m, call, what); + ret_tok(m, call, o); + } + BuiltinNs::TypedArray | BuiltinNs::ArrayBuffer => { + implicit_args(m, call, "typed array source", Protocol::Any); + let o = site_tmpl(m, call, "TypedArray"); + let fa = m.field_any(o); + m.add_prim(fa, prim::NUM | prim::BIGINT); + let c = m.calls[call as usize].clone(); + if let Some(&src) = c.args.first() { + m.cons(src, Cons::ShareBuffer { ta: o }); + } + if ns == BuiltinNs::ArrayBuffer && c.args.len() > 1 { + // B4: `new ArrayBuffer(n, { maxByteLength })` is resizable. + let r = m.reason("B4 resizable ArrayBuffer"); + m.mark_hard(o, Hard::B4Resizable, r); + } + ret_tok(m, call, o); + } BuiltinNs::Function => { set_dyn_eval(m, "E14 new Function(...)"); unknown_method(m, call, "E14 dynamic code"); @@ -1944,30 +2010,12 @@ pub fn construct_named(m: &mut Model, name: &str, call: CallId) { return new_builtin_ns(m, ns, call); } match name { - "Date" | "RegExp" | "URL" | "URLSearchParams" | "TextEncoder" | "TextDecoder" - | "AbortController" | "Headers" => { + "URL" | "URLSearchParams" | "TextEncoder" | "TextDecoder" | "AbortController" + | "Headers" => { implicit_args(m, call, "builtin constructor argument", Protocol::Any); let o = site_tmpl(m, call, "builtin"); ret_tok(m, call, o); } - "Uint8Array" | "Int8Array" | "Uint16Array" | "Int16Array" | "Uint32Array" - | "Int32Array" | "Float32Array" | "Float64Array" | "BigInt64Array" | "BigUint64Array" - | "Uint8ClampedArray" | "ArrayBuffer" | "DataView" => { - implicit_args(m, call, "typed array source", Protocol::Any); - let o = site_tmpl(m, call, "TypedArray"); - let fa = m.field_any(o); - m.add_prim(fa, prim::NUM | prim::BIGINT); - let c = m.calls[call as usize].clone(); - if let Some(&src) = c.args.first() { - m.cons(src, Cons::ShareBuffer { ta: o }); - } - if name == "ArrayBuffer" && c.args.len() > 1 { - // B4: `new ArrayBuffer(n, { maxByteLength })` is resizable. - let r = m.reason("B4 resizable ArrayBuffer"); - m.mark_hard(o, Hard::B4Resizable, r); - } - ret_tok(m, call, o); - } "Proxy" => { let c = m.calls[call as usize].clone(); let r = m.reason("E9 new Proxy (by name)"); diff --git a/crates/perry-wpfacts/src/builtins/members.rs b/crates/perry-wpfacts/src/builtins/members.rs index 64df8a78cc6..27c1ae1a361 100644 --- a/crates/perry-wpfacts/src/builtins/members.rs +++ b/crates/perry-wpfacts/src/builtins/members.rs @@ -497,11 +497,18 @@ fn ns_statics(ns: BuiltinNs) -> Option<&'static [(&'static str, Mem)]> { // Error and its native subclasses; V8's `captureStackTrace` and // `stackTraceLimit` (node), `prepareStackTrace` (absent until set: // a program write is a store, `intrinsic_written`). - BuiltinNs::Error => &[ + // `AggregateError` is an `Error` subclass: the same statics apply. + BuiltinNs::Error | BuiltinNs::AggregateError => &[ ("captureStackTrace", FN), ("isError", FN), ("stackTraceLimit", N), ], + // The model does not describe these namespaces' own members. They are + // constructor tokens only, for the `new`/call summaries; naming them + // here would claim a precision the model does not have. + BuiltinNs::Date | BuiltinNs::RegExp | BuiltinNs::TypedArray | BuiltinNs::ArrayBuffer => { + return None + } BuiltinNs::Symbol => &[ ("asyncDispose", SY), ("asyncIterator", SY), diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index 5d4790ab8e6..6466adb2081 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -87,6 +87,11 @@ pub enum BuiltinNs { GlobalThis, Process, Error, + AggregateError, + Date, + RegExp, + TypedArray, + ArrayBuffer, Symbol, Number, String, diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index add4ed92857..a48a6ff016b 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -1711,3 +1711,4 @@ fn a_protocol_touch_of_an_argument_marker_reads_nothing() { let tj = m.marker_load(r, Some(a)); assert!(m.marker_use.get(&tj).is_some_and(|&u| u & MARK_ESCAPE != 0)); } +mod constructor_values; diff --git a/crates/perry-wpfacts/src/tests/constructor_values.rs b/crates/perry-wpfacts/src/tests/constructor_values.rs new file mode 100644 index 00000000000..1f02aa7020d --- /dev/null +++ b/crates/perry-wpfacts/src/tests/constructor_values.rs @@ -0,0 +1,143 @@ +use super::{facts, field, tmpl}; +use crate::{compute, gen::Gen, model::*, FactsConfig}; +use perry_hir::{Expr, Stmt}; + +fn capture_facts(unknown_in_constructor_slot: bool, snapshot: bool) -> crate::ProgramFacts { + let parsed = perry_parser::parse_typescript( + "class Payload { value = 7; } class Known { constructor(x: any) { this.item = x; } } class Host {}", + "t.ts", + ).unwrap(); + let mut hir = perry_hir::lower_module(&parsed, "t", "t.ts").unwrap(); + let unknown = Expr::ExternFuncRef { + name: "externalCtor".into(), + param_types: vec![], + return_type: perry_hir::types::Type::Any, + }; + hir.init.push(Stmt::Expr(Expr::RegisterClassCaptures { + class_name: "Host".into(), + captures: vec![Expr::ClassRef("Known".into()), unknown.clone()], + })); + hir.init.push(Stmt::Expr(Expr::ClassEnvSet { + class_name: "Host".into(), + index: if unknown_in_constructor_slot { 0 } else { 1 }, + value: Box::new(unknown), + guarded: true, + publish: false, + })); + let callee = if snapshot { + Expr::ClassCaptureValue { + class_name: "Host".into(), + index: 0, + fallback: None, + prefer_fallback: false, + } + } else { + Expr::ClassEnvGet { + class_name: "Host".into(), + index: 0, + guarded: true, + } + }; + hir.init.push(Stmt::Expr(Expr::NewDynamic { + callee: Box::new(callee), + args: vec![Expr::New { + class_name: "Payload".into(), + args: vec![], + type_args: vec![], + byte_offset: 0, + cap_args_appended: 0, + byte_end: 0, + }], + byte_offset: 0, + byte_end: 0, + })); + compute(&[("t.ts".into(), &hir)], &FactsConfig::default()) +} + +#[test] +fn capture_slots_keep_constructor_identity_separate_from_unknown_captures() { + for snapshot in [false, true] { + let pf = capture_facts(false, snapshot); + assert!(tmpl(&pf, "Payload").prefix_stable, "{}", pf.dump_text()); + assert!( + field(tmpl(&pf, "Known"), "item") + .kind + .desc() + .contains("obj"), + "{}", + pf.dump_text() + ); + } +} + +#[test] +fn unknown_constructor_alternative_in_the_same_capture_slot_still_escapes_arguments() { + for snapshot in [false, true] { + let pf = capture_facts(true, snapshot); + assert!(!tmpl(&pf, "Payload").prefix_stable, "{}", pf.dump_text()); + } +} + +#[test] +fn property_and_parameter_constructor_values_preserve_parameters_and_return_substitution() { + let pf = facts( + r#" + class Payload { value = 7; } + class Replacement { payload: any; constructor(p: any) { this.payload = p; } } + function Ctor(this: any, p: any) { this.unused = 1; return new Replacement(p); } + const holder = { klass: Ctor }; + function make(C: any, p: any) { return new C(p); } + const result = make(holder.klass, new Payload()); + console.log(result.payload.value); + "#, + ); + assert!(tmpl(&pf, "Payload").prefix_stable, "{}", pf.dump_text()); + let sites: Vec<_> = pf.sites.iter().filter(|s| s.key == "value").collect(); + assert!(!sites.is_empty()); + assert!(sites.iter().all(|s| !s.top), "{}", pf.dump_text()); +} + +#[test] +fn builtin_promise_spread_executor_is_connected() { + let pf = facts( + r#" + class Payload { value = 7; } + const P = globalThis.Promise; + const args: any[] = [(resolve: any) => resolve(new Payload())]; + new P(...args).then((p: any) => console.log(p.value)); + "#, + ); + let sites: Vec<_> = pf.sites.iter().filter(|s| s.key == "value").collect(); + assert!(!sites.is_empty()); + assert!( + sites + .iter() + .all(|s| s.receivers.iter().any(|r| r.ends_with(":Payload"))), + "{}", + pf.dump_text() + ); +} + +#[test] +fn class_expression_constructor_capture_keeps_other_capture_slots_separate() { + let pf = facts( + r#" + class Payload { value = 7; } + class Box { item: any; constructor(p: any) { this.item = p; } } + function factory(Ctor: any, unrelated: any) { + return class { + create(p: any) { return new Ctor(p); } + other() { return unrelated; } + }; + } + const Maker = factory(Box, externalFactory()); + const maker = new Maker(); + const out = maker.create(new Payload()); + console.log(out.item.value); + "#, + ); + assert!(tmpl(&pf, "Payload").prefix_stable, "{}", pf.dump_text()); + let sites: Vec<_> = pf.sites.iter().filter(|s| s.key == "value").collect(); + assert!(!sites.is_empty()); + assert!(sites.iter().all(|s| !s.top), "{}", pf.dump_text()); +} From 25f2232fed6671e6b2164c02f4b31e2946c09e7b Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:34:58 +0100 Subject: [PATCH 072/144] changelog: record the builtin constructor summaries --- changelog.d/13623-builtin-ctor-summaries.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13623-builtin-ctor-summaries.md diff --git a/changelog.d/13623-builtin-ctor-summaries.md b/changelog.d/13623-builtin-ctor-summaries.md new file mode 100644 index 00000000000..de3fbe09701 --- /dev/null +++ b/changelog.d/13623-builtin-ctor-summaries.md @@ -0,0 +1 @@ +**The facts model now summarizes the builtin constructor families it was missing: `AggregateError`, `Date`, `RegExp`, the typed arrays and `ArrayBuffer`/`SharedArrayBuffer`.** `ns_by_name` maps them — `AggregateError` splitting out of `Error`, the typed-array family plus `DataView` to `TypedArray` — which is what makes both spellings resolve, because `construct_named` consults that table before its own. `AggregateError` shares `Error`'s summary and models its `errors` field from the iterated first argument; `Date()` is `ToString`-shaped while `new Date()` is an object summary; `RegExp(re)` may hand back the regexp it was given, so that edge is added; typed arrays and `ArrayBuffer` get the `TypedArray` summary with its numeric-element field, a `ShareBuffer` edge from the source, and the `B4Resizable` hard fact when an `ArrayBuffer` is built with a second argument. A call that spreads cannot be read by fixed argument positions, so `new_builtin_ns` rematches the site with every fixed and spread value in each position it consumes. The branches for these names in `construct_named` were dead once the table mapped them and are removed; `ns_statics` covers the new variants, with `Date`/`RegExp`/`TypedArray`/`ArrayBuffer` deliberately left undescribed rather than claiming a precision the model lacks. Each summary is visible in the facts dump (`what: Date`, `RegExp`, `TypedArray`, `Error`, plus AggregateError's `errors` field) and the compiled program matches Node. From d413e02f12123cfa1692572a61a6fd95bc2b5d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 12:00:17 +0000 Subject: [PATCH 073/144] wpfacts: a contract publishes the protocol that touches a parameter A callee that runs a builtin protocol (ToPrimitive, iteration, instanceof) on its parameter published one touch bit, and the caller unit ran Protocol::Any on its argument: every function under any key called with the argument as receiver, an escape. TypeScript diagnosticToString(d) converts d on its array arm only; each Diagnostics record a unit passed escaped and the diag() layout broke in every unit. The touch now carries its protocol (MARK_TOUCH_* bits); the reader runs that protocol with its own keys. JSON and Any keep MARK_TOUCH. --- crates/perry-wpfacts/src/gen.rs | 5 +- crates/perry-wpfacts/src/gen/import_values.rs | 34 +++----- crates/perry-wpfacts/src/model.rs | 41 ++++++++++ crates/perry-wpfacts/src/solve.rs | 18 ++--- crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../src/tests/unit_mode/protocol_touch.rs | 81 +++++++++++++++++++ 6 files changed, 147 insertions(+), 34 deletions(-) create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/protocol_touch.rs diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index 64da7c243ba..2c3cbadda41 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -208,7 +208,8 @@ pub struct Gen<'a> { /// DECISIONS 45: what other units' functions let escape, or touch, of /// what their arguments reach: one node each for the unit. pub reach_escape: Option, - pub reach_touch: Option, + /// Per set of protocols (`MARK_TOUCH*` bits) the touch runs. + pub reach_touch: Vec<(u8, NodeId)>, /// The contracts behind the stand-ins: a stand-in template's class or /// layout contract, a stand-in function token's function contract. pub(crate) stand_in_cc: FxHashMap, @@ -443,7 +444,7 @@ impl<'a> Gen<'a> { stand_in_nss: FxHashMap::default(), unnamed_imports: FxHashMap::default(), reach_escape: None, - reach_touch: None, + reach_touch: Vec::new(), stand_in_cc: FxHashMap::default(), own_classes: FxHashMap::default(), closed_ctors: Vec::new(), diff --git a/crates/perry-wpfacts/src/gen/import_values.rs b/crates/perry-wpfacts/src/gen/import_values.rs index 1406e19f18a..9955ec50fcd 100644 --- a/crates/perry-wpfacts/src/gen/import_values.rs +++ b/crates/perry-wpfacts/src/gen/import_values.rs @@ -602,7 +602,7 @@ impl<'a> Gen<'a> { c: &'a crate::units::FnContract, holder: Option<(TmplId, bool)>, ) -> NodeId { - use crate::model::{MARK_ESCAPE, MARK_TOUCH}; + use crate::model::{MARK_ESCAPE, MARK_TOUCHES}; let c = self.owner_fn(c); if let Some(&(n, _)) = self.stand_in_fns.get(&(c.name.clone(), holder)) { return n; @@ -790,14 +790,9 @@ impl<'a> Gen<'a> { self.m.sink_node(p, r); continue; } - if u & MARK_TOUCH != 0 { - self.m.cons( - p, - Cons::Implicit { - reason: r, - proto: Protocol::Any, - }, - ); + // The protocols the callee runs on it, each with its own keys. + for proto in Protocol::of_touches(u) { + self.m.cons(p, Cons::Implicit { reason: r, proto }); } // The keys the callee stores into the argument (DECISIONS 84): // what it stores under each (DECISIONS 118), a store this unit @@ -902,11 +897,12 @@ impl<'a> Gen<'a> { } }; self.m.cons(p, Cons::Load { key, dst: q }); - } else if ru & MARK_TOUCH != 0 { - let q = match self.reach_touch { - Some(q) => q, + } else if ru & MARK_TOUCHES != 0 { + let touches = ru & MARK_TOUCHES; + let q = match self.reach_touch.iter().find(|&&(b, _)| b == touches) { + Some(&(_, q)) => q, None => { - let q = self.m.node_named("reach", || "reached(touch)".into()); + let q = self.m.node_named("reach", || format!("reached(touch {touches})")); self.m.nodes[q as usize].sink = true; self.m.cons( q, @@ -915,14 +911,10 @@ impl<'a> Gen<'a> { dst: q, }, ); - self.m.cons( - q, - Cons::Implicit { - reason: r, - proto: Protocol::Any, - }, - ); - self.reach_touch = Some(q); + for proto in Protocol::of_touches(touches) { + self.m.cons(q, Cons::Implicit { reason: r, proto }); + } + self.reach_touch.push((touches, q)); q } }; diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index 6466adb2081..8ec89c878d4 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -538,6 +538,32 @@ impl Protocol { } } + /// The use bit of a touch by this protocol (`MARK_TOUCH*`). + pub fn touch_bit(self) -> u8 { + match self { + Protocol::ToPrimitive => MARK_TOUCH_PRIMITIVE, + Protocol::Iterate => MARK_TOUCH_ITERATE, + Protocol::IterNext => MARK_TOUCH_ITER_NEXT, + Protocol::IterResult => MARK_TOUCH_ITER_RESULT, + Protocol::HasInstance => MARK_TOUCH_HAS_INSTANCE, + Protocol::Json | Protocol::OwnKeys | Protocol::Any => MARK_TOUCH, + } + } + + /// The protocols the touch bits of `uses` name, each once. + pub fn of_touches(uses: u8) -> impl Iterator { + [ + Protocol::Any, + Protocol::ToPrimitive, + Protocol::Iterate, + Protocol::IterNext, + Protocol::IterResult, + Protocol::HasInstance, + ] + .into_iter() + .filter(move |p| uses & p.touch_bit() != 0) + } + /// The protocol run on what the called methods return. pub fn of_result(self) -> Option { match self { @@ -1890,3 +1916,18 @@ pub(crate) fn marker_top_bit(bits: &[u64], t: Tok) -> bool { } pub const MARK_TOUCH: u8 = 2; pub const MARK_ESCAPE: u8 = 4; +/// A touch by a protocol whose keys ECMA-262 fixes (`Protocol::keys`): +/// the reader runs that protocol on its value, not `Protocol::Any` +/// (`MARK_TOUCH`: a touch whose keys are any, `Any` and JSON). +pub const MARK_TOUCH_PRIMITIVE: u8 = 8; +pub const MARK_TOUCH_ITERATE: u8 = 16; +pub const MARK_TOUCH_ITER_NEXT: u8 = 32; +pub const MARK_TOUCH_ITER_RESULT: u8 = 64; +pub const MARK_TOUCH_HAS_INSTANCE: u8 = 128; +/// Every touch bit. +pub const MARK_TOUCHES: u8 = MARK_TOUCH + | MARK_TOUCH_PRIMITIVE + | MARK_TOUCH_ITERATE + | MARK_TOUCH_ITER_NEXT + | MARK_TOUCH_ITER_RESULT + | MARK_TOUCH_HAS_INSTANCE; diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index ffdb7a11d90..ffad674b3d5 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1275,11 +1275,11 @@ impl Model { /// stored keys hold unknown values, what it reaches is escaped or /// touched as published). False: `t` escapes. pub(crate) fn admit_this(&mut self, t: Tok, tmpl: TmplId, this: u32, reason: u32) -> bool { - use crate::model::{MARK_ESCAPE, MARK_READ, MARK_TOUCH}; + use crate::model::{MARK_ESCAPE, MARK_READ, MARK_TOUCHES}; let Some(c) = self.this_contracts.get(this as usize).cloned() else { return false; }; - if c.uses & (MARK_ESCAPE | MARK_TOUCH) != 0 { + if c.uses & (MARK_ESCAPE | MARK_TOUCHES) != 0 { return false; } // A published receiver token stands for its class's instances; a @@ -1347,7 +1347,7 @@ impl Model { } // What the receiver reaches (its fields, transitively), as for an // argument (gen.rs `stand_in_fn_of`). - if c.reach & (MARK_ESCAPE | MARK_TOUCH) != 0 { + if c.reach & (MARK_ESCAPE | MARK_TOUCHES) != 0 { let h = self.node_l("admitted-this"); let q = self.node_l("admitted-this-reach"); self.cons( @@ -1367,13 +1367,9 @@ impl Model { if c.reach & MARK_ESCAPE != 0 { self.sink_node(q, reason); } else { - self.cons( - q, - Cons::Implicit { - reason, - proto: Protocol::Any, - }, - ); + for proto in Protocol::of_touches(c.reach) { + self.cons(q, Cons::Implicit { reason, proto }); + } } self.add_tok(h, t); } @@ -1636,7 +1632,7 @@ impl Model { if !calls && reads.is_empty() { return; } - self.mark_use(t, crate::model::MARK_TOUCH); + self.mark_use(t, proto.touch_bit()); // A marker: the protocol runs on the values beside it; on the // marker it calls what the marker reaches under the protocol's // keys, an escape of that (`method_on`, DECISIONS 111). It has no diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index 32c19ba08fa..2b835b493c8 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -607,3 +607,5 @@ mod method_on_read; #[path = "unit_mode/fn_transfer.rs"] mod fn_transfer; +#[path = "unit_mode/protocol_touch.rs"] +mod protocol_touch; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/protocol_touch.rs b/crates/perry-wpfacts/src/tests/unit_mode/protocol_touch.rs new file mode 100644 index 00000000000..38f20326d33 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/protocol_touch.rs @@ -0,0 +1,81 @@ +//! A builtin protocol a callee runs on its parameter (ToPrimitive, +//! iteration …) is published with its protocol: the caller's unit runs +//! that protocol on what it passes, with that protocol's keys +//! (`Protocol::keys`), not a call of every key it holds. GFX: TypeScript's +//! `diagnosticToString(d)` converts `d` on the array arm only +//! (`isArray(d) ? … d.slice(1) …`); as a call of any key, each +//! `Diagnostics.X` record a unit passed was the receiver of its field +//! values, an escape that broke the `diag()` layout in every unit. + +use super::*; + +const D: &str = r#"function diag(code: number, category: number, key: string, message: string, reportsUnnecessary?: {}, elided?: boolean, deprecated?: {}) { + return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid: elided, reportsDeprecated: deprecated }; +} +export const Diagnostics = { A: diag(1, 1, "A_1", "a"), B: diag(2, 1, "B_2", "b") };"#; + +const U: &str = r#"export function getMsg(m: any) { return m.message; } +export function toStr(d: any): string { return BODY; } +export function action(name: string, d: any) { return { name, description: toStr(d) }; }"#; + +const T: &str = r#"import { Diagnostics } from "./d"; +import { action } from "./u"; +const codes = [Diagnostics.A.code]; +console.log(codes[0], action("x", Diagnostics.B).description, Diagnostics.A.message, Diagnostics.B.key);"#; + +/// `t.ts` reads of the records' fields, per unit and whole: all proven, +/// per unit no stronger than whole, no layout broken. +fn reads_proven(body: &str) { + let u_src = U.replace("BODY", body); + let mods = [("d", D), ("u", u_src.as_str()), ("t", T)]; + let (u, w) = (units_of(&mods), whole_of(&mods)); + let stronger: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(stronger.is_empty(), "per unit stronger than whole: {stronger:?}"); + for pf in [&w, &u] { + let sites: Vec<_> = pf + .sites + .iter() + .filter(|s| { + pf.module_paths[s.module as usize] == "t.ts" + && ["code", "message", "key"].contains(&s.key.as_str()) + }) + .collect(); + assert_eq!(sites.len(), 3, "{}", pf.dump_text()); + for s in sites { + assert!( + matches!(s.verdict, Verdict::T0Mono | Verdict::T0Set), + "{body}: {}@{} {:?} broken {:?}\n{}", + s.key, + s.byte_offset, + s.verdict, + pf.broken, + pf.dump_text() + ); + } + assert!(pf.broken.is_empty(), "{body}: broken {:?}", pf.broken); + } +} + +/// ToPrimitive of what the parameter holds under a computed key. +#[test] +fn a_conversion_of_an_element_runs_to_primitive_on_the_argument() { + reads_proven("Array.isArray(d) ? String(d[0]) : getMsg(d)"); +} + +/// ToPrimitive of the result of a method the parameter's caller runs. +#[test] +fn a_conversion_of_a_method_result_runs_to_primitive_on_the_argument() { + reads_proven("Array.isArray(d) ? String(d.slice(1)) : getMsg(d)"); +} + +/// TypeScript's `diagnosticToString` shape. +#[test] +fn diagnostic_to_string() { + reads_proven("Array.isArray(d) ? getMsg(d[0]) + d.slice(1).join(\",\") : getMsg(d)"); +} + +/// A conversion of the parameter itself. +#[test] +fn a_conversion_of_the_parameter_runs_to_primitive_on_the_argument() { + reads_proven("typeof d === \"string\" ? `${d}` : getMsg(d)"); +} From 0e1fde938572c9898992b517bf5d541b9f4f0044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 12:47:59 +0100 Subject: [PATCH 074/144] release: build every native ext in one cargo invocation The Unix release legs built each perry-ext-* crate in its own cargo invocation together with perry, perry-runtime-static, perry-stdlib-static and perry-wasm-host. Each invocation resolved its own feature union, so almost every iteration recompiled perry, the runtime and stdlib: 16 invocations took 3h15 of the 5h37 macos-15-intel leg (run 41135044965) and the next run hit the 6 h job limit. The shipped compiler and wrapper archives came from whichever iteration wrote them last, so extension archives did not share stdlib's union, which is what #7358 asks for. Build the core set and every governed extension in one invocation with --keep-going: one union for every shipped archive, and an extension that fails still leaves the others built. If that build fails, the core must build on its own and each extension is retried alone, as before. The glibc 2.31 container script gets the same change. The third runtime-libraries invocation (perry + wrappers + wasm-host) now runs only on Windows; on Unix the ext step builds that package set. --- .github/workflows/release-packages.yml | 50 +++++++++++++++++++------- scripts/build_linux_glibc_2_31.sh | 27 ++++++++++---- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 126025d8806..37082e21790 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -898,10 +898,16 @@ jobs: run: | ${{ runner.os == 'Windows' && 'cargo' || 'mbx' }} build --profile prod --target ${{ matrix.target }} -p perry-runtime -p perry-runtime-static ${{ runner.os == 'Windows' && 'cargo' || 'mbx' }} build --profile prod --target ${{ matrix.target }} -p perry-stdlib -p perry-stdlib-static - # The WebAssembly host archive, co-built with the archives it links - # beside: perry links it for a WebAssembly program and never builds - # it while compiling one. - ${{ runner.os == 'Windows' && 'cargo' || 'mbx' }} build --profile prod --target ${{ matrix.target }} -p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host ${{ matrix.wasi && needs.build-wasi.outputs.enabled == 'true' && '--features perry/target-wasi' || '' }} + + - name: Build WebAssembly host archive (Windows) + # The WebAssembly host archive, co-built with the archives it links + # beside: perry links it for a WebAssembly program and never builds + # it while compiling one. On Unix the native ext step below builds this + # same package set (plus every extension) in its one invocation, which + # is the build that ships; doing it here too cost a full second + # compile of perry + runtime + stdlib under a different feature union. + if: runner.os == 'Windows' && matrix.old_glibc_image == '' && matrix.musl_image == '' + run: cargo build --profile prod --target ${{ matrix.target }} -p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host ${{ matrix.wasi && needs.build-wasi.outputs.enabled == 'true' && '--features perry/target-wasi' || '' }} - name: Build panic=abort runtime variant (Unix) # Out-of-tree installs can't rebuild the runtime, so ship the @@ -926,24 +932,42 @@ jobs: # needs the prebuilt `libperry_ext_*.a` sitting next to # libperry_runtime.a so `perry compile` can link them without a # workspace checkout (see optimized_libs.rs::resolve_prebuilt_ext_libs). - # #7358: keep the compiler and both shipped wrapper archives in each - # Cargo invocation. Building an ext staticlib alone gives its bundled - # runtime/shared dependencies a different feature union from stdlib; - # the link deduper then cannot remove the whole second copy, and the - # HTTP accept loop reads a different async reactor from the one the JS - # event loop drives. + # #7358: build the compiler, both shipped wrapper archives and every + # extension in ONE Cargo invocation, so they all share one feature + # union. Building an ext staticlib with a different union from stdlib + # leaves its bundled runtime/shared dependencies a second copy the + # link deduper cannot remove, and the HTTP accept loop reads a + # different async reactor from the one the JS event loop drives. + # One invocation per extension (the former loop) gave every extension + # its own union, so each iteration recompiled perry, the runtime and + # stdlib (3+ h of the Intel leg) and the shipped archives came from + # whichever iteration wrote them last. # #5716: the governance inventory is the explicit shipping set; adding # a crate requires a recorded retain/migration decision. Best-effort # per crate: a wrapper that can't build on this host must not fail the - # whole release — packaging below ships whatever was produced. + # whole release — packaging below ships whatever was produced. If the + # combined build fails, the core set must still build on its own, and + # each extension is then retried alone as before. if: runner.os != 'Windows' && matrix.old_glibc_image == '' && matrix.musl_image == '' run: | set -euo pipefail governed_ext_packages=$(./scripts/release_ext_packages.sh) + ext_args="" + while IFS= read -r name; do + ext_args="$ext_args -p $name" + done <<< "$governed_ext_packages" + core_args="-p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host ${{ matrix.wasi && needs.build-wasi.outputs.enabled == 'true' && '--features perry/target-wasi' || '' }}" + # shellcheck disable=SC2086 # word splitting is the point + if mbx build --keep-going --profile prod --target ${{ matrix.target }} $core_args $ext_args; then + exit 0 + fi + echo "::warning::combined ext build failed; building the core alone, then each extension on its own" + # shellcheck disable=SC2086 + mbx build --profile prod --target ${{ matrix.target }} $core_args while IFS= read -r name; do echo "::group::build $name" - mbx build --profile prod --target ${{ matrix.target }} \ - -p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host -p "$name" ${{ matrix.wasi && needs.build-wasi.outputs.enabled == 'true' && '--features perry/target-wasi' || '' }} \ + # shellcheck disable=SC2086 + mbx build --profile prod --target ${{ matrix.target }} $core_args -p "$name" \ || echo " (skipped $name — failed to build on this host)" echo "::endgroup::" done <<< "$governed_ext_packages" diff --git a/scripts/build_linux_glibc_2_31.sh b/scripts/build_linux_glibc_2_31.sh index ae55895d891..4ae8e65fb0a 100755 --- a/scripts/build_linux_glibc_2_31.sh +++ b/scripts/build_linux_glibc_2_31.sh @@ -80,16 +80,29 @@ cp "$abort_target_dir/$target/prod/libperry_runtime.a" \ # Match the ordinary release leg's best-effort extension-library build. #5716: # enumerate the explicit governance inventory rather than every matching -# directory. Keep perry and both wrappers in each invocation so Cargo resolves -# one feature union and the final linker can deduplicate shared dependencies. +# directory. Build perry, both wrappers and every extension in ONE invocation +# so Cargo resolves one feature union and the final linker can deduplicate +# shared dependencies (#7358). One invocation per extension gave each its own +# union and recompiled perry, the runtime and stdlib for every extension. If +# the combined build fails, the core must still build alone, and each +# extension is then retried on its own. governed_ext_packages=$(./scripts/release_ext_packages.sh) +core_packages=(-p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host "${wasi_features[@]}") +ext_packages=() while IFS= read -r package; do - echo "::group::build $package" - "${cargo_build[@]}" --profile prod --target "$target" \ - -p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host -p "$package" "${wasi_features[@]}" \ - || echo " (skipped $package -- failed to build on the glibc 2.31 sysroot)" - echo "::endgroup::" + ext_packages+=(-p "$package") done <<< "$governed_ext_packages" +if ! "${cargo_build[@]}" --keep-going --profile prod --target "$target" \ + "${core_packages[@]}" "${ext_packages[@]}"; then + echo "::warning::combined ext build failed on the glibc 2.31 sysroot; building the core alone, then each extension on its own" + "${cargo_build[@]}" --profile prod --target "$target" "${core_packages[@]}" + while IFS= read -r package; do + echo "::group::build $package" + "${cargo_build[@]}" --profile prod --target "$target" "${core_packages[@]}" -p "$package" \ + || echo " (skipped $package -- failed to build on the glibc 2.31 sysroot)" + echo "::endgroup::" + done <<< "$governed_ext_packages" +fi # GC effects sidecars (.gc-effects) over the final runtime bytes: the # ext loop above re-links the runtime, so this is the last write. The table From 4901eb138f489931f5274b53bd5b6ceea6f0c4a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 14:32:14 +0000 Subject: [PATCH 075/144] tests: set namespace_self_import in CompileOptions literals --- crates/perry-codegen/src/codegen/emission_order_tests.rs | 1 + crates/perry-codegen/src/codegen/entry/tests.rs | 1 + crates/perry-codegen/src/codegen/number_exactness_tests.rs | 1 + crates/perry-codegen/src/collectors/proven_this_routing_tests.rs | 1 + crates/perry-codegen/src/expr/array_push_guard_tests.rs | 1 + crates/perry-codegen/src/expr/class_field_barrier_tests.rs | 1 + .../src/expr/class_method_arguments_object_tests.rs | 1 + crates/perry-codegen/src/expr/property_get/tests.rs | 1 + crates/perry-codegen/src/lower_call/alloc_hot_tests.rs | 1 + crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs | 1 + crates/perry-codegen/src/native_root_coverage/mod.rs | 1 + crates/perry-codegen/src/stmt/boxed_slot_no_root_tests.rs | 1 + crates/perry-codegen/src/stmt/class_field_loop_tests.rs | 1 + crates/perry-codegen/src/stmt/element_shape_loop_tests.rs | 1 + crates/perry-codegen/src/stmt/prealloc_module_global_tests.rs | 1 + crates/perry-codegen/src/temp_root_coverage/mod.rs | 1 + crates/perry-codegen/src/type_analysis/numeric/tests.rs | 1 + crates/perry-codegen/tests/app_window_config_options.rs | 1 + crates/perry-codegen/tests/argless_builtin_extra_args.rs | 1 + crates/perry-codegen/tests/class_field_store_pointer_test.rs | 1 + crates/perry-codegen/tests/class_keys_gc_root.rs | 1 + crates/perry-codegen/tests/constructor_recursion.rs | 1 + crates/perry-codegen/tests/crypto_hash_chain_lowering.rs | 1 + crates/perry-codegen/tests/destructure_call_location.rs | 1 + crates/perry-codegen/tests/i64_spec_ternary_recursion.rs | 1 + crates/perry-codegen/tests/ios_platform_api_lowering.rs | 1 + crates/perry-codegen/tests/large_object_barriers.rs | 1 + crates/perry-codegen/tests/loop_safepoint_purity.rs | 1 + crates/perry-codegen/tests/macos_bundle_chdir_gate.rs | 1 + crates/perry-codegen/tests/native_proof_buffer_views.rs | 1 + crates/perry-codegen/tests/native_proof_regressions.rs | 1 + crates/perry-codegen/tests/node_test_mock_property_presence.rs | 1 + crates/perry-codegen/tests/perry_builtin_name_collision.rs | 1 + crates/perry-codegen/tests/release_boxes_lowering.rs | 1 + crates/perry-codegen/tests/scalar_replaced_slot_roots.rs | 1 + crates/perry-codegen/tests/shadow_slot_hygiene.rs | 1 + crates/perry-codegen/tests/static_symbol_hygiene.rs | 1 + crates/perry-codegen/tests/temp_root_operand_temporaries.rs | 1 + crates/perry-codegen/tests/typed_feedback.rs | 1 + crates/perry-codegen/tests/typed_shape_descriptor.rs | 1 + crates/perry-codegen/tests/typed_shape_descriptors.rs | 1 + .../src/commands/compile/object_cache/object_cache_tests.rs | 1 + 42 files changed, 42 insertions(+) diff --git a/crates/perry-codegen/src/codegen/emission_order_tests.rs b/crates/perry-codegen/src/codegen/emission_order_tests.rs index 9a013be2a46..1cdc89e17b5 100644 --- a/crates/perry-codegen/src/codegen/emission_order_tests.rs +++ b/crates/perry-codegen/src/codegen/emission_order_tests.rs @@ -68,6 +68,7 @@ const N: u32 = 16; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/codegen/entry/tests.rs b/crates/perry-codegen/src/codegen/entry/tests.rs index 21758c1ee17..32ac1f2e43a 100644 --- a/crates/perry-codegen/src/codegen/entry/tests.rs +++ b/crates/perry-codegen/src/codegen/entry/tests.rs @@ -3,6 +3,7 @@ use perry_hir::{types::Type, Expr, Module, ModuleInitKind, Stmt}; fn entry_opts(output_type: &str) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/codegen/number_exactness_tests.rs b/crates/perry-codegen/src/codegen/number_exactness_tests.rs index 476fb5589f6..5360b0d2e1c 100644 --- a/crates/perry-codegen/src/codegen/number_exactness_tests.rs +++ b/crates/perry-codegen/src/codegen/number_exactness_tests.rs @@ -27,6 +27,7 @@ use perry_hir::{BinaryOp, CompareOp, Expr, Function, Module, ModuleInitKind, Par fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs b/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs index 3a753635702..a50aa25106f 100644 --- a/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs +++ b/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs @@ -38,6 +38,7 @@ use perry_hir::{BinaryOp, Class, ClassField, Expr, Function, Module, ModuleInitK fn ir_opts(is_entry: bool) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/expr/array_push_guard_tests.rs b/crates/perry-codegen/src/expr/array_push_guard_tests.rs index 592875f9ed8..cbdcf36b74b 100644 --- a/crates/perry-codegen/src/expr/array_push_guard_tests.rs +++ b/crates/perry-codegen/src/expr/array_push_guard_tests.rs @@ -40,6 +40,7 @@ const POINTER_LAYOUT_MASK: &str = "63616"; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/expr/class_field_barrier_tests.rs b/crates/perry-codegen/src/expr/class_field_barrier_tests.rs index 0a5575a0e8d..7be90939aa4 100644 --- a/crates/perry-codegen/src/expr/class_field_barrier_tests.rs +++ b/crates/perry-codegen/src/expr/class_field_barrier_tests.rs @@ -62,6 +62,7 @@ pub(super) fn assert_default_barrier_env_not_disabled() { pub(super) fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/expr/class_method_arguments_object_tests.rs b/crates/perry-codegen/src/expr/class_method_arguments_object_tests.rs index 479f0fa0148..6081e7fa5be 100644 --- a/crates/perry-codegen/src/expr/class_method_arguments_object_tests.rs +++ b/crates/perry-codegen/src/expr/class_method_arguments_object_tests.rs @@ -54,6 +54,7 @@ const RECV_ID: u32 = 4; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/expr/property_get/tests.rs b/crates/perry-codegen/src/expr/property_get/tests.rs index 61c3040d9a4..a525f5f74b1 100644 --- a/crates/perry-codegen/src/expr/property_get/tests.rs +++ b/crates/perry-codegen/src/expr/property_get/tests.rs @@ -22,6 +22,7 @@ use front_contract::{front_call_block, verify_front_directory}; fn ir_opts(debug_locations: bool, module_source: Option<&str>) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/lower_call/alloc_hot_tests.rs b/crates/perry-codegen/src/lower_call/alloc_hot_tests.rs index c8465c658de..63bdcbb3dc1 100644 --- a/crates/perry-codegen/src/lower_call/alloc_hot_tests.rs +++ b/crates/perry-codegen/src/lower_call/alloc_hot_tests.rs @@ -45,6 +45,7 @@ const STAGE_FUNC_BASE: u32 = 900; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs b/crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs index 31e5c16e0a1..f9d88ea7f0f 100644 --- a/crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs +++ b/crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs @@ -30,6 +30,7 @@ fn object_header_word() -> String { fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/native_root_coverage/mod.rs b/crates/perry-codegen/src/native_root_coverage/mod.rs index 92dc459794d..02d6b1efaab 100644 --- a/crates/perry-codegen/src/native_root_coverage/mod.rs +++ b/crates/perry-codegen/src/native_root_coverage/mod.rs @@ -119,6 +119,7 @@ pub(crate) const NATIVE_TARGETS: [&str; 2] = pub(crate) fn ir_opts(target: &str, is_entry: bool) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/stmt/boxed_slot_no_root_tests.rs b/crates/perry-codegen/src/stmt/boxed_slot_no_root_tests.rs index 57b8e5c7304..31dfc29821b 100644 --- a/crates/perry-codegen/src/stmt/boxed_slot_no_root_tests.rs +++ b/crates/perry-codegen/src/stmt/boxed_slot_no_root_tests.rs @@ -6,6 +6,7 @@ use perry_hir::{Expr, Function, Module, ModuleInitKind, Stmt}; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/stmt/class_field_loop_tests.rs b/crates/perry-codegen/src/stmt/class_field_loop_tests.rs index 8294a900a8b..ef3cd2d4f25 100644 --- a/crates/perry-codegen/src/stmt/class_field_loop_tests.rs +++ b/crates/perry-codegen/src/stmt/class_field_loop_tests.rs @@ -16,6 +16,7 @@ use perry_hir::{ fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/stmt/element_shape_loop_tests.rs b/crates/perry-codegen/src/stmt/element_shape_loop_tests.rs index 87fe3b76ff2..62e9f44b973 100644 --- a/crates/perry-codegen/src/stmt/element_shape_loop_tests.rs +++ b/crates/perry-codegen/src/stmt/element_shape_loop_tests.rs @@ -23,6 +23,7 @@ use perry_hir::{ fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/stmt/prealloc_module_global_tests.rs b/crates/perry-codegen/src/stmt/prealloc_module_global_tests.rs index aad358855be..4346fd5eb8d 100644 --- a/crates/perry-codegen/src/stmt/prealloc_module_global_tests.rs +++ b/crates/perry-codegen/src/stmt/prealloc_module_global_tests.rs @@ -41,6 +41,7 @@ use perry_hir::{Expr, Module, ModuleInitKind, Param, Stmt}; pub(super) fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/temp_root_coverage/mod.rs b/crates/perry-codegen/src/temp_root_coverage/mod.rs index 13e77487dd8..bac4e0b85d8 100644 --- a/crates/perry-codegen/src/temp_root_coverage/mod.rs +++ b/crates/perry-codegen/src/temp_root_coverage/mod.rs @@ -51,6 +51,7 @@ mod set_receiver; pub(crate) fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/src/type_analysis/numeric/tests.rs b/crates/perry-codegen/src/type_analysis/numeric/tests.rs index f4f02e71a70..32514b31b2b 100644 --- a/crates/perry-codegen/src/type_analysis/numeric/tests.rs +++ b/crates/perry-codegen/src/type_analysis/numeric/tests.rs @@ -16,6 +16,7 @@ mod scalar_own_read_tests; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/app_window_config_options.rs b/crates/perry-codegen/tests/app_window_config_options.rs index 52b58428036..a7391815378 100644 --- a/crates/perry-codegen/tests/app_window_config_options.rs +++ b/crates/perry-codegen/tests/app_window_config_options.rs @@ -14,6 +14,7 @@ use perry_hir::{Expr, Function, Module, ModuleInitKind, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/argless_builtin_extra_args.rs b/crates/perry-codegen/tests/argless_builtin_extra_args.rs index a07debbf2af..938b9153dfe 100644 --- a/crates/perry-codegen/tests/argless_builtin_extra_args.rs +++ b/crates/perry-codegen/tests/argless_builtin_extra_args.rs @@ -10,6 +10,7 @@ use perry_hir::{Expr, Module, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/class_field_store_pointer_test.rs b/crates/perry-codegen/tests/class_field_store_pointer_test.rs index 51163706177..91c6d4ad705 100644 --- a/crates/perry-codegen/tests/class_field_store_pointer_test.rs +++ b/crates/perry-codegen/tests/class_field_store_pointer_test.rs @@ -23,6 +23,7 @@ use perry_hir::{Class, ClassField, Expr, Function, Module, ModuleInitKind, Param fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/class_keys_gc_root.rs b/crates/perry-codegen/tests/class_keys_gc_root.rs index 9fa96818980..e32d18fddc4 100644 --- a/crates/perry-codegen/tests/class_keys_gc_root.rs +++ b/crates/perry-codegen/tests/class_keys_gc_root.rs @@ -28,6 +28,7 @@ use perry_hir::{Class, ClassField, Module}; fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/constructor_recursion.rs b/crates/perry-codegen/tests/constructor_recursion.rs index e4df03bed1b..352282890bc 100644 --- a/crates/perry-codegen/tests/constructor_recursion.rs +++ b/crates/perry-codegen/tests/constructor_recursion.rs @@ -4,6 +4,7 @@ use perry_hir::{Class, Expr, Function, Module, ModuleInitKind, Param, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/crypto_hash_chain_lowering.rs b/crates/perry-codegen/tests/crypto_hash_chain_lowering.rs index 1baf7fc4f8a..86119d64b99 100644 --- a/crates/perry-codegen/tests/crypto_hash_chain_lowering.rs +++ b/crates/perry-codegen/tests/crypto_hash_chain_lowering.rs @@ -19,6 +19,7 @@ use perry_hir::{Expr, Module, ModuleInitKind, Stmt}; fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/destructure_call_location.rs b/crates/perry-codegen/tests/destructure_call_location.rs index c6f58ee78d3..4e73d5a937b 100644 --- a/crates/perry-codegen/tests/destructure_call_location.rs +++ b/crates/perry-codegen/tests/destructure_call_location.rs @@ -20,6 +20,7 @@ use perry_hir::{Expr, Module, ModuleInitKind, Stmt}; fn base_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/i64_spec_ternary_recursion.rs b/crates/perry-codegen/tests/i64_spec_ternary_recursion.rs index dc3b3776186..0e7b3af77be 100644 --- a/crates/perry-codegen/tests/i64_spec_ternary_recursion.rs +++ b/crates/perry-codegen/tests/i64_spec_ternary_recursion.rs @@ -21,6 +21,7 @@ use perry_hir::{BinaryOp, CompareOp, Expr, Function, Module, ModuleInitKind, Par fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/ios_platform_api_lowering.rs b/crates/perry-codegen/tests/ios_platform_api_lowering.rs index c99e805c0fa..3660a8e5e2d 100644 --- a/crates/perry-codegen/tests/ios_platform_api_lowering.rs +++ b/crates/perry-codegen/tests/ios_platform_api_lowering.rs @@ -6,6 +6,7 @@ use perry_hir::{Expr, Function, Module, Stmt}; fn options(target: Option<&str>) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/large_object_barriers.rs b/crates/perry-codegen/tests/large_object_barriers.rs index ceace7b10dc..3df492666d9 100644 --- a/crates/perry-codegen/tests/large_object_barriers.rs +++ b/crates/perry-codegen/tests/large_object_barriers.rs @@ -4,6 +4,7 @@ use perry_hir::{Expr, Function, Module, ModuleInitKind, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/loop_safepoint_purity.rs b/crates/perry-codegen/tests/loop_safepoint_purity.rs index fce840119de..57387c3661a 100644 --- a/crates/perry-codegen/tests/loop_safepoint_purity.rs +++ b/crates/perry-codegen/tests/loop_safepoint_purity.rs @@ -41,6 +41,7 @@ use perry_hir::{BinaryOp, CompareOp, Export, Expr, Module, ModuleInitKind, Stmt, fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs b/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs index 72c327de991..27f543d17c0 100644 --- a/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs +++ b/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs @@ -10,6 +10,7 @@ use perry_hir::{Module, ModuleInitKind}; fn entry_opts(target: Option<&str>) -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/native_proof_buffer_views.rs b/crates/perry-codegen/tests/native_proof_buffer_views.rs index f444a49116e..b58292a7cad 100644 --- a/crates/perry-codegen/tests/native_proof_buffer_views.rs +++ b/crates/perry-codegen/tests/native_proof_buffer_views.rs @@ -34,6 +34,7 @@ mod pointer_lifetime; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/native_proof_regressions.rs b/crates/perry-codegen/tests/native_proof_regressions.rs index 4077d15df25..5bc71d8551e 100644 --- a/crates/perry-codegen/tests/native_proof_regressions.rs +++ b/crates/perry-codegen/tests/native_proof_regressions.rs @@ -42,6 +42,7 @@ use native_proof_support::{ fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/node_test_mock_property_presence.rs b/crates/perry-codegen/tests/node_test_mock_property_presence.rs index a35f1a0e25e..91b07ecd445 100644 --- a/crates/perry-codegen/tests/node_test_mock_property_presence.rs +++ b/crates/perry-codegen/tests/node_test_mock_property_presence.rs @@ -6,6 +6,7 @@ use perry_hir::{Expr, Module, Stmt}; fn ir_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/perry_builtin_name_collision.rs b/crates/perry-codegen/tests/perry_builtin_name_collision.rs index 569e58e8285..63bffddfcdf 100644 --- a/crates/perry-codegen/tests/perry_builtin_name_collision.rs +++ b/crates/perry-codegen/tests/perry_builtin_name_collision.rs @@ -21,6 +21,7 @@ use perry_hir::{Expr, Import, ImportSpecifier, Module, ModuleInitKind, ModuleKin fn base_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/release_boxes_lowering.rs b/crates/perry-codegen/tests/release_boxes_lowering.rs index a10002b07d9..9bb9a4d3803 100644 --- a/crates/perry-codegen/tests/release_boxes_lowering.rs +++ b/crates/perry-codegen/tests/release_boxes_lowering.rs @@ -20,6 +20,7 @@ use perry_hir::{Expr, Module, ModuleInitKind, Stmt}; fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/scalar_replaced_slot_roots.rs b/crates/perry-codegen/tests/scalar_replaced_slot_roots.rs index 71877f96641..2d3102a333a 100644 --- a/crates/perry-codegen/tests/scalar_replaced_slot_roots.rs +++ b/crates/perry-codegen/tests/scalar_replaced_slot_roots.rs @@ -60,6 +60,7 @@ use perry_hir::{Expr, Module, ModuleInitKind, Stmt}; fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/shadow_slot_hygiene.rs b/crates/perry-codegen/tests/shadow_slot_hygiene.rs index bf45984ca70..f10073d27a6 100644 --- a/crates/perry-codegen/tests/shadow_slot_hygiene.rs +++ b/crates/perry-codegen/tests/shadow_slot_hygiene.rs @@ -34,6 +34,7 @@ use perry_hir::{Expr, Function, Module, ModuleInitKind, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/static_symbol_hygiene.rs b/crates/perry-codegen/tests/static_symbol_hygiene.rs index b7a6cccdd81..93b4f5cbc47 100644 --- a/crates/perry-codegen/tests/static_symbol_hygiene.rs +++ b/crates/perry-codegen/tests/static_symbol_hygiene.rs @@ -4,6 +4,7 @@ use perry_hir::{Class, Expr, Function, Module, ModuleInitKind, Stmt}; fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/temp_root_operand_temporaries.rs b/crates/perry-codegen/tests/temp_root_operand_temporaries.rs index 2e4f81badac..68a7f375343 100644 --- a/crates/perry-codegen/tests/temp_root_operand_temporaries.rs +++ b/crates/perry-codegen/tests/temp_root_operand_temporaries.rs @@ -61,6 +61,7 @@ use perry_hir::{Class, Expr, Module, ModuleInitKind, Stmt}; fn entry_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/typed_feedback.rs b/crates/perry-codegen/tests/typed_feedback.rs index 95b0a0da336..91c84016b66 100644 --- a/crates/perry-codegen/tests/typed_feedback.rs +++ b/crates/perry-codegen/tests/typed_feedback.rs @@ -91,6 +91,7 @@ impl Drop for EnvVarGuard { fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/typed_shape_descriptor.rs b/crates/perry-codegen/tests/typed_shape_descriptor.rs index a5a799602da..ab98a1605e5 100644 --- a/crates/perry-codegen/tests/typed_shape_descriptor.rs +++ b/crates/perry-codegen/tests/typed_shape_descriptor.rs @@ -4,6 +4,7 @@ use perry_hir::{Class, ClassField, Expr, Function, Module, ModuleInitKind, Stmt} fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry-codegen/tests/typed_shape_descriptors.rs b/crates/perry-codegen/tests/typed_shape_descriptors.rs index 393fdb0ad39..261863944d2 100644 --- a/crates/perry-codegen/tests/typed_shape_descriptors.rs +++ b/crates/perry-codegen/tests/typed_shape_descriptors.rs @@ -7,6 +7,7 @@ use perry_hir::{ fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), diff --git a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs index a49eff86f64..2818f68991b 100644 --- a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs +++ b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs @@ -11,6 +11,7 @@ use tempfile::tempdir; /// to vary one field mutate the returned value before hashing. fn empty_opts() -> CompileOptions { CompileOptions { + namespace_self_import: false, static_shape_ids: Vec::new(), program_layout_ordinals: Default::default(), program_class_shape_ids: Default::default(), From 533945757d638e17bfa7ba350e0e1c45abb315f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 14:55:56 +0000 Subject: [PATCH 076/144] wpfacts: an unknown accessor defines the one key it is installed under MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An accessor whose getter or setter is an unknown function value stored ⊤ into its holder under any key. On a holder from unmodeled code that is a computed store into ⊤, which counts every key of every builtin prototype written, so every key a program object gains late read the opaque member. Data descriptors fed the loop: their get/set are read through the chain and are that member once anything is written. The store now goes under the accessor key (any key only when the key is computed); the holder still escapes to the code that runs. --- crates/perry-wpfacts/src/solve/apply.rs | 15 +++- crates/perry-wpfacts/src/tests.rs | 1 + .../src/tests/unknown_accessor_key.rs | 79 +++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 crates/perry-wpfacts/src/tests/unknown_accessor_key.rs diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index ba84f47c720..63016a1a1f9 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -613,16 +613,25 @@ impl Model { self.cons(holder, Cons::AccessorInstall { key, f: t, setter }); } TokKind::Top | TokKind::BuiltinFn | TokKind::Bound(_) => { - // An unknown accessor: reads of any key on the - // holder return ⊤ and stores escape. + // An unknown accessor: the key it is installed + // under (any key, when computed) reads ⊤ on the + // holder, and the holder escapes to the code that + // runs. It defines that one key: on a holder from + // unmodeled code, a builtin prototype may hold it + // under that key only. let r = self.reason("accessor of unknown function value"); let fa = self.node_l("acc-unknown"); self.add_top(fa, |_| "accessor of unknown function value".into()); let site = self.anon_post_site("unknown accessor"); + let k = if self.atoms[key as usize] == crate::STAR_KEY { + Key::Any + } else { + Key::Named(key) + }; self.cons( holder, Cons::Store { - key: Key::Any, + key: k, src: fa, site, }, diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index a48a6ff016b..3c0fd55d948 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -36,6 +36,7 @@ mod ledger; mod named_accessors; mod native_fs; mod native_proto; +mod unknown_accessor_key; mod parent_value; mod prim_result; mod proof_receivers; diff --git a/crates/perry-wpfacts/src/tests/unknown_accessor_key.rs b/crates/perry-wpfacts/src/tests/unknown_accessor_key.rs new file mode 100644 index 00000000000..c8093488ce1 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unknown_accessor_key.rs @@ -0,0 +1,79 @@ +//! An accessor whose getter or setter is an unknown function value defines +//! the one key it is installed under. On a value from unmodeled code (⊤) +//! that may write that key onto `Object.prototype`, and no other: a key +//! some program object only gains late (absent at birth) still reads its +//! own stores or `undefined`, not the opaque member of every key. + +use super::*; + +/// `l.cache` is not a birth key of `Holder`: absent, the read consults +/// `Object.prototype`. `h` is from unmodeled code. +fn kind_verdict(define: &str, read: &str) -> Verdict { + let src = format!( + r#" + declare const c: boolean; + declare const name: string; + const h: any = (globalThis as any).hostFn(); + const g: any = (globalThis as any).hostGet; + {define} + class Node {{ kind: number; constructor(k: number) {{ this.kind = k; }} }} + class Holder {{ a = 1; }} + const l: any = new Holder(); + if (c) {{ l.cache = new Node(1); l.k = new Node(2); }} + const n = l.{read}; + if (n) console.log(n.kind); + "# + ); + site(&facts(&src), "kind").verdict.clone() +} + +fn proven(v: &Verdict) -> bool { + !matches!(v, Verdict::No(_)) +} + +#[test] +fn no_top_store_reads_the_late_key() { + // Control: without the accessor the late key's read is its stores. + let v = kind_verdict("", "cache"); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn an_unknown_getter_under_a_named_key_writes_that_key_only() { + let v = kind_verdict(r#"Object.defineProperty(h, "k", { get: g });"#, "cache"); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn an_unknown_setter_under_a_named_key_writes_that_key_only() { + let v = kind_verdict(r#"Object.defineProperty(h, "k", { set: g });"#, "cache"); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn an_unknown_getter_writes_its_own_key() { + // `Object.prototype.k` may now be the accessor: `l.k` reads ⊤. + let v = kind_verdict(r#"Object.defineProperty(h, "k", { get: g });"#, "k"); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn an_unknown_getter_under_a_computed_key_writes_every_key() { + let v = kind_verdict("Object.defineProperty(h, name, { get: g });", "cache"); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn an_unknown_getter_on_a_program_object_reads_top_under_its_key() { + // The holder is the program's own object: its accessor key reads ⊤ + // (the getter is unknown code), and the object escapes to it. + let src = r#" + const g: any = (globalThis as any).hostGet; + class Node { kind: number; constructor(k: number) { this.kind = k; } } + const o: any = { k: new Node(1) }; + Object.defineProperty(o, "k", { get: g }); + console.log(o.k.kind); + "#; + let v = site(&facts(src), "kind").verdict.clone(); + assert!(!proven(&v), "{v:?}"); +} From 329df179ae5c34285c5cae7428ab6ce9f42978f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 16:34:30 +0000 Subject: [PATCH 077/144] changelog: unknown accessor key --- changelog.d/0-unknown-accessor-key.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog.d/0-unknown-accessor-key.md diff --git a/changelog.d/0-unknown-accessor-key.md b/changelog.d/0-unknown-accessor-key.md new file mode 100644 index 00000000000..08d934a31b4 --- /dev/null +++ b/changelog.d/0-unknown-accessor-key.md @@ -0,0 +1,5 @@ +wpfacts: an accessor whose getter or setter is an unknown function value now +defines only the key it is installed under. It used to count as a computed +store into ⊤, which marked every key of every builtin prototype written, so +every key a program object gains late read as an opaque member (2,536 such +writes per unit in tssrc). From c695a9b72068e961cdae02c2d604232e003586df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 17:36:33 +0100 Subject: [PATCH 078/144] changelog: number the PR fragment --- .../{0-unknown-accessor-key.md => 13628-unknown-accessor-key.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-unknown-accessor-key.md => 13628-unknown-accessor-key.md} (100%) diff --git a/changelog.d/0-unknown-accessor-key.md b/changelog.d/13628-unknown-accessor-key.md similarity index 100% rename from changelog.d/0-unknown-accessor-key.md rename to changelog.d/13628-unknown-accessor-key.md From 3789b85b1e1c2da7a86139ac062d604a7797ee68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:42:04 +0000 Subject: [PATCH 079/144] =?UTF-8?q?wpfacts:=20a=20number=20key=20reads=20o?= =?UTF-8?q?nly=20number=20keys=20a=20store=20into=20=E2=8A=A4=20wrote=20on?= =?UTF-8?q?=20an=20intrinsic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DECISIONS 217 (c): a number key names no member of an intrinsic prototype. A program prototype then gives its numeric fields (num_fields), but the intrinsic arm (builtins::plain_key, prim_plain_key) read under STAR_KEY: any key a store into ⊤ wrote, so a named store such as checker.ts msg.elidedInCompatabilityPyramid = originalValue made every element read of an array that misses its own elements an opaque member (⊤ + builtin function). TypeScript createNodeFactory runs forEach(nodeFactoryPatchers, fn => fn(factory)) over an empty array: fn held ⊤ and the factory singleton escaped (passed to an opaque builtin function value) in whole mode. The read now uses NUM_KEY; native_written hits for it a write under every key, an element store (NUM_KEY) or a numeric name. --- crates/perry-wpfacts/src/builtins.rs | 22 ++++++---- .../src/builtins/native_protocol.rs | 7 +++ crates/perry-wpfacts/src/tests/proto_value.rs | 43 +++++++++++++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/crates/perry-wpfacts/src/builtins.rs b/crates/perry-wpfacts/src/builtins.rs index e831540fb0c..a0c64a5a22a 100644 --- a/crates/perry-wpfacts/src/builtins.rs +++ b/crates/perry-wpfacts/src/builtins.rs @@ -978,14 +978,20 @@ fn any_written(m: &mut Model, dst: NodeId) { } /// An intrinsic prototype on `origin`'s chain read or called under a key -/// that names none of its members (a number, DECISIONS 217): only what a -/// store into ⊤ wrote there, now or later in the solve. +/// that names none of its members (a number, DECISIONS 217): only a number +/// key a store into ⊤ wrote there, now or later in the solve +/// (`native_written` of `NUM_KEY`), as a program prototype gives its +/// numeric fields (`Model::num_fields`). pub fn plain_key(m: &mut Model, origin: Tok, act: ChainAct) { - let star = m.atom(crate::STAR_KEY); + let num = m.atom(NUM_KEY); match act { - ChainAct::Load(dst) => any_written(m, dst), + ChainAct::Load(dst) => { + if !intrinsic_miss(m, num, NativeMiss::Load(dst)) { + opaque_member(m, dst); + } + } ChainAct::Call(call) => { - if !intrinsic_miss(m, star, NativeMiss::Call(origin, call)) { + if !intrinsic_miss(m, num, NativeMiss::Call(origin, call)) { let prev = m.cur_this.replace(origin); unknown_method(m, call, "argument of a written intrinsic key"); m.cur_this = prev; @@ -997,13 +1003,13 @@ pub fn plain_key(m: &mut Model, origin: Tok, act: ChainAct) { /// `p[n](...)` on primitives of kinds `prim` under a number key: an index /// of a string is a one-character string, of anything else absent; only a -/// key a store into ⊤ wrote on the prototype is a method (DECISIONS 217). +/// number key a store into ⊤ wrote on the prototype is a method (DECISIONS 217). pub fn prim_plain_key(m: &mut Model, prim: u16, call: CallId) { if prim & (prim::NULL | prim::UNDEF) == prim { return; } - let star = m.atom(crate::STAR_KEY); - if !intrinsic_miss(m, star, NativeMiss::PrimCall(prim, call)) { + let num = m.atom(NUM_KEY); + if !intrinsic_miss(m, num, NativeMiss::PrimCall(prim, call)) { unknown_method(m, call, "argument of a written primitive method"); } } diff --git a/crates/perry-wpfacts/src/builtins/native_protocol.rs b/crates/perry-wpfacts/src/builtins/native_protocol.rs index 65898806483..85259791659 100644 --- a/crates/perry-wpfacts/src/builtins/native_protocol.rs +++ b/crates/perry-wpfacts/src/builtins/native_protocol.rs @@ -115,11 +115,18 @@ pub fn native_written(m: &Model, np: Tok, a: Atom) -> bool { }); // A computed read (`STAR_KEY`) may name the key of any write. let any = m.atoms[a as usize] == crate::STAR_KEY; + // A read under a number (`NUM_KEY`, DECISIONS 217 c) names a numeric key + // only: an element store's, a computed one's, or a numeric name's. + let num_read = m.atoms[a as usize] == crate::model::NUM_KEY; let hit = |t: Tok| { m.native_writes.contains_key(&(t, None)) || m.native_writes.contains_key(&(t, Some(a))) || num.is_some_and(|n| m.native_writes.contains_key(&(t, Some(n)))) || (any && m.native_writes.keys().any(|&(w, _)| w == t)) + || (num_read + && m.native_writes.keys().any(|&(w, k)| { + w == t && k.is_some_and(|k| crate::solve::numeric_key(&m.atoms[k as usize])) + })) }; let global = match &m.toks[np as usize] { TokKind::NativeProto(n) => ns_by_name(n).is_some(), diff --git a/crates/perry-wpfacts/src/tests/proto_value.rs b/crates/perry-wpfacts/src/tests/proto_value.rs index 56ab44ef65c..845aa80ccd9 100644 --- a/crates/perry-wpfacts/src/tests/proto_value.rs +++ b/crates/perry-wpfacts/src/tests/proto_value.rs @@ -206,6 +206,49 @@ fn a_number_key_calls_what_a_store_into_an_intrinsic_wrote() { assert!(call_escapes(s, "s[n](d)")); } +/// `f` escapes from the callbacks an empty array's elements give, after +/// the store `w` into ⊤ (tsc's `forEach(nodeFactoryPatchers, fn => +/// fn(factory))`, where no patcher is ever added). +fn element_callee_escapes(w: &str) -> bool { + let pf = facts(&format!( + "declare const unknown: any; + class F {{ y = 2; make() {{ return 1; }} }} + const f = new F(); + const t: any = unknown.reach(0); + const n = Math.random() > 0.5 ? 0 : 1; + const k = String(Math.random()); + {w} + const fns: any[] = []; + function each(a: any[], cb: (x: any) => void) {{ + for (let i = 0; i < a.length; i++) cb(a[i]); + }} + each(fns, (fn: any) => fn(f)); + console.log(f.make());" + )); + tmpl(&pf, "F").escape.is_some() +} + +#[test] +fn a_number_key_reads_no_named_key_a_store_into_top_wrote() { + // `"elided"` is no ToPropertyKey of a number: `a[i]` of an array reads + // nothing it stored on Array.prototype. (Was: any key a store into ⊤ + // wrote, so `fn` held ⊤ and `f` escaped: tsc's `factory` singleton, + // after checker's `msg.elidedInCompatabilityPyramid = originalValue`.) + let fun = "(x: any) => ((globalThis as any).sink = x)"; + assert!(!element_callee_escapes("")); + assert!(!element_callee_escapes(&format!("t.elided = {fun};"))); + // A number key a store into ⊤ may have written: an element store, a + // numeric name, a computed key. + for w in [ + format!("t[n] = {fun};"), + format!("t[0] = {fun};"), + format!("t['1'] = {fun};"), + format!("t[k] = {fun};"), + ] { + assert!(element_callee_escapes(&w), "{w}"); + } +} + const PROTO_OF_PARAM: &str = r#" export function protoOf(o: any) { return o.__proto__; } export function getProto(o: any) { return Object.getPrototypeOf(o); } From 86bfb54f01cf8d1fcdc74fb7352b26f750107a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 12:03:26 +0000 Subject: [PATCH 080/144] =?UTF-8?q?wpfacts:=20per=20unit,=20the=20builtin?= =?UTF-8?q?=20prototypes'=20handover=20writes=20only=20the=20keys=20stored?= =?UTF-8?q?=20into=20=E2=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shared_closure turned every store and accessor into ⊤ into one on Object.prototype once a unit handed it over: an accessor there is E23 (every lookup, every fact off) and a store puts a ⊤ value in its field, neither of which the whole program does. A store or accessor into ⊤ already writes its key on each builtin prototype (native_written), in every unit as in the whole program, so the handover adds nothing. --- crates/perry-wpfacts/src/tests/unit_shared.rs | 34 +++++++++++++++++++ crates/perry-wpfacts/src/units/driver.rs | 7 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/perry-wpfacts/src/tests/unit_shared.rs b/crates/perry-wpfacts/src/tests/unit_shared.rs index 1188b0839b4..8c43af3f1b8 100644 --- a/crates/perry-wpfacts/src/tests/unit_shared.rs +++ b/crates/perry-wpfacts/src/tests/unit_shared.rs @@ -412,3 +412,37 @@ fn another_units_accessor_on_a_namespace_is_one_key() { pf.shared_out ); } + +/// Per unit, another unit's handover of `Object.prototype` is what the +/// whole program sees: a store or accessor into ⊤ writes its key on every +/// builtin prototype (`builtins::native_written`), not an accessor installed +/// on `Object.prototype` (E23, every lookup off) nor a ⊤ value stored there. +#[test] +fn another_units_handover_of_object_prototype_writes_only_the_keys_stored_into_top() { + let src = r#" + class A { a = 1; } + class M { m(o: any) { return o.a; } } + class N { inner: any; constructor() { this.inner = new M(); } } + declare const k: number; + declare const unknown: any; + function hand(): void { unknown.take(Object.prototype); } + function install(): void { + Object.defineProperty(unknown.v, "zz", { get() { return 1; } }); + } + hand(); + install(); + const v: any = k > 1 ? new N() : "text"; + const i = v.inner; + if (i) console.log(i.m(new A())); + "#; + for (mode, pf) in [("unit", units(src)), ("whole", whole(src))] { + let s: Vec<_> = pf.sites.iter().filter(|s| s.key == "m").collect(); + assert!( + !s.is_empty() + && s.iter() + .all(|s| !s.top && matches!(s.verdict, Verdict::T0Mono | Verdict::T0Set)), + "{mode}\n{}", + pf.dump_text() + ); + } +} diff --git a/crates/perry-wpfacts/src/units/driver.rs b/crates/perry-wpfacts/src/units/driver.rs index fc0e5a88132..275d18c5463 100644 --- a/crates/perry-wpfacts/src/units/driver.rs +++ b/crates/perry-wpfacts/src/units/driver.rs @@ -1089,13 +1089,16 @@ pub(super) fn replay_is_own(w: &crate::model::SharedWrite) -> bool { /// ⊤ is one on that object, as the whole-program solve sees it on the /// object's own token. A store under a computed key covers every key of its /// object: the stores under one key beside it are dropped, so a write that -/// says nothing new solves no unit again. +/// says nothing new solves no unit again. The builtin prototypes are not +/// such an object: a store or accessor into ⊤ already writes its key on +/// each of them (`builtins::native_written`), as in the whole program; it +/// installs no accessor on `Object.prototype` (E23) and stores no value. pub(super) fn shared_closure(shared: &mut std::collections::BTreeSet) { use crate::model::{Shared, SharedWrite}; let handed: Vec = shared .iter() .filter_map(|w| match w { - SharedWrite::Handed(s) => Some(s.clone()), + SharedWrite::Handed(s) if *s != Shared::ObjectProto => Some(s.clone()), _ => None, }) .collect(); From 9e349e27e8e2be31b53198196094af4fdb1218ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 16:06:13 +0000 Subject: [PATCH 081/144] =?UTF-8?q?wpfacts:=20an=20E6=20on=20=E2=8A=A4=20i?= =?UTF-8?q?n=20one=20unit=20reaches=20the=20other=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/solve/call_and_construct.rs | 5 +++ crates/perry-wpfacts/src/tests/unit_shared.rs | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index a79b0ea947f..9f9bf2d35d4 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -1822,6 +1822,11 @@ impl Model { crate::builtins::note_native_write(self, t, None, &what); } if t == TOP { + // Another unit passes the same runtime objects: an E6 on ⊤ is + // its E6 too (`replay_shared`); before, it reached no other unit. + if kind == Hard::E6SetPrototypeOf { + self.note_shared(SharedWrite::Hard(Shared::Top, kind)); + } return; } if let Some(is) = self.contract_recv.get(&t).cloned() { diff --git a/crates/perry-wpfacts/src/tests/unit_shared.rs b/crates/perry-wpfacts/src/tests/unit_shared.rs index 8c43af3f1b8..dd2384b0c41 100644 --- a/crates/perry-wpfacts/src/tests/unit_shared.rs +++ b/crates/perry-wpfacts/src/tests/unit_shared.rs @@ -446,3 +446,48 @@ fn another_units_handover_of_object_prototype_writes_only_the_keys_stored_into_t ); } } + +/// tsc's debug.ts: `Object.setPrototypeOf(unknown.x, p)` in one unit. ⊤ may +/// be any runtime object, so in the whole program every other unit's reads +/// through the builtin prototypes may find `p`'s keys. Before, an E6 on ⊤ +/// reached no other unit: the reading unit proved what the whole program +/// could not. +#[test] +fn another_units_e6_on_top_reaches_this_units_reads() { + let probe = r#" + class A { a = 1; } + class M { m(o: any) { return o.a; } } + class N { inner: any; constructor() { this.inner = new M(); } } + declare const k: number; + declare const unknown: any; + "#; + let helper = format!("{probe}\nObject.setPrototypeOf(unknown.x, {{ q: 1 }});"); + let main = format!( + "{probe}\nimport \"./h\";\nconst v: any = k > 1 ? new N() : \"text\";\nconst i = v.inner;\nif (i) console.log(i.m(new A()));" + ); + let hs: Vec<(String, perry_hir::Module)> = [("h", &helper), ("t", &main)] + .iter() + .map(|(name, src)| { + let path = format!("{name}.ts"); + let module = perry_parser::parse_typescript(src, &path).expect("parses"); + let mut hir = perry_hir::lower_module(&module, name, &path).expect("lowers"); + for imp in &mut hir.imports { + if let Some(n) = imp.source.strip_prefix("./") { + imp.resolved_path = Some(format!("{n}.ts")); + } + } + (path, hir) + }) + .collect(); + let list: Vec<(String, &perry_hir::Module)> = hs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let cfg = FactsConfig::default(); + let w = crate::compute(&list, &cfg); + let u = crate::compute_units(&list, &cfg); + let s = super::body_activation::stronger_sites(&u, &w); + assert!( + s.is_empty(), + "per unit claims more than whole-program: {s:?}\n--- per unit\n{}\n--- whole\n{}", + u.dump_text(), + w.dump_text() + ); +} From 5000049ce0494571eeeb54b8ab52c3e780acd1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 16:06:50 +0000 Subject: [PATCH 082/144] changelog: IPS5 wpfacts soundness fixes --- changelog.d/0-ips5-wpfacts-soundness.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog.d/0-ips5-wpfacts-soundness.md diff --git a/changelog.d/0-ips5-wpfacts-soundness.md b/changelog.d/0-ips5-wpfacts-soundness.md new file mode 100644 index 00000000000..33aef92fdcd --- /dev/null +++ b/changelog.d/0-ips5-wpfacts-soundness.md @@ -0,0 +1,6 @@ +Per-unit program facts no longer claim more than the whole program in three +cases. A unit that hands `Object.prototype` to unknown code now writes only the +keys stored into ⊤ onto the builtin prototypes (not every key); a number-key +read of an intrinsic prototype sees only number-key writes; and an +`Object.setPrototypeOf` on an unknown target in one unit reaches the other +units, as it does in the whole program. From 7be37242326a7a41b8f5021f986ee62b593d787d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 17:48:59 +0100 Subject: [PATCH 083/144] changelog: number the PR fragment --- ...-ips5-wpfacts-soundness.md => 13629-ips5-wpfacts-soundness.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-ips5-wpfacts-soundness.md => 13629-ips5-wpfacts-soundness.md} (100%) diff --git a/changelog.d/0-ips5-wpfacts-soundness.md b/changelog.d/13629-ips5-wpfacts-soundness.md similarity index 100% rename from changelog.d/0-ips5-wpfacts-soundness.md rename to changelog.d/13629-ips5-wpfacts-soundness.md From 0e138c0981109ff04df25af23bbbe6080101b536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 06:46:13 +0100 Subject: [PATCH 084/144] release: highlights for the 0.5.x release --- changelog.d/HIGHLIGHTS.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/changelog.d/HIGHLIGHTS.md b/changelog.d/HIGHLIGHTS.md index 7a91ef15b23..78c18f022c6 100644 --- a/changelog.d/HIGHLIGHTS.md +++ b/changelog.d/HIGHLIGHTS.md @@ -2,6 +2,20 @@ This is the first release since v0.5.1520 (2026-09-11). The per-PR entries follow under "All changes". Numbers below come from the linked fragments or from measured results; PRs without a fragment are cited by number only. +### What is new in this release + +**Faster, smaller programs.** On the js-compiled benchmark set, all 22 programs compile and print the same output as node, with this release and with v0.5.1520. Median run time, v0.5.1520 to this release: nbody 9368 to 364 ms, matmul 27916 to 182 ms, generators 20724 to 394 ms, array-sort 3300 to 102 ms, crc32 2804 to 524 ms. Compiled binaries shrank from about 17 MB to 6.5 MB, and hello world starts in 0.9 ms instead of 2.6 ms. No perry benchmark in that set is more than 10% slower than before (the worst is mandelbrot at +7%). + +**Startup.** Small programs start faster: the runtime builds its object layouts when the first such object is born, maps its fixed caches on first use, and builds per-thread state in place. On macOS arm64, hello world takes 2.33 ms instead of 2.95 ms and uses 5.3 MB instead of 6.0 MB peak RSS (#13345); a further 57,000 instructions (8.5% of hello on x86-64) were removed (#13484), and Linux no longer parses `/proc/self/maps` for the stack bounds (#13456). + +**Memory.** Large strings, arrays and objects are allocated in a young large-object space. Measured in the PRs: string building -22% RSS and JSON parsing -25% instructions (#13479), file reading -39% RSS and -74% instructions (#13463), and a second file-reading pass with -60% instructions and -27% RSS (#13392). + +**Whole-program analysis that scales.** The compiler's analysis of what the program does (used to remove checks and resolve calls at compile time) now works per module, with contracts between modules. It is the default (#13132) and finishes on large programs where the whole-program version did not: fastify, the TypeScript compiler, and opencode (6,877 modules, 31 GiB for the whole compile, #13355). It proves more read and call sites than before (tsc +4.8%, TypeScript compiler sources +6.9%, Effect schema +13.5%, #13297). + +**WebAssembly and WASI.** `perry compile --target wasip1` produces a core module that imports only `wasi_snapshot_preview1` and runs on plain preview 1 runtimes (#13343). It supports inbound TCP, signals and stdin (#13376), perry-stdlib and the Node module crates (#13356), and optional outbound networking (`PERRY_WASM_NET=1`, #13386) and page discard (`PERRY_WASM_DISCARD=1`, #13417). `--target wasi` produces a WASI 0.2 component; the native component host runs WASI 0.2 and 0.3 and no longer depends on tokio (#13189, #13364). Installed releases ship both the `wasm32-wasip1` and `wasm32-wasip2` runtime archives (#13394). Compiling for WASI needs LLVM 22.1.5 for native WebAssembly exception handling. + +**Real programs.** The scoreboard of 43 real programs matched node on every row at the 2026-10-19 check. fastify, zod, the TypeScript compiler, Effect and Next.js 16 compile and run; fastify handles request bodies through `req.pipe` (#13275), and zod 4.4.3 no longer miscompiles `generateFastpass` (#13270). Programs no longer abort at startup on Linux kernels built without transparent huge pages (#13309). `node:trace_events` namespace imports work (#13607), and the Windows, `dns`, `net` and `zlib` modules received further fixes listed under "All changes". + ### Upgrade notes and breaking changes **Native npm bindings were removed; the real package is compiled from source.** If your project imports one of these, the package must now be installed in `node_modules` (no `perry.compilePackages` entry is needed). The old bindings were hand-written Rust copies that differed from upstream, for example `jsonwebtoken.verify` accepting forged tokens, `decimal.js` division returning `1`, and `commander` never running `.action()` callbacks. @@ -85,7 +99,7 @@ Dynamic code (`eval`, `new Function`, dynamic `require`) is a single summary nod - Namespaces, including `enum` + `namespace` merging, compile with direct calls (#12775, #12802). - `events`, `stream`, `http` (`IncomingMessage`, `ServerResponse`), `worker_threads` (no multi-second stalls, #12448; `terminate()` stops any loop, #12762), `perf_hooks`, `dgram`, WebSocket upgrade (#12460) and `node:test` mocking (#12747) received parity fixes. See the per-PR list. - Bun: `Bun.Transpiler`/`Bun.build` subsets (#9602), `bun:sqlite` (#8525), SQL (#9634), zero-copy `bun:ffi` views (#8329). -- On the 2026-10-19 check, all 43 scoreboard programs print the same output as node. On the 2026-10-17 measurement, rows above 2× node by instructions were: fastify, schema-record, qs, zod, commander, effect, property-prototype. tsc is 1.72×; the rest are under 1.8×. +- On the 2026-10-19 check, all 43 scoreboard programs printed the same output as node. On the 2026-10-17 measurement, rows above 2× node by instructions were: fastify, schema-record, qs, zod, commander, effect, property-prototype. tsc is 1.72×; the rest are under 1.8×. ### Platforms @@ -94,11 +108,10 @@ Dynamic code (`eval`, `new Function`, dynamic `require`) is a single summary nod - Linux musl x86_64/aarch64 release packages build again (#9584, #9298, #12811). Android UI apps link `perry-stdlib` (#11290, #12768). - macOS UI builds produce signed `.app` bundles (#10240). Windows gets `fork()` IPC over named pipes (#8318), libuv-shaped errnos and `node:net` `err.errno` (#10385, #10403), `file://` drive letters (#12545) and `fs.fchown` parity (#12525). visionOS heap guards were fixed (#12247). -- `perry compile --target wasi` links a WASIp2 component behind the off-by-default `target-wasi` feature (#11413). Target detection covers OpenHarmony aliases (#12400). +- `perry compile --target wasi` builds a WASI 0.2 component and `--target wasip1` a preview 1 module (see above). Target detection covers OpenHarmony aliases (#12400). ### Known gaps -- ES2025 `Set` methods (`union`, `intersection`, and the rest) are not supported. - Promise/timer ordering differs from node in one case that is not yet root-caused. - Typed arrays larger than 1 GiB are not supported, and allocation-exhaustion behavior differs from node. - On the competitive suite at 2026-10-19, 218 of 242 rows pass, with no regressions against the previous release. From 436ae7e587a7334baa3acfa6231a6c6d440a51c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 18:16:03 +0100 Subject: [PATCH 085/144] release: highlights use the 2026-10-31 release checks --- changelog.d/HIGHLIGHTS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.d/HIGHLIGHTS.md b/changelog.d/HIGHLIGHTS.md index 78c18f022c6..d5b18257579 100644 --- a/changelog.d/HIGHLIGHTS.md +++ b/changelog.d/HIGHLIGHTS.md @@ -14,7 +14,7 @@ This is the first release since v0.5.1520 (2026-09-11). The per-PR entries follo **WebAssembly and WASI.** `perry compile --target wasip1` produces a core module that imports only `wasi_snapshot_preview1` and runs on plain preview 1 runtimes (#13343). It supports inbound TCP, signals and stdin (#13376), perry-stdlib and the Node module crates (#13356), and optional outbound networking (`PERRY_WASM_NET=1`, #13386) and page discard (`PERRY_WASM_DISCARD=1`, #13417). `--target wasi` produces a WASI 0.2 component; the native component host runs WASI 0.2 and 0.3 and no longer depends on tokio (#13189, #13364). Installed releases ship both the `wasm32-wasip1` and `wasm32-wasip2` runtime archives (#13394). Compiling for WASI needs LLVM 22.1.5 for native WebAssembly exception handling. -**Real programs.** The scoreboard of 43 real programs matched node on every row at the 2026-10-19 check. fastify, zod, the TypeScript compiler, Effect and Next.js 16 compile and run; fastify handles request bodies through `req.pipe` (#13275), and zod 4.4.3 no longer miscompiles `generateFastpass` (#13270). Programs no longer abort at startup on Linux kernels built without transparent huge pages (#13309). `node:trace_events` namespace imports work (#13607), and the Windows, `dns`, `net` and `zlib` modules received further fixes listed under "All changes". +**Real programs.** The scoreboard of 43 real programs matches node on every row on this release (checked 2026-10-31), including zod 4 and three.js math, whose compiles no longer run out of memory or loop in the per-module analysis (#13579, #13608). fastify, zod, the TypeScript compiler, Effect and Next.js 16 compile and run; fastify handles request bodies through `req.pipe` (#13275), and zod 4.4.3 no longer miscompiles `generateFastpass` (#13270). Programs no longer abort at startup on Linux kernels built without transparent huge pages (#13309). `node:trace_events` namespace imports work (#13607), and the Windows, `dns`, `net` and `zlib` modules received further fixes listed under "All changes". ### Upgrade notes and breaking changes @@ -80,13 +80,13 @@ Dynamic code (`eval`, `new Function`, dynamic `require`) is a single summary nod - A front-end prefetch (#12705) moved the tsc front end from 43.9 s to 35.9 s with byte-identical objects (#12728). - The whole-program analysis is computed faster (#12686). The per-module object cache and the completed-build cache share one compile-environment identity (#12582), and cached units are checkpointed atomically (#9997). - Compile memory is released across phase boundaries (#12813), class analysis is bounded by graph distance (#12814), and lowering inputs and function source text are released or shared instead of cloned (#12916). -- Compiling opencode now fits in about 30 GB, down from 140 GB: one walk per IIFE body and one shared type-alias map (#12890). +- Compiling opencode now fits in about 34 GB, down from 140 GB: one walk per IIFE body and one shared type-alias map (#12890). - Large links go through a response file on every host, so very big programs no longer hit command-line length limits (#12913). - Compile wall time and peak RSS measured on 2026-10-17: tsc 286 s / 9.7 GB, effect 31 s / 3.8 GB, schema-record 30 s / 3.9 GB. `perry compile --report-size` writes a per-crate and per-symbol size report (#8576). ### Node and ecosystem compatibility -- opencode compiles and runs again: compile memory (#12890), Effect tagged errors and `extends Error` across modules (#12897, #12898, #12899), class-expression instances (#12919), and the macOS link (#12913). +- opencode compiles and runs again (`opencode --help` prints the same as the bun build): compile memory (#12890), inline LLVM metadata in split modules (#13598), Effect tagged errors and `extends Error` across modules (#12897, #12898, #12899), class-expression instances (#12919), and the macOS link (#12913). - Subclasses of `EventEmitter`, streams, `Map`/`Set` and `Promise` run their overrides and methods correctly, through typed and untyped receivers and through `await` (#12815, #12888, #12891). - `zod` and `marked` can be used in the same program (#12882). `{ ...a, ...b }` where `b` came from `{ ...undefined }` no longer crashes, which `marked.parse` hit (#12843). - `commander` programs run 14.7% faster (#12894). @@ -99,7 +99,7 @@ Dynamic code (`eval`, `new Function`, dynamic `require`) is a single summary nod - Namespaces, including `enum` + `namespace` merging, compile with direct calls (#12775, #12802). - `events`, `stream`, `http` (`IncomingMessage`, `ServerResponse`), `worker_threads` (no multi-second stalls, #12448; `terminate()` stops any loop, #12762), `perf_hooks`, `dgram`, WebSocket upgrade (#12460) and `node:test` mocking (#12747) received parity fixes. See the per-PR list. - Bun: `Bun.Transpiler`/`Bun.build` subsets (#9602), `bun:sqlite` (#8525), SQL (#9634), zero-copy `bun:ffi` views (#8329). -- On the 2026-10-19 check, all 43 scoreboard programs printed the same output as node. On the 2026-10-17 measurement, rows above 2× node by instructions were: fastify, schema-record, qs, zod, commander, effect, property-prototype. tsc is 1.72×; the rest are under 1.8×. +- On the 2026-10-31 check, all 43 scoreboard programs printed the same output as node. On the 2026-10-17 measurement, rows above 2× node by instructions were: fastify, schema-record, qs, zod, commander, effect, property-prototype. tsc is 1.72×; the rest are under 1.8×. ### Platforms From 3a71b6cacbd794ffe758aef6875fdc3653a7999a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 20:58:17 +0100 Subject: [PATCH 086/144] feat(compile): drop modules only unreachable import() sites load (DCE layer 2, module level) (#13612) --- changelog.d/13612-dynamic-reach.md | 1 + crates/perry-wpfacts/src/builtins.rs | 17 +- crates/perry-wpfacts/src/gen.rs | 38 ++- crates/perry-wpfacts/src/gen/for_in.rs | 1 + crates/perry-wpfacts/src/gen/walk.rs | 241 +++++++++++-- crates/perry-wpfacts/src/gen_call.rs | 1 + crates/perry-wpfacts/src/gen_expr.rs | 92 ++++- crates/perry-wpfacts/src/gen_rest.rs | 12 +- crates/perry-wpfacts/src/lib.rs | 32 +- crates/perry-wpfacts/src/model.rs | 73 ++++ crates/perry-wpfacts/src/solve.rs | 8 + crates/perry-wpfacts/src/solve/apply.rs | 27 +- .../src/solve/call_and_construct.rs | 10 + crates/perry-wpfacts/src/solve/lookup.rs | 113 ++++++- crates/perry-wpfacts/src/summary.rs | 11 +- crates/perry-wpfacts/src/tests.rs | 3 +- .../src/tests/dead_dynamic_import.rs | 320 ++++++++++++++++++ crates/perry-wpfacts/src/tests/unit_ctor.rs | 20 +- .../src/tests/unit_mode/contracts.rs | 7 +- .../src/tests/unit_mode/shared_values.rs | 5 +- crates/perry/src/commands/compile.rs | 1 + .../perry/src/commands/compile/compile_env.rs | 3 + .../src/commands/compile/dynamic_reach.rs | 191 +++++++++++ .../src/commands/compile/program_facts.rs | 68 ++++ .../src/commands/compile/reachability.rs | 13 +- .../src/commands/compile/run_pipeline.rs | 14 + 26 files changed, 1261 insertions(+), 61 deletions(-) create mode 100644 changelog.d/13612-dynamic-reach.md create mode 100644 crates/perry-wpfacts/src/tests/dead_dynamic_import.rs create mode 100644 crates/perry/src/commands/compile/dynamic_reach.rs diff --git a/changelog.d/13612-dynamic-reach.md b/changelog.d/13612-dynamic-reach.md new file mode 100644 index 00000000000..4bc31f9d4e6 --- /dev/null +++ b/changelog.d/13612-dynamic-reach.md @@ -0,0 +1 @@ +A module that only dynamic `import()` / `require(expr)` sites load, each in a function that provably never runs, is no longer compiled. Right after collection, the whole program's activation pass (DECISIONS 225) finds the bodies that can run; a dynamic edge whose sites are all in other bodies is dead, and every module only dead edges reach leaves the graph before the facts and codegen. In the Supabase CLI this drops the whole oxfmt formatter (28 modules and its platform bindings): `@supabase/postgrest-typegen` imports it only from its default formatter, and the CLI always passes its own. The activation pass got the precision this needs: a conditional, a logical operator, a destructuring or parameter default and an assignment in an `if` branch reach their result only when their test can select them; an object literal's keys are present at birth; a read of an own key the object surely holds reads no `undefined` from the end of the prototype chain; a spread literal holds a key every source surely holds; and a module only `import()` loads runs only once an active body loads it. A builtin prototype method read as a value (`Function.prototype.call`, `String.prototype.replace`) is now a builtin function, so a callback given through `.call`, `.bind` or a call-bound helper counts as running. diff --git a/crates/perry-wpfacts/src/builtins.rs b/crates/perry-wpfacts/src/builtins.rs index a0c64a5a22a..6c60531f4d5 100644 --- a/crates/perry-wpfacts/src/builtins.rs +++ b/crates/perry-wpfacts/src/builtins.rs @@ -404,6 +404,17 @@ fn undescribed_member(m: &mut Model, dst: NodeId) { m.add_prim(dst, prim::ALL); } +/// A read on `ObjectProto` itself, not through a chain (`Function.prototype +/// .call`, `String.prototype.replace`): it stands for every builtin +/// prototype, the undescribed ones included, so any key but `constructor` +/// (`tok_load`) may name a builtin function or a primitive member. +pub fn intrinsic_proto_own(m: &mut Model, key: Key, dst: NodeId) { + if m.is_constructor_key(key) { + return; + } + undescribed_member(m, dst); +} + /// The family's `constructor`. fn family_ctor(m: &mut Model, fam: Family, dst: NodeId) { let nss: &[BuiltinNs] = match fam { @@ -1970,7 +1981,11 @@ pub fn new_builtin_ns(m: &mut Model, ns: BuiltinNs, call: CallId) { } BuiltinNs::Date | BuiltinNs::RegExp => { implicit_args(m, call, "builtin constructor argument", Protocol::Any); - let what = if ns == BuiltinNs::Date { "Date" } else { "RegExp" }; + let what = if ns == BuiltinNs::Date { + "Date" + } else { + "RegExp" + }; let o = site_tmpl(m, call, what); ret_tok(m, call, o); } diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index 2c3cbadda41..4cd55e8a78a 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -102,6 +102,10 @@ pub(crate) enum PendingWalk<'a> { Fields(u32, &'a perry_hir::Class), /// A closure's body (its emitted definition) in its creation context. Closure(crate::gen::Fx, &'a perry_hir::Expr), + /// A module only `import()` loads: its body runs once an `import()` + /// site a body that can run holds names it (or a module it loads + /// imports it). + Module(u32), } pub struct Gen<'a> { @@ -281,6 +285,16 @@ pub struct Gen<'a> { /// The negative control (`PERRY_FACTS_SABOTAGE=dynimport`): `import()` /// sites skip their module mapping (DECISIONS 226). pub dynimport_sabotage: bool, + /// The `import()` / `require(expr)` specifiers of the bodies walked, by + /// module (`live_dynamic_imports`): in the activation pass, the sites a + /// body that can run holds. + pub dynimport_seen: crate::pts::FxHashSet<(u32, String)>, + /// Activation: the modules only `import()` loads (`dynamic_only`), and + /// every module's body function once they exist. + pub dynamic_only: crate::pts::FxHashSet, + /// `FactsConfig::roots`: modules loaded whatever imports them. + pub roots: Vec, + pub init_fn_of: FxHashMap, /// Object-literal IIFE parameter node → the literal template it births. pub iife_birth: FxHashMap, /// Construction-phase analysis of plain functions used as constructors. @@ -331,6 +345,11 @@ pub(crate) struct Fx { pub new_target: NodeId, /// A plain function / function expression that `new` may call. pub fn_ctor: bool, + /// The innermost `if` branch being walked: its test node and the test + /// that selects it. An assignment to a local there reaches the local + /// only when the test can select the branch (`Cons::Guard`); every + /// other effect of the branch stays unconditional. + pub guard: Option<(NodeId, u8)>, } #[path = "gen/import_values.rs"] @@ -477,6 +496,10 @@ impl<'a> Gen<'a> { class_envs: FxHashMap::default(), export_sources: Default::default(), dynimport_sabotage: false, + dynimport_seen: Default::default(), + dynamic_only: Default::default(), + roots: Vec::new(), + init_fn_of: FxHashMap::default(), iife_birth: FxHashMap::default(), fn_phase: FxHashMap::default(), rooted_stores: Default::default(), @@ -1121,6 +1144,19 @@ impl<'a> Gen<'a> { if !c.is_literal_shape() { self.own_classes.entry(display.clone()).or_insert(tm); } + // An object literal's shape: every birth evaluates all its values + // first and defines every key before the object can be seen, so its + // keys are present (the fields of a class are not: its constructor + // can read them before they are stored). + let literal_keys: Vec = if c.is_literal_shape() { + c.fields + .iter() + .filter(|f| f.key_expr.is_none()) + .map(|f| self.m.atom(&f.name)) + .collect() + } else { + Vec::new() + }; self.m.tmpls.push(Template { kind: TmplKind::Class { module: mi, @@ -1130,7 +1166,7 @@ impl<'a> Gen<'a> { }, display, birth_keys: Vec::new(), - definite: Vec::new(), + definite: literal_keys, declared: Vec::new(), reserved: Vec::new(), parent: None, diff --git a/crates/perry-wpfacts/src/gen/for_in.rs b/crates/perry-wpfacts/src/gen/for_in.rs index 2782df1cbf3..3eaee252210 100644 --- a/crates/perry-wpfacts/src/gen/for_in.rs +++ b/crates/perry-wpfacts/src/gen/for_in.rs @@ -263,6 +263,7 @@ impl<'a> Gen<'a> { self.m.captures.insert((fx.this, g)); } let bfx = Fx { + guard: None, f: g, this, ret, diff --git a/crates/perry-wpfacts/src/gen/walk.rs b/crates/perry-wpfacts/src/gen/walk.rs index 6eb82206513..d8d30d4117a 100644 --- a/crates/perry-wpfacts/src/gen/walk.rs +++ b/crates/perry-wpfacts/src/gen/walk.rs @@ -31,53 +31,189 @@ impl<'a> Gen<'a> { self.compute_birth_keys(tm, module); } } + // DECISIONS 225: a module only `import()` loads runs when an active + // body loads it; its body is walked then (`PendingWalk::Module`). + if self.m.act.is_some() { + self.dynamic_only = self.dynamic_only_modules(); + } + if !self.dynamic_only.is_empty() { + for mi in 0..modules.len() as u32 { + let f = self.synthetic_fn(mi, ""); + self.init_fn_of.insert(mi, f); + } + } for mi in 0..modules.len() { // Over a bound: the program gets no facts; stop generating. if self.m.aborted { return; } let mi = mi as u32; - let init_fn = self.synthetic_fn(mi, ""); - self.m.note_walked(init_fn); - let this = self.m.funcs[init_fn as usize].this; - // Module top-level `this`: CJS `module.exports`-like object or - // undefined in ESM; ⊤-free: a fresh literal-ish object. - self.m.add_prim(this, prim::UNDEF); - let fx = Fx { - module: mi, - f: init_fn, - this, - ret: self.m.funcs[init_fn as usize].ret, - class: None, - ctor_of: None, - new_target: self.m.all_prim_node, - fn_ctor: false, + let init_fn = match self.init_fn_of.get(&mi) { + Some(&f) => f, + None => { + let f = self.synthetic_fn(mi, ""); + self.init_fn_of.insert(mi, f); + f + } }; - for g in self.globals_of(mi as usize) { - if let Some(init) = &g.init { - let v = self.expr(&fx, init); - let gn = self.global(mi, g.id); - self.m.edge(v, gn); + if self.dynamic_only.contains(&mi) && self.defer_walk(init_fn, PendingWalk::Module(mi)) + { + continue; + } + self.walk_module(mi, init_fn); + if self.m.aborted { + return; + } + } + } + + /// The modules that load only through `import()`: no static import or + /// re-export reaches them from a module no edge enters (the entry, a + /// worker). Empty when a re-export source does not resolve (every + /// module loads). + fn dynamic_only_modules(&self) -> crate::pts::FxHashSet { + let n = self.modules.len(); + let mut entered = vec![false; n]; + let mut succ: Vec> = vec![Vec::new(); n]; + for mi in 0..n { + let imports = self.imports_of(mi); + let path = self.modules[mi].0; + // A re-export source as the compile driver resolved it + // (`export_sources`), else through an import of the same source. + let target = |source: &str| { + self.export_sources + .get(path) + .and_then(|m| m.get(source)) + .and_then(|r| self.path_index.get(r).copied()) + .or_else(|| { + imports + .iter() + .find(|i| i.source == source) + .and_then(|i| i.resolved_path.as_ref()) + .and_then(|r| self.path_index.get(r).copied()) + }) + }; + for i in &imports { + if i.type_only || i.runtime_erased { + continue; + } + let Some(t) = i + .resolved_path + .as_ref() + .and_then(|r| self.path_index.get(r).copied()) + else { + continue; + }; + entered[t as usize] = true; + if !i.is_dynamic { + succ[mi].push(t); } } - for s in self.init_of(mi as usize) { - self.stmt(&fx, s); - self.name_enum_object(mi, s); + for e in &self.modules[mi].1.exports { + let source = match e { + perry_hir::Export::ReExport { source, .. } + | perry_hir::Export::ExportAll { source } + | perry_hir::Export::NamespaceReExport { source, .. } => source, + perry_hir::Export::Named { .. } => continue, + }; + if source.starts_with("node:") + || !(source.starts_with('.') || source.starts_with('/')) + { + if let Some(t) = target(source) { + entered[t as usize] = true; + succ[mi].push(t); + } + continue; + } + match target(source) { + Some(t) => { + entered[t as usize] = true; + succ[mi].push(t); + } + None => { + return Default::default(); + } + } } - for f in self.functions_of(mi as usize) { - if self.m.aborted { - return; + } + let mut eager = vec![false; n]; + let mut work: Vec = (0..n as u32) + .filter(|&m| { + !entered[m as usize] || self.roots.iter().any(|r| r == self.modules[m as usize].0) + }) + .collect(); + for &m in &work { + eager[m as usize] = true; + } + while let Some(m) = work.pop() { + for &t in &succ[m as usize] { + if !eager[t as usize] { + eager[t as usize] = true; + work.push(t); } - let fxi = self.mods[mi as usize].funcs[&f.id]; - self.walk_fn(mi, fxi, f, None, None); } - for c in self.classes_of(mi as usize) { - if self.m.aborted { - return; + } + (0..n as u32).filter(|&m| !eager[m as usize]).collect() + } + + /// The body of module `mi`: its globals, top-level statements, functions + /// and classes. A module it loads statically loads with it. + pub(crate) fn walk_module(&mut self, mi: u32, init_fn: FnIdx) { + if !self.dynamic_only.is_empty() { + for i in self.imports_of(mi as usize) { + if i.is_dynamic || i.type_only || i.runtime_erased { + continue; + } + let t = i + .resolved_path + .as_ref() + .and_then(|r| self.path_index.get(r).copied()); + if let Some(t) = t.filter(|t| self.dynamic_only.contains(t)) { + let f = self.init_fn_of[&t]; + self.m.activate(f, "import"); } - self.walk_class(mi, c, &fx); } } + self.m.note_walked(init_fn); + let this = self.m.funcs[init_fn as usize].this; + // Module top-level `this`: CJS `module.exports`-like object or + // undefined in ESM; ⊤-free: a fresh literal-ish object. + self.m.add_prim(this, prim::UNDEF); + let fx = Fx { + guard: None, + module: mi, + f: init_fn, + this, + ret: self.m.funcs[init_fn as usize].ret, + class: None, + ctor_of: None, + new_target: self.m.all_prim_node, + fn_ctor: false, + }; + for g in self.globals_of(mi as usize) { + if let Some(init) = &g.init { + let v = self.expr(&fx, init); + let gn = self.global(mi, g.id); + self.m.edge(v, gn); + } + } + for s in self.init_of(mi as usize) { + self.stmt(&fx, s); + self.name_enum_object(mi, s); + } + for f in self.functions_of(mi as usize) { + if self.m.aborted { + return; + } + let fxi = self.mods[mi as usize].funcs[&f.id]; + self.walk_fn(mi, fxi, f, None, None); + } + for c in self.classes_of(mi as usize) { + if self.m.aborted { + return; + } + self.walk_class(mi, c, &fx); + } } fn compute_birth_keys(&mut self, tm: TmplId, module: &Module) { @@ -315,6 +451,10 @@ impl<'a> Gen<'a> { } PendingWalk::Fields(mi, c) => self.walk_fields_body(mi, c), PendingWalk::Closure(cfx, e) => self.walk_closure_body(&cfx, e), + PendingWalk::Module(mi) => { + let f = self.init_fn_of[&mi]; + self.walk_module(mi, f); + } } } } @@ -333,6 +473,7 @@ impl<'a> Gen<'a> { let info = self.m.funcs[fxi as usize].clone(); let fn_ctor = class.is_none() && ctor_of.is_none(); let fx = Fx { + guard: None, module: mi, f: fxi, this: info.this, @@ -376,8 +517,18 @@ impl<'a> Gen<'a> { for p in params { let n = self.local(fx.module, p.id); if let Some(d) = &p.default { + // The default is the parameter's value only when a call + // passes `undefined` or nothing (missing arguments are + // `undefined`, `bind_args`). let v = self.expr(fx, d); - self.m.edge(v, n); + self.m.cons( + n, + Cons::Guard { + test: crate::model::guard::UNDEF, + from: v, + dst: n, + }, + ); } } } @@ -400,6 +551,7 @@ impl<'a> Gen<'a> { self.m.note_walked(fi); let fthis = self.m.funcs[fi as usize].this; let ffx = Fx { + guard: None, module: mi, f: fi, this: fthis, @@ -423,7 +575,13 @@ impl<'a> Gen<'a> { // `x: T;` defines `undefined` first; when the constructor // assigns it before anyone can observe the instance, that // `undefined` is never seen. - None if f.key_expr.is_none() && ctor_definite.contains(&f.name) => continue, + None if f.key_expr.is_none() + && (ctor_definite.contains(&f.name) || c.is_literal_shape()) => + { + // An object literal's shape: its constructor stores + // every key from the literal's values first. + continue; + } None => self.prim_node(prim::UNDEF), }; match &f.key_expr { @@ -468,6 +626,7 @@ impl<'a> Gen<'a> { let si = self.static_init_fn[&tm]; self.m.note_walked(si); let sfx = Fx { + guard: None, module: mi, f: si, this: self.m.funcs[si as usize].this, @@ -681,10 +840,18 @@ impl<'a> Gen<'a> { then_branch, else_branch, } => { - let _ = self.expr(fx, condition); - self.stmts(fx, then_branch); + let (t, then_test, else_test) = self.guard_test(fx, condition); + let then_fx = Fx { + guard: Some((t, then_test)), + ..fx.clone() + }; + self.stmts(&then_fx, then_branch); if let Some(e) = else_branch { - self.stmts(fx, e); + let else_fx = Fx { + guard: Some((t, else_test)), + ..fx.clone() + }; + self.stmts(&else_fx, e); } } Stmt::While { condition, body } | Stmt::DoWhile { body, condition } => { diff --git a/crates/perry-wpfacts/src/gen_call.rs b/crates/perry-wpfacts/src/gen_call.rs index 5b6685e5977..09335e31963 100644 --- a/crates/perry-wpfacts/src/gen_call.rs +++ b/crates/perry-wpfacts/src/gen_call.rs @@ -244,6 +244,7 @@ impl<'a> Gen<'a> { } let fn_ctor = !*is_arrow; let cfx = Fx { + guard: None, module: mi, f, this: info.this, diff --git a/crates/perry-wpfacts/src/gen_expr.rs b/crates/perry-wpfacts/src/gen_expr.rs index f79dc52419c..aabe9626af6 100644 --- a/crates/perry-wpfacts/src/gen_expr.rs +++ b/crates/perry-wpfacts/src/gen_expr.rs @@ -98,6 +98,35 @@ impl<'a> Gen<'a> { } /// Fail closed: every operand escapes, the result is ⊤. + /// The node a conditional's test reads and the guards of its two + /// branches: a comparison with `undefined`/`null` tests its operand, + /// anything else tests its own value's truthiness. + pub(crate) fn guard_test(&mut self, fx: &Fx, condition: &Expr) -> (NodeId, u8, u8) { + use crate::model::guard::*; + if let Expr::Compare { op, left, right } = condition { + let (operand, nil) = match (left.as_ref(), right.as_ref()) { + (x, Expr::Undefined) | (Expr::Undefined, x) => (Some(x), Expr::Undefined), + (x, Expr::Null) | (Expr::Null, x) => (Some(x), Expr::Null), + _ => (None, Expr::Undefined), + }; + if let Some(x) = operand { + let strict_undef = matches!(nil, Expr::Undefined); + let tests = match op { + CompareOp::Eq if strict_undef => Some((UNDEF, NOT_UNDEF)), + CompareOp::Ne if strict_undef => Some((NOT_UNDEF, UNDEF)), + CompareOp::LooseEq => Some((NULLISH, NOT_NULLISH)), + CompareOp::LooseNe => Some((NOT_NULLISH, NULLISH)), + _ => None, + }; + if let Some((then_test, else_test)) = tests { + let v = self.expr(fx, x); + return (v, then_test, else_test); + } + } + } + (self.expr(fx, condition), TRUTHY, FALSY) + } + pub(crate) fn fail_closed(&mut self, fx: &Fx, e: &Expr, name: &str) -> NodeId { let why = format!("{UNMODELED_OPERAND} {name}"); let mut kids: Vec<&Expr> = Vec::new(); @@ -449,7 +478,17 @@ impl<'a> Gen<'a> { Expr::LocalSet(id, v) => { let vn = self.expr(fx, v); let l = self.local_write(mi, *id); - self.m.edge(vn, l); + match fx.guard { + Some((t, test)) => self.m.cons( + t, + Cons::Guard { + test, + from: vn, + dst: l, + }, + ), + None => self.m.edge(vn, l), + } vn } Expr::ScopedTemp { id, value, body } => { @@ -627,8 +666,21 @@ impl<'a> Gen<'a> { let r = self.expr(fx, right); let out = self.tmp("logical"); self.m.edge(l, out); - self.m.edge(r, out); - let _ = matches!(op, LogicalOp::And); + // The right operand is the result only when the left one + // selects it. + let test = match op { + LogicalOp::And => crate::model::guard::TRUTHY, + LogicalOp::Or => crate::model::guard::FALSY, + LogicalOp::Coalesce => crate::model::guard::NULLISH, + }; + self.m.cons( + l, + Cons::Guard { + test, + from: r, + dst: out, + }, + ); out } Expr::Conditional { @@ -636,12 +688,26 @@ impl<'a> Gen<'a> { then_expr, else_expr, } => { - let _ = self.expr(fx, condition); + let (t, then_test, else_test) = self.guard_test(fx, condition); let a = self.expr(fx, then_expr); let b = self.expr(fx, else_expr); let out = self.tmp("cond"); - self.m.edge(a, out); - self.m.edge(b, out); + self.m.cons( + t, + Cons::Guard { + test: then_test, + from: a, + dst: out, + }, + ); + self.m.cons( + t, + Cons::Guard { + test: else_test, + from: b, + dst: out, + }, + ); out } Expr::TypeOf(x) => { @@ -1041,6 +1107,16 @@ impl<'a> Gen<'a> { let has_spread = parts.iter().any(|(k, _)| k.is_none()); let (tm, t) = self.new_lit(mi, e, if has_spread { &[] } else { &named }); let obj = self.tok_node(t); + if has_spread { + let named = named.iter().map(|k| self.m.atom(k)).collect(); + self.m.lit_spread.insert( + tm, + crate::model::LitSpread { + named, + sites: Vec::new(), + }, + ); + } for (k, v) in parts { let vn = self.expr(fx, v); match k { @@ -1060,6 +1136,10 @@ impl<'a> Gen<'a> { // Own enumerable keys of the source, key by key // (getters run): `Cons::Spread`. let site = self.store_site(mi, fx.f, "lit...".into(), Some(tm), true); + if let Some(l) = self.m.lit_spread.get_mut(&tm) { + l.sites.push((site, None)); + } + self.m.lit_spread_site.insert(site, tm); self.m.cons(vn, Cons::Spread { into: obj, site }); } } diff --git a/crates/perry-wpfacts/src/gen_rest.rs b/crates/perry-wpfacts/src/gen_rest.rs index 8156229e7b6..42b086556b2 100644 --- a/crates/perry-wpfacts/src/gen_rest.rs +++ b/crates/perry-wpfacts/src/gen_rest.rs @@ -1831,14 +1831,22 @@ impl<'a> Gen<'a> { // codegen's dispatch resolves it. A target outside this view // (native, another unit) is the fallback's ⊤. let out = self.tmp("dynimport"); + for p in paths { + self.dynimport_seen.insert((mi, p.clone())); + } let imports = self.imports_of(mi as usize); for p in paths { - let target = imports + let loads = imports .iter() - .filter(|_| !self.dynimport_sabotage) .find(|i| &i.source == p && (i.is_dynamic || i.is_dynamic_target)) .and_then(|i| i.resolved_path.as_ref()) .and_then(|r| self.path_index.get(r).copied()); + // The site loads its target: its body runs. + if let Some(t) = loads.filter(|t| self.dynamic_only.contains(t)) { + let f = self.init_fn_of[&t]; + self.m.activate(f, "import"); + } + let target = loads.filter(|_| !self.dynimport_sabotage); if let Some(t) = target { let ns = self.mods[t as usize].ns_tok; self.m.add_tok(out, ns); diff --git a/crates/perry-wpfacts/src/lib.rs b/crates/perry-wpfacts/src/lib.rs index 8d29a2432f3..7e88ec19ae0 100644 --- a/crates/perry-wpfacts/src/lib.rs +++ b/crates/perry-wpfacts/src/lib.rs @@ -49,6 +49,9 @@ pub(crate) const STAR_KEY: &str = "\u{1}*"; #[derive(Clone, Debug, Default)] pub struct FactsConfig { + /// Modules the program loads besides the ones no edge enters (worker + /// entries, bundle extensions): their bodies run whatever imports them. + pub roots: Vec, /// Capture observational site data for the existing facts dump only. pub diagnostics: bool, /// Record the constraint relations for the independent recomputation. @@ -314,6 +317,7 @@ fn generate<'a>( g.m.diagnostic = Some(Default::default()); } g.export_sources = cfg.export_sources.clone(); + g.roots = cfg.roots.clone(); g.dynimport_sabotage = cfg.sabotage.as_deref() == Some("dynimport"); g.unit_in = published; g.view = view; @@ -409,6 +413,32 @@ fn activation<'a>( cfg: &FactsConfig, t0: std::time::Instant, ) -> Option { + activation_pass(sorted, cfg, t0).map(|(a, _)| a) +} + +/// The dynamic `import()` / `require(expr)` specifiers a body that can run +/// holds (DECISIONS 225's activation pass over the whole program), as +/// (module path, specifier); `None` past the work bound (every site may +/// run). A site no body that can run holds never imports its target. +pub fn live_dynamic_imports( + modules: &[(String, &Module)], + cfg: &FactsConfig, +) -> Option> { + let mut sorted: Vec<(&str, &Module)> = modules.iter().map(|(p, m)| (p.as_str(), *m)).collect(); + sorted.sort_by(|a, b| a.0.cmp(b.0)); + let (_, seen) = activation_pass(&sorted, cfg, std::time::Instant::now())?; + Some( + seen.into_iter() + .map(|(mi, spec)| (sorted[mi as usize].0.to_owned(), spec)) + .collect(), + ) +} + +fn activation_pass<'a>( + sorted: &[(&'a str, &'a Module)], + cfg: &FactsConfig, + t0: std::time::Instant, +) -> Option<(model::Act, crate::pts::FxHashSet<(u32, String)>)> { let sabotage = cfg .sabotage .as_deref() @@ -429,7 +459,7 @@ fn activation<'a>( let mut a = g.m.act.take()?; a.fixed = true; a.queue.clear(); - Some(a) + Some((a, std::mem::take(&mut g.dynimport_seen))) } /// [`compute`] for one unit of a per-unit run: `published` is what the other diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index 8ec89c878d4..c5ae8fd21c9 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -43,6 +43,39 @@ pub const TOP: Tok = 0; pub const NO_CTX: CallId = u32::MAX; /// Primitive kind bits carried next to the object tokens of a node. +/// What a test value must be able to hold for a [`Cons::Guard`] to let its +/// branch through. ⊤ and parameter or argument markers (another unit's or +/// an unseen caller's value) can hold anything; every other token is an +/// object or a function: truthy, neither `undefined` nor `null`. +pub mod guard { + pub const UNDEF: u8 = 0; + pub const NOT_UNDEF: u8 = 1; + pub const NULLISH: u8 = 2; + pub const NOT_NULLISH: u8 = 3; + pub const TRUTHY: u8 = 4; + pub const FALSY: u8 = 5; + + /// The primitive kinds (`prim` bits) a test admits. + pub fn prims(test: u8) -> u16 { + use super::prim as p; + match test { + UNDEF => p::UNDEF, + NOT_UNDEF => p::ALL & !p::UNDEF, + NULLISH => p::UNDEF | p::NULL, + NOT_NULLISH => p::ALL & !(p::UNDEF | p::NULL), + // Every primitive kind but `undefined`/`null` has a truthy value. + TRUTHY => p::NUM | p::STR | p::BOOL | p::BIGINT | p::SYMBOL, + // `0`, `""`, `false`, `0n`, `null`, `undefined`. + _ => p::NUM | p::STR | p::BOOL | p::BIGINT | p::NULL | p::UNDEF, + } + } + + /// An object or function token admits the test. + pub fn objects(test: u8) -> bool { + matches!(test, NOT_UNDEF | NOT_NULLISH | TRUTHY) + } +} + pub mod prim { pub const NUM: u16 = 1 << 0; pub const STR: u16 = 1 << 1; @@ -701,6 +734,17 @@ pub enum Cons { from: NodeId, reason: Option, }, + /// `from` flows into `dst` once the node may hold a value `test` + /// admits (`guard`): a conditional's branch, a logical operator's right + /// operand, a destructuring default reach the result only when their + /// test can take the value that selects them. A node that never holds + /// such a value is a test that never selects the branch. + Guard { test: u8, from: NodeId, dst: NodeId }, + /// The node's values flow into `dst` without `undefined`: the chain of + /// a read whose own data key the object surely holds. The lookup stops + /// at the object, so the chain's end (a key no link defines reads + /// `undefined`) is never reached; its values stay, a superset. + Defined { dst: NodeId }, /// Every object token reaches a HARD operation. Hard { kind: Hard, reason: u32 }, /// `delete o.key`: E7 HARD on every object token; on ⊤ (which may be a @@ -950,6 +994,8 @@ impl Cons { Cons::ContractThis { .. } => "contractthis", Cons::OverrideThis { .. } => "overridethis", Cons::TopIfAny { .. } => "topifany", + Cons::Guard { .. } => "guard", + Cons::Defined { .. } => "defined", Cons::Hard { .. } => "hard", Cons::DeleteKey { .. } => "deletekey", Cons::AddsKeys => "addskeys", @@ -1124,6 +1170,16 @@ impl WorkProfile { pub const STEP: u64 = 5_000_000; } +/// An object literal with spreads: the keys it defines itself, and per +/// spread site the keys every source so far surely holds (`None`: no source +/// yet). A key is sure when the literal defines it or one spread copies it +/// from every source. +#[derive(Default, Clone, Debug)] +pub struct LitSpread { + pub named: Vec, + pub sites: Vec<(SiteId, Option>)>, +} + #[derive(Default)] pub struct Model { pub(crate) diagnostic: Option, @@ -1250,6 +1306,23 @@ pub struct Model { /// the source gains later is copied too (`Model::field`). pub spread_to: FxHashMap>, pub spread_done: crate::pts::FxHashSet<(Tok, Tok)>, + /// Object literals with spreads (`{ a, ...s }`): which keys they surely + /// hold (`LitSpread`), by template, and their spread sites. + pub lit_spread: FxHashMap, + pub lit_spread_site: FxHashMap, + /// Reads of a key a fact unit surely holds (they read no `undefined`), + /// by template: when the key stops being sure (a spread source that + /// lacks it, a `delete`), each gets `undefined` after all. + pub present_reads: FxHashMap>, + pub present_seen: crate::pts::FxHashSet<(TmplId, Atom, NodeId)>, + /// The `Cons::Defined` node a present read's chain flows through, by + /// the read's destination. + pub defined_via: FxHashMap, + /// (chain node, read destination) pairs with their `Cons::Defined`. + pub defined_done: crate::pts::FxHashSet<(NodeId, NodeId)>, + /// Fact units whose keys can disappear (delete, defineProperty, with): + /// no key of theirs is sure. + pub keys_unsure: crate::pts::FxHashSet, /// Calls the runtime makes for a protocol (ToPrimitive, iteration …). pub protocol_calls: crate::pts::FxHashSet, /// Shared prototype-chain value nodes: (token, key) → node. diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index ffad674b3d5..fde6194d2a8 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -626,6 +626,12 @@ impl Model { if !self.spread_done.insert((s, d)) { return; } + if let Some(&tm) = self.lit_spread_site.get(&site) { + if self.tmpl_of(d) == Some(tm) { + let keys = self.spread_source_keys(s); + self.spread_source(tm, site, keys); + } + } if d == TOP || matches!(self.toks[d as usize], TokKind::BuiltinFn) { let r = self.reason("spread into a value from unmodeled code"); let all = if s == TOP { @@ -1771,6 +1777,8 @@ impl Model { "overridethis {n} {tmpl} {} {reason}", self.key_name(Key::Named(*key)) ), + Cons::Guard { test, from, dst } => format!("guard {n} {test} {from} {dst}"), + Cons::Defined { dst } => format!("defined {n} {dst}"), Cons::TopIfAny { dst, from, reason } => format!( "topifany {n} {dst} {from} {}", reason.map_or(-1, |r| r as i64) diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index 63016a1a1f9..389e87b4eaf 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -296,6 +296,23 @@ impl Model { } } } + Cons::Guard { test, from, dst } => { + let admits = prim & guard::prims(test) != 0 + || toks + .into_iter() + .any(|&t| t == TOP || guard::objects(test) || self.is_marker(t)); + if admits { + self.edge(from, dst); + } + } + Cons::Defined { dst } => { + for &t in toks { + self.add_tok(dst, t); + } + if prim & !prim::UNDEF != 0 { + self.add_prim(dst, prim & !prim::UNDEF); + } + } Cons::TopIfAny { dst, from, reason } => { if !toks.is_empty() || prim != 0 { if let Some(r) = reason { @@ -769,7 +786,8 @@ impl Model { if self.foreign.contains_key(&tm) { self.relinked_parents.insert(tm); } else if theirs(self, of) { - let reason = self.reason("class given as the parent of a class unknown code constructs"); + let reason = self + .reason("class given as the parent of a class unknown code constructs"); self.escape(p, reason); } } @@ -1097,6 +1115,13 @@ impl Model { Cons::RetInto { call, run } => self.ret_into(call, run, toks, prim), Cons::ArgInto { call, run, i } => self.arg_into(call, run, i, toks), Cons::Spread { into, site } => { + // A primitive source copies no key surely (`null`, + // `undefined`: none at all). + if prim != 0 { + if let Some(&tm) = self.lit_spread_site.get(&site) { + self.spread_source(tm, site, Vec::new()); + } + } for &s in toks { self.cons(into, Cons::SpreadFrom { src: s, site }); } diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index 9f9bf2d35d4..e8158c18405 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -1802,6 +1802,16 @@ impl Model { pub fn mark_hard(&mut self, t: Tok, kind: Hard, reason: u32) { self.virt_revoke(t, None); + if matches!( + kind, + Hard::E5DefineProperty | Hard::E7Delete | Hard::E15With + ) { + if let Some(tm) = self.tmpl_of(t) { + if self.keys_unsure.insert(tm) { + self.flush_present_reads(tm); + } + } + } if kind == Hard::E6SetPrototypeOf { self.super_relink.insert(t); } diff --git a/crates/perry-wpfacts/src/solve/lookup.rs b/crates/perry-wpfacts/src/solve/lookup.rs index 6812e7f531b..00cbcf03c9e 100644 --- a/crates/perry-wpfacts/src/solve/lookup.rs +++ b/crates/perry-wpfacts/src/solve/lookup.rs @@ -4,6 +4,77 @@ use crate::builtins; use crate::model::*; impl Model { + /// `a` is a key every object of spread literal `tm` holds. + pub(crate) fn spread_sure(&self, tm: TmplId, a: Atom) -> bool { + self.lit_spread.get(&tm).is_some_and(|l| { + l.named.contains(&a) + || l.sites + .iter() + .any(|(_, keys)| keys.as_ref().is_none_or(|k| k.contains(&a))) + }) + } + + /// The keys a spread copies surely from source `s`: the definite keys of + /// a fact unit's object; none from anything else. + pub(crate) fn spread_source_keys(&self, s: Tok) -> Vec { + match self.tmpl_of(s) { + Some(tm) + if self.tmpls[tm as usize].kind.is_fact_unit() + && !self.keys_unsure.contains(&tm) => + { + let mut keys = self.tmpls[tm as usize].definite.clone(); + if let Some(l) = self.lit_spread.get(&tm) { + keys.extend(l.named.iter().copied()); + } + keys + } + _ => Vec::new(), + } + } + + /// Spread site `site` of literal `tm` copies from a source holding + /// surely `keys` (empty: a primitive or an opaque value). + pub(crate) fn spread_source(&mut self, tm: TmplId, site: SiteId, keys: Vec) { + let Some(l) = self.lit_spread.get_mut(&tm) else { + return; + }; + let Some((_, common)) = l.sites.iter_mut().find(|(s, _)| *s == site) else { + return; + }; + let shrank = match common { + None => { + *common = Some(keys); + true + } + Some(c) => { + let before = c.len(); + c.retain(|k| keys.contains(k)); + c.len() != before + } + }; + if shrank { + self.flush_present_reads(tm); + } + } + + /// The reads of `tm` that found a key no longer sure read `undefined`. + pub(crate) fn flush_present_reads(&mut self, tm: TmplId) { + let reads = self.present_reads.remove(&tm).unwrap_or_default(); + let mut kept = Vec::new(); + for (a, dst) in reads { + let sure = !self.keys_unsure.contains(&tm) + && (self.tmpls[tm as usize].definite.contains(&a) || self.spread_sure(tm, a)); + if sure { + kept.push((a, dst)); + } else { + self.add_prim(dst, prim::UNDEF); + } + } + if !kept.is_empty() { + self.present_reads.insert(tm, kept); + } + } + /// dst ⊇ value of `key` read on object `t` (receiver `origin`). pub fn load_from(&mut self, origin: Tok, t: Tok, key: Key, dst: NodeId) { self.mark_use(t, crate::model::MARK_READ); @@ -61,8 +132,13 @@ impl Model { // fact unit is known to be present. let present = match (key, self.tmpl_of(t)) { (Key::Named(a), Some(tm)) => { - self.tmpls[tm as usize].kind.is_fact_unit() - && self.tmpls[tm as usize].definite.contains(&a) + let sure = self.tmpls[tm as usize].kind.is_fact_unit() + && !self.keys_unsure.contains(&tm) + && (self.tmpls[tm as usize].definite.contains(&a) || self.spread_sure(tm, a)); + if sure && self.present_seen.insert((tm, a, dst)) { + self.present_reads.entry(tm).or_default().push((a, dst)); + } + sure } _ => false, }; @@ -110,8 +186,16 @@ impl Model { // DECISIONS 124: a receiver marker with values beside it stands for // instances of its classes, whose builtin members are the chain's // (`cv` below reaches Object.prototype): no builtin object's. + // An own data key the object surely holds stops the lookup at the + // object: the chain's end, where a key no link defines reads + // `undefined`, is never reached. Its values stay (a superset). + let defined = present && !self.is_proto_key(key); + let chain = if defined { self.defined_into(dst) } else { dst }; if !(self.is_marker(t) && self.beside_values(t)) { - self.with_b(|m| builtins::tok_load(m, t, key, dst)); + self.with_b(|m| builtins::tok_load(m, t, key, chain)); + } + if matches!(self.toks[t as usize], TokKind::ObjectProto) && !self.is_proto_key(key) { + self.with_b(|m| builtins::intrinsic_proto_own(m, key, dst)); } // Element reads of array-likes stop at their own elements: their // builtin prototype members are approximated by `tok_load`. @@ -120,7 +204,16 @@ impl Model { } // The prototype chain's values, shared by every receiver of `t`. let cv = self.cv(t, key); - self.edge(cv, dst); + if defined { + // A rule on the chain node, not an edge into a relay: an + // observation node feeds only observation nodes (`edge`), and + // `sum_skip` applies that to this rule by its `dst`. + if self.defined_done.insert((cv, dst)) { + self.cons(cv, Cons::Defined { dst }); + } + } else { + self.edge(cv, dst); + } // Accessors run with the receiver as `this`: walk per receiver, only // for keys an accessor may be defined under. self.accessor_walk(origin, t, key, ChainAct::Load(dst)); @@ -364,6 +457,18 @@ impl Model { n } + /// The node whose values reach `dst` without `undefined` (`Cons::Defined`): + /// `tok_load` writes into it (no edge, so an observation `dst` is fine). + fn defined_into(&mut self, dst: NodeId) -> NodeId { + if let Some(&n) = self.defined_via.get(&dst) { + return n; + } + let n = self.node_l("defined"); + self.defined_via.insert(dst, n); + self.cons(n, Cons::Defined { dst }); + n + } + /// Every prototype of `t`, transitively. pub fn cp(&mut self, t: Tok) -> NodeId { if let Some(&n) = self.cp_nodes.get(&t) { diff --git a/crates/perry-wpfacts/src/summary.rs b/crates/perry-wpfacts/src/summary.rs index e50fd2952a2..12b959f12e6 100644 --- a/crates/perry-wpfacts/src/summary.rs +++ b/crates/perry-wpfacts/src/summary.rs @@ -293,6 +293,12 @@ pub fn map_cons( this, }, OverrideThis { tmpl, key, reason } => OverrideThis { tmpl, key, reason }, + Guard { test, from, dst } => Guard { + test, + from: n(from), + dst: n(dst), + }, + Defined { dst } => Defined { dst: n(dst) }, TopIfAny { dst, from, reason } => TopIfAny { dst: n(dst), from: n(from), @@ -523,6 +529,7 @@ fn gen_kind(c: &Cons) -> bool { | ContractThis { .. } | OverrideThis { .. } | TopIfAny { .. } + | Guard { .. } | Hard { .. } | DeleteKey { .. } | DefineKey { .. } @@ -579,7 +586,9 @@ impl Model { } if self.nodes[n as usize].obs { let dst = match *c { - Cons::TopIfAny { dst, .. } => Some(dst), + Cons::TopIfAny { dst, .. } | Cons::Guard { dst, .. } | Cons::Defined { dst } => { + Some(dst) + } _ => read_dst(c), }; return !dst.is_some_and(|d| self.nodes[d as usize].obs); diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index 3c0fd55d948..67907cd9273 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -19,6 +19,8 @@ mod cjs_require; mod class_statics; mod closed_world; mod closure_context; +mod ctor_return; +mod dead_dynamic_import; mod diagnostics; mod dynamic_code; mod dynamic_import; @@ -54,7 +56,6 @@ mod super_heritage; mod unit_alias; mod unit_cjs; mod ctor_observed; -mod ctor_return; mod unit_ctor; mod unit_effects; mod unit_extends; diff --git a/crates/perry-wpfacts/src/tests/dead_dynamic_import.rs b/crates/perry-wpfacts/src/tests/dead_dynamic_import.rs new file mode 100644 index 00000000000..8b2735d0996 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/dead_dynamic_import.rs @@ -0,0 +1,320 @@ +//! A dynamic `import()` held only by a body that cannot run (DCE layer 2). +//! `defaultFormat` is the destructuring default of `generateTypescript` +//! options: every caller passes `format`, so the default is never taken, no +//! body calls `defaultFormat`, `loadFmt` never runs and the `import()` of the +//! formatter never happens (postgrest-typegen and oxfmt in the Supabase CLI). +//! Each arm that can take the default keeps the import live. + +use super::*; + +const FMT: &str = r#" +export async function format(name: string, code: string, opts: any) { return { code: "F:" + code, errors: [] as any[] }; } +"#; + +const GEN: &str = r#" +const loadFmt = async () => { + try { return await import("./fmtlib.js"); } + catch (error) { throw new Error("not installed", { cause: error }); } +}; +const defaultFormat = async (code: string) => { + const { format } = await loadFmt(); + const { code: formatted } = await format("output.ts", code, { semi: false }); + return formatted; +}; +export const generateTypescript = async (metadata: any, opts: any = {}) => { + const { detect = false, defaultSchema = "public", format = defaultFormat } = opts; + let output = "types for " + metadata.name + " " + defaultSchema + " " + detect; + output = await format(output); + return output; +}; +"#; + +const LANG: &str = r#" +import { generateTypescript } from "./gen"; +export const typescript = { + name: "typescript", + async generate(metadata: any, values: any, host: any) { + const { format } = host; + return generateTypescript(metadata, { + detect: values.detect === true, + defaultSchema: values.schema as string, + ...(format ? { format: (code: string) => format(code, "output.ts") } : {}), + }); + }, +}; +"#; + +fn main_with(host: &str) -> String { + format!( + r#" +import {{ typescript }} from "./lang"; +const host = {host}; +typescript.generate({{ name: "db" }}, {{ detect: process.argv.length > 5, schema: "public" }}, host).then((s: string) => console.log(s)); +"# + ) +} + +/// The program as the compile driver hands it over: the `import()` site's +/// `paths` filled, its target a dynamic import entry, relative sources +/// resolved to `.ts`. +fn hirs(main: &str) -> Vec<(String, perry_hir::Module)> { + [ + ("fmtlib", FMT), + ("gen", GEN), + ("lang", LANG), + ("main", main), + ] + .iter() + .map(|(name, src)| { + let path = format!("{name}.ts"); + let module = perry_parser::parse_typescript(src, &path).expect("parses"); + let mut hir = perry_hir::lower_module(&module, name, &path).expect("lowers"); + let mut sites = 0; + perry_hir::for_each_dynamic_import_mut(&mut hir, &mut |e| { + if let perry_hir::Expr::DynamicImport { paths: p, .. } = e { + *p = vec!["./fmtlib.js".to_string()]; + sites += 1; + } + }); + if sites > 0 { + hir.imports.push(perry_hir::Import { + source: "./fmtlib.js".to_string(), + specifiers: Vec::new(), + is_native: false, + module_kind: Default::default(), + resolved_path: None, + type_only: false, + runtime_erased: false, + is_dynamic: true, + is_dynamic_target: false, + is_deferred_require: false, + is_adopted_require: false, + }); + } + for imp in &mut hir.imports { + if let Some(n) = imp.source.strip_prefix("./") { + imp.resolved_path = Some(format!("{}.ts", n.trim_end_matches(".js"))); + } + } + (path, hir) + }) + .collect() +} + +/// Is the formatter's `import()` held by a body that can run? +fn imports_formatter(host: &str) -> bool { + let hirs = hirs(&main_with(host)); + let list: Vec<(String, &perry_hir::Module)> = + hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let live = crate::live_dynamic_imports(&list, &FactsConfig::default()).expect("in bound"); + live.contains(&("gen.ts".to_string(), "./fmtlib.js".to_string())) +} + +const CLI_HOST: &str = + "{ cwd: \"/\", format: (code: string, _n: string) => Promise.resolve(code) }"; + +#[test] +fn a_default_every_caller_overrides_never_imports() { + assert!(!imports_formatter(CLI_HOST)); + // The whole program agrees: neither `loadFmt` nor `defaultFormat` nor + // the formatter can run. + let hirs = hirs(&main_with(CLI_HOST)); + let list: Vec<(String, &perry_hir::Module)> = + hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let pf = crate::compute(&list, &FactsConfig::default()); + let on = pf.active_bodies.as_ref().expect("activated"); + for (path, id) in [("gen.ts", 0), ("gen.ts", 1), ("fmtlib.ts", 0)] { + assert!( + !on.iter().any(|(p, i, _)| p == path && *i == id), + "{path} fn {id} active" + ); + } +} + +#[test] +fn every_way_the_default_can_be_taken_imports() { + for host in [ + // No `format` at all. + "{ cwd: \"/\" }", + // `format` may be `undefined`. + "{ cwd: \"/\", format: process.argv.length > 9 ? (c: string) => Promise.resolve(c) : undefined }", + // `format` may be falsy (the spread copies `{}`). + "{ cwd: \"/\", format: process.argv.length > 9 ? (c: string) => Promise.resolve(c) : 0 }", + // The key is deleted before the call. + "(() => { const h: any = { cwd: \"/\", format: (c: string) => Promise.resolve(c) }; if (process.argv.length > 9) delete h.format; return h; })()", + // The host comes from unknown code. + "(globalThis as any)[\"ho\" + \"st\"]", + ] { + assert!(imports_formatter(host), "{host}"); + } +} + +/// Is `oxmark` able to run in `src`? +fn oxmark_runs(src: &str) -> bool { + let src = format!("function oxmark(c: string) {{ return \"OX\" + c; }}\nconst d = (c: string) => oxmark(c);\n{src}"); + let m = perry_parser::parse_typescript(&src, "p.ts").expect("parses"); + let hir = perry_hir::lower_module(&m, "p", "p.ts").expect("lowers"); + let pf = crate::compute(&[("p.ts".to_string(), &hir)], &FactsConfig::default()); + let ox = hir + .functions + .iter() + .find(|f| f.name == "oxmark") + .expect("oxmark") + .id; + pf.active_bodies + .as_ref() + .expect("activated") + .iter() + .any(|(_, id, _)| *id == ox) +} + +#[test] +fn a_branch_runs_only_when_its_test_can_select_it() { + let call = "console.log(g({ format: (c: string) => c }));"; + for (src, runs) in [ + // Conditionals, logical operators, destructuring and parameter + // defaults over a value that is always a function. + ("const g = (o: any) => { const f = o.format; return (f === undefined ? d : f)(\"x\"); };", false), + ("const g = (o: any) => { const { format = d } = o; return format(\"x\"); };", false), + ("const g = async (o: any = {}) => { const { format = d } = o; return await format(\"x\"); };", false), + ("const g = (o: any) => (o.format || d)(\"x\");", false), + ("const g = (o: any) => (o.format ?? d)(\"x\");", false), + ("const g = (o: any) => { let f = o.format; if (f === undefined) { f = d; } return f(\"x\"); };", false), + // Each test that can select the branch. + ("const g = (o: any) => (o.missing || d)(\"x\");", true), + ("const g = (o: any) => (o.format && d)(\"x\");", true), + ("const g = (o: any) => { let f = o.missing; if (f === undefined) { f = d; } return f(\"x\"); };", true), + ("const g = (o: any) => { let f = o.format; if (f !== undefined) { f = d; } return f(\"x\"); };", true), + ] { + assert_eq!(oxmark_runs(&format!("{src}\n{call}")), runs, "{src}"); + } + // A default the caller omits, an option a spread may not copy. + assert!(oxmark_runs( + "const g = (o: any = { format: d }) => o.format(\"x\"); console.log(g());" + )); + assert!(oxmark_runs( + "const g = (o: any) => { const { format = d } = o; return format(\"x\"); };\nconst k: any = process.argv.length > 9 ? { format: (c: string) => c } : {};\nconsole.log(g({ ...k }));" + )); +} + +/// Modules `(name, source)`, every relative `import()` site filled with its +/// specifier and its target a dynamic import entry, as the driver hands them. +fn program_of(mods: &[(&str, &str)]) -> Vec<(String, perry_hir::Module)> { + mods.iter() + .map(|(name, src)| { + let path = format!("{name}.ts"); + let module = perry_parser::parse_typescript(src, &path).expect("parses"); + let mut hir = perry_hir::lower_module(&module, name, &path).expect("lowers"); + let mut specs = Vec::new(); + perry_hir::for_each_dynamic_import_mut(&mut hir, &mut |e| { + if let perry_hir::Expr::DynamicImport { paths, arg, .. } = e { + if let perry_hir::Expr::String(s) = arg.as_ref() { + *paths = vec![s.clone()]; + specs.push(s.clone()); + } + } + }); + for s in specs { + hir.imports.push(perry_hir::Import { + source: s, + specifiers: Vec::new(), + is_native: false, + module_kind: Default::default(), + resolved_path: None, + type_only: false, + runtime_erased: false, + is_dynamic: true, + is_dynamic_target: false, + is_deferred_require: false, + is_adopted_require: false, + }); + } + for imp in &mut hir.imports { + if let Some(n) = imp.source.strip_prefix("./") { + imp.resolved_path = Some(format!("{}.ts", n.trim_end_matches(".js"))); + } + } + (path, hir) + }) + .collect() +} + +const LAZY: &str = r#" +// Its body runs only when it loads; it writes a computed key of `Object`. +(Object as any)[(globalThis as any)["k" + "ey"]] = (x: any) => x; +export const v = 1; +"#; + +#[test] +fn a_module_only_an_uncalled_body_imports_never_runs() { + for (main, loads) in [ + ("const never = () => import(\"./lazy.js\");\nconsole.log([1, 2].map((x: number) => x + 1), typeof never);", false), + ("const now = () => import(\"./lazy.js\");\nnow().then((m: any) => console.log(m.v, [1].map((x: number) => x)));", true), + ] { + let hirs = program_of(&[("lazy", LAZY), ("main", main)]); + let list: Vec<(String, &perry_hir::Module)> = hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + let live = crate::live_dynamic_imports(&list, &FactsConfig::default()).expect("in bound"); + assert_eq!(live.contains(&("main.ts".to_string(), "./lazy.js".to_string())), loads, "{main}"); + let pf = crate::compute(&list, &FactsConfig::default()); + let on = pf.active_bodies.as_ref().expect("activated"); + let init = on.iter().any(|(p, id, name)| p == "lazy.ts" && *id == u32::MAX && name.ends_with(":")); + assert_eq!(init, loads, "{main}"); + } +} + +#[test] +fn a_root_runs_whatever_imports_it() { + // A worker entry: the driver's dynamic edge names it, no `import()` site + // does; its body runs in the worker (`FactsConfig::roots`). + let worker = "(Object as any)[(globalThis as any)[\"k\" + \"ey\"]] = 1; export const w = 1;"; + let mut hirs = program_of(&[ + ("lazy", worker), + ("main", "console.log([1].map((x: number) => x));"), + ]); + hirs[1].1.imports.push(perry_hir::Import { + source: "./lazy.ts".to_string(), + specifiers: Vec::new(), + is_native: false, + module_kind: Default::default(), + resolved_path: Some("lazy.ts".to_string()), + type_only: false, + runtime_erased: false, + is_dynamic: true, + is_dynamic_target: false, + is_deferred_require: false, + is_adopted_require: false, + }); + let list: Vec<(String, &perry_hir::Module)> = + hirs.iter().map(|(p, h)| (p.clone(), h)).collect(); + for (roots, runs) in [(vec![], false), (vec!["lazy.ts".to_string()], true)] { + let cfg = FactsConfig { + roots, + ..FactsConfig::default() + }; + let pf = crate::compute(&list, &cfg); + let on = pf.active_bodies.as_ref().expect("activated"); + let init = on + .iter() + .any(|(p, id, name)| p == "lazy.ts" && *id == u32::MAX && name.ends_with(":")); + assert_eq!(init, runs); + } +} + +/// `X.prototype.method` read as a value is a builtin function (the token +/// stands for every builtin prototype): a callback given to it through +/// `call`, `bind` or a call-bound helper runs (get-intrinsic's +/// `stringToPath`: `$replace = callBind(String.prototype.replace)`). +#[test] +fn a_callback_given_through_a_builtin_prototype_method_value_runs() { + for src in [ + "const $replace: any = Function.prototype.call.bind(String.prototype.replace);\nconst r: any[] = [];\n$replace(\"aa\", /a/g, (m: any) => { r[r.length] = d; return m; });\nconsole.log(r[0](\"x\"));", + "const $call: any = Function.prototype.call;\nconst r: any[] = [];\n$call.call(String.prototype.replace, \"aa\", /a/g, (m: any) => { r[r.length] = d; return m; });\nconsole.log(r[0](\"x\"));", + "const t: any = String.prototype.replace;\nt.call(\"aa\", /a/g, d);", + "const $replace: any = String.prototype.replace.bind(\"aa\");\n$replace(/a/g, d);", + // The callback's parameters are the native's: a guard over them + // lets either branch through. + "const $replace: any = Function.prototype.call.bind(String.prototype.replace);\nconst r: any[] = [];\n$replace(\"aa\", /a/g, (m: any, n: any) => { r[r.length] = n ? d : d; return m; });\nconsole.log(r[0](\"x\"));", + ] { + assert!(oxmark_runs(src), "{src}"); + } +} diff --git a/crates/perry-wpfacts/src/tests/unit_ctor.rs b/crates/perry-wpfacts/src/tests/unit_ctor.rs index 5b243362f27..dfc03bba51d 100644 --- a/crates/perry-wpfacts/src/tests/unit_ctor.rs +++ b/crates/perry-wpfacts/src/tests/unit_ctor.rs @@ -93,7 +93,15 @@ fn a_constructor_fed_its_own_instances_keeps_its_class() { .collect() }; assert!(held(&pf).len() >= 3, "{:?}", held(&pf)); - assert_eq!(held(&pf), held(&whole), "{}", pf.dump_text()); + // DCE: the whole program reads `left`/`right` as the own keys every + // Node surely holds (no prototype read, no `undefined`: null or Node); + // per unit the readers still see `undefined` beside them, never less + // than the whole program. + let (u, w) = (held(&pf), held(&whole)); + assert_eq!(u.len(), w.len(), "{u:?} {w:?}"); + for (a, b) in u.iter().zip(&w) { + assert!(a == b || a.replace("prims=24", "prims=8") == *b, "{a} {b}"); + } } #[test] @@ -746,7 +754,15 @@ fn a_constructor_argument_the_receiver_reaches_stays_the_callers() { } // Soundness: Res's unit sees the caller's Val in `v`, as the program. let (u, w) = (field(tmpl(&pf, "Res"), "v"), field(tmpl(&wf, "Res"), "v")); - assert_eq!(u.kind, w.kind, "{}", pf.dump_text()); + // DCE: the whole program reads `v` as the own key every Res surely + // holds (no `undefined`); per unit it reads `undefined` beside it. + assert_eq!(u.kind.objs, w.kind.objs, "{}", pf.dump_text()); + assert_eq!( + u.kind.prims & !crate::model::prim::UNDEF, + w.kind.prims, + "{}", + pf.dump_text() + ); } const PUB_CTOR: &str = r#" diff --git a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs index ea7193b0b94..08226ebfabd 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs @@ -315,8 +315,11 @@ pub(super) fn a_broken_namespace_field_function_resolves_only_the_units_that_see // round 2 builds what the readers held as ⊤, and the fixpoint ends one // round earlier. A method called on a marker with values beside it // reads its key's reach (`method_on`): [754, 1090, 948, 42] -> the pin - // below. - assert_eq!(pf.round_work, [758, 1094, 952, 42], "{:?}", pf.units); + // below. DCE: a read of a key the object surely holds takes its chain + // through a relay that drops the chain end's `undefined` + // (`Cons::Defined`): a relay node for the read's own builtin members + // and a rule on the chain node, [758, 1094, 952, 42] -> the pin below. + assert_eq!(pf.round_work, [762, 1104, 960, 42], "{:?}", pf.units); } #[test] diff --git a/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs b/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs index 5f6869f7d72..604f8699dab 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs @@ -753,7 +753,10 @@ pub(super) fn an_escaped_value_beside_top_is_that_top() { (e8, e16, e32), // DEC221 removes four entries from the redundant wildcard escape // at each n; the existing quadratic storage behavior is unchanged. - (1064, 2944, 9392), + // DCE: `o.l{i} || (o.l{i} = ...)` joins its right operand when the + // left one can be falsy (here ⊤), through an edge the solve makes, + // which takes no shared set: two entries per literal. + (1080, 2976, 9456), "entries {e8} {e16} {e32}" ); } diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index e01dddc4d9c..687d2c6bd9e 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -37,6 +37,7 @@ mod collect_modules; mod collect_modules_helpers; mod component_host; mod compressed_libs; +mod dynamic_reach; mod embed; mod env_fold; mod export_analysis; diff --git a/crates/perry/src/commands/compile/compile_env.rs b/crates/perry/src/commands/compile/compile_env.rs index 7fcf3ac0b67..8999fa3e92d 100644 --- a/crates/perry/src/commands/compile/compile_env.rs +++ b/crates/perry/src/commands/compile/compile_env.rs @@ -46,6 +46,8 @@ pub(super) const COMPILE_ENV_VARS: &[&str] = &[ "PERRY_DISABLE_BUFFER_FAST_PATH", "PERRY_DISABLE_WELL_KNOWN", "PERRY_DYNAMIC_ADD_PAIR_GUARD", + "PERRY_DYNAMIC_REACH", + "PERRY_DYNAMIC_REACH_MAX_WORK", "PERRY_ENTRY_SYMBOL", "PERRY_ENUM_DIAG", "PERRY_EVAL_CSP", @@ -230,6 +232,7 @@ pub(super) fn compile_env_value(name: &str) -> Option { #[cfg(test)] const COMPILE_ENV_EXCLUSIONS: &[(&str, &str)] = &[ + ("PERRY_DYNAMIC_REACH_REPORT", "Writes the dynamic-reach report file only; no pruning decision or emitted code depends on it."), ("PERRY_EXPORT_DEMAND_REPORT", "Writes the export-demand report file only; no pruning decision or emitted code depends on it."), ("PERRY_REALM_REACH", "Compiler-only diagnostic of source facts; changes no emitted bytes, runtime state or archive selection."), ("PERRY_REACH_REPORT", "Compiler-only diagnostic; bypasses object caches and does not change emitted code."), diff --git a/crates/perry/src/commands/compile/dynamic_reach.rs b/crates/perry/src/commands/compile/dynamic_reach.rs new file mode 100644 index 00000000000..4fa380fc43b --- /dev/null +++ b/crates/perry/src/commands/compile/dynamic_reach.rs @@ -0,0 +1,191 @@ +//! Dead-code elimination, layer 2 at module level: a module that only +//! dynamic `import()` / `require(expr)` sites reach, each held by a body that +//! cannot run (the whole program's activation pass, DECISIONS 225), is never +//! loaded. It leaves the graph right after collection, with every module only +//! it reaches, so neither facts nor codegen see it. A site whose body cannot +//! run keeps the specifiers of the modules that stay; one left with none +//! lowers to the runtime fallback, as an unresolved specifier does. +//! +//! The roots are the entry, the bundle extension entries and every module no +//! edge enters (workers and the like). Static imports, re-exports and the +//! hoisted requires of CommonJS stay live edges. +//! +//! `PERRY_DYNAMIC_REACH=off` keeps every module (A/B measurement); +//! `PERRY_DYNAMIC_REACH_REPORT=` lists the modules dropped. + +use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::path::PathBuf; + +use anyhow::Result; + +use super::CompilationContext; + +pub(super) fn prune(ctx: &mut CompilationContext, roots: &[PathBuf]) -> Result<()> { + if matches!( + std::env::var("PERRY_DYNAMIC_REACH").as_deref(), + Ok("off" | "0" | "false" | "no") + ) { + return Ok(()); + } + let any_dynamic = ctx.native_modules.values().any(|m| { + m.imports + .iter() + .any(|i| i.is_dynamic || i.is_dynamic_target) + }); + if !any_dynamic { + return Ok(()); + } + let started = std::time::Instant::now(); + let dynamic_code = perry_hir::has_deferred_dynamic_code_sites() + || ctx.native_module_imports.contains("vm") + || ctx.uses_data_url_dynamic_import; + let Some(live) = super::program_facts::live_dynamic_imports( + &ctx.native_modules, + super::program_facts::export_sources(ctx), + dynamic_code, + &ctx.patched_builtins.dynamic_code_reach, + roots.iter().map(|p| p.display().to_string()).collect(), + ) else { + eprintln!( + "perry: dynamic reach: the activation pass ran past its bounds, nothing dropped ({:.1}s)", + started.elapsed().as_secs_f64() + ); + return Ok(()); + }; + let report = std::env::var_os("PERRY_DYNAMIC_REACH_REPORT"); + let mut lines: Vec = Vec::new(); + let paths: Vec = ctx.native_modules.keys().cloned().collect(); + let key_set: HashSet = paths.iter().cloned().collect(); + // Edges: (target, live). + let mut edges: HashMap> = HashMap::new(); + let mut entered: HashSet = HashSet::new(); + for p in &paths { + let (imports, exports) = { + let m = &ctx.native_modules[p]; + (m.imports.clone(), m.exports.clone()) + }; + let name = p.display().to_string(); + let mut out = Vec::new(); + for i in &imports { + if i.type_only || i.runtime_erased { + continue; + } + let Some(t) = super::reachability::resolve_edge( + ctx, + p, + i.resolved_path.as_deref(), + &i.source, + &key_set, + ) else { + continue; + }; + // A synthesized `import()` edge lives when a site a body that + // can run holds names its specifier. + let alive = !i.is_dynamic || live.contains(&(name.clone(), i.source.clone())); + if i.is_dynamic && report.is_some() { + lines.push(format!( + "edge {} {name} -> {}", + if alive { "live" } else { "dead" }, + i.source + )); + } + entered.insert(t.clone()); + out.push((t, alive)); + } + for e in &exports { + let source = match e { + perry_hir::Export::ReExport { source, .. } + | perry_hir::Export::ExportAll { source } + | perry_hir::Export::NamespaceReExport { source, .. } => source, + perry_hir::Export::Named { .. } => continue, + }; + if let Some(t) = super::reachability::resolve_edge(ctx, p, None, source, &key_set) { + entered.insert(t.clone()); + out.push((t, true)); + } + } + edges.insert(p.clone(), out); + } + let mut reached: HashSet = HashSet::new(); + let mut work: VecDeque = VecDeque::new(); + for p in &paths { + if roots.contains(p) || !entered.contains(p) { + reached.insert(p.clone()); + work.push_back(p.clone()); + } + } + while let Some(p) = work.pop_front() { + for (t, alive) in edges.get(&p).into_iter().flatten() { + if *alive && reached.insert(t.clone()) { + work.push_back(t.clone()); + } + } + } + let pruned: HashSet = paths + .iter() + .filter(|p| !reached.contains(*p)) + .cloned() + .collect(); + let dead_edges = lines.iter().filter(|l| l.starts_with("edge dead")).count(); + eprintln!( + "perry: dynamic reach: {} dynamic edges, {dead_edges} held only by bodies that cannot run; {} modules dropped ({:.1}s)", + lines.len(), + pruned.len(), + started.elapsed().as_secs_f64() + ); + if pruned.is_empty() { + if let Some(path) = &report { + lines.sort(); + std::fs::write(path, lines.join("\n") + "\n")?; + } + return Ok(()); + } + // Sites (in bodies that cannot run) keep only specifiers of modules that + // stay. + let dropped_sources: HashMap> = paths + .iter() + .filter(|p| !pruned.contains(*p)) + .map(|p| { + let m = &ctx.native_modules[p]; + let gone = m + .imports + .iter() + .filter(|i| { + i.is_dynamic + && super::reachability::resolve_edge_cached( + &key_set, + i.resolved_path.as_deref(), + ) + .is_some_and(|t| pruned.contains(&t)) + }) + .map(|i| i.source.clone()) + .collect(); + (p.clone(), gone) + }) + .collect(); + super::reachability::rewrite_pruned_edges(ctx, &pruned); + for (p, gone) in &dropped_sources { + if gone.is_empty() { + continue; + } + if let Some(m) = ctx.native_modules.get_mut(p) { + perry_hir::for_each_dynamic_import_mut(m, &mut |e| { + if let perry_hir::Expr::DynamicImport { paths, .. } = e { + paths.retain(|s| !gone.contains(s)); + } + }); + } + } + let mut list: Vec = pruned + .iter() + .map(|p| format!("dropped {}", p.display())) + .collect(); + list.sort(); + if let Some(path) = &report { + lines.sort(); + lines.extend(list); + std::fs::write(path, lines.join("\n") + "\n")?; + } + ctx.native_modules.retain(|p, _| !pruned.contains(p)); + Ok(()) +} diff --git a/crates/perry/src/commands/compile/program_facts.rs b/crates/perry/src/commands/compile/program_facts.rs index 8fb822d0718..502dbf1799d 100644 --- a/crates/perry/src/commands/compile/program_facts.rs +++ b/crates/perry/src/commands/compile/program_facts.rs @@ -54,6 +54,20 @@ pub(crate) fn contracts_requested() -> bool { /// collection and init order resolve them, for the targets that are program /// modules. The HIR keeps a re-export's source as written; without this the /// analysis cannot link `export * as X from "./x"` to `./x`'s namespace. +/// The modules the program loads whatever imports them (worker entries): +/// `FactsConfig::roots`. +pub(crate) fn module_roots(ctx: &super::CompilationContext) -> Vec { + ctx.worker_url_entries + .iter() + .flat_map(|(lexical, canonical)| { + [ + lexical.display().to_string(), + canonical.display().to_string(), + ] + }) + .collect() +} + pub(crate) fn export_sources(ctx: &super::CompilationContext) -> perry_wpfacts::ExportSources { let mut out = perry_wpfacts::ExportSources::new(); for (path, module) in &ctx.native_modules { @@ -84,6 +98,7 @@ pub(crate) fn run( dynamic_code: bool, dynamic_code_reach: &perry_hir::dynamic_code::DynamicCodeReach, wrapped: &std::collections::HashMap, + roots: Vec, ) -> Option> { if !perry_wpfacts::enabled_from_env() { return None; @@ -93,6 +108,7 @@ pub(crate) fn run( let relations = dump.is_some() && std::env::var("PERRY_FACTS_RELATIONS").is_ok_and(|v| v == "1"); let cfg = perry_wpfacts::FactsConfig { + roots, diagnostics: dump.is_some(), relations, sabotage: std::env::var("PERRY_FACTS_SABOTAGE") @@ -531,6 +547,58 @@ mod outlined_tests; mod literal_chunks_tests; /// Borrow ordinary modules; reconstruct only outlined module entries. +/// Dead-code elimination (`dynamic_reach`): the dynamic `import()` / +/// `require(expr)` specifiers a body that can run holds, by the whole +/// program's activation pass over `modules`, as (module path, specifier). +/// `None` when facts are off or past their bounds (every site may run). +pub(crate) fn live_dynamic_imports( + modules: &BTreeMap, + export_sources: perry_wpfacts::ExportSources, + dynamic_code: bool, + dynamic_code_reach: &perry_hir::dynamic_code::DynamicCodeReach, + roots: Vec, +) -> Option> { + if !perry_wpfacts::enabled_from_env() { + return None; + } + let cfg = perry_wpfacts::FactsConfig { + roots, + budget_secs: std::env::var("PERRY_FACTS_BUDGET_SECS") + .ok() + .and_then(|v| v.parse().ok()) + .filter(|&s| s != 0), + // The activation pass alone, over the whole program: a larger bound + // than the per-unit facts' (the Supabase CLI needs ~20x the default). + max_work: Some( + std::env::var("PERRY_DYNAMIC_REACH_MAX_WORK") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(DEFAULT_MAX_WORK * 100), + ), + max_pts: Some( + std::env::var("PERRY_FACTS_MAX_PTS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(400_000_000), + ), + export_sources, + dynamic_code_reach: dynamic_code_reach.clone(), + dynamic_code: dynamic_code.then(|| { + "E14 the program can run interpreted code (deferred new Function/eval or node:vm)" + .into() + }), + unit: false, + jobs: perry_codegen::workers::unit_workers(), + ..Default::default() + }; + let inputs = facts_inputs(modules); + let list: Vec<(String, &perry_hir::Module)> = inputs + .iter() + .map(|(p, m)| (p.clone(), m.as_ref())) + .collect(); + perry_wpfacts::live_dynamic_imports(&list, &cfg) +} + fn facts_inputs( modules: &BTreeMap, ) -> Vec<(String, std::borrow::Cow<'_, perry_hir::Module>)> { diff --git a/crates/perry/src/commands/compile/reachability.rs b/crates/perry/src/commands/compile/reachability.rs index b9073918ddd..01c03e609f7 100644 --- a/crates/perry/src/commands/compile/reachability.rs +++ b/crates/perry/src/commands/compile/reachability.rs @@ -424,7 +424,7 @@ fn follow_import_local( // Edge rewriting (drop references to pruned modules from survivors) // --------------------------------------------------------------------------- -fn rewrite_pruned_edges(ctx: &mut CompilationContext, pruned: &HashSet) { +pub(super) fn rewrite_pruned_edges(ctx: &mut CompilationContext, pruned: &HashSet) { let surviving: Vec = ctx .native_modules .keys() @@ -808,3 +808,14 @@ mod tests { ); } } + +/// The graph module a resolved import path names, if any (no resolver call). +pub(super) fn resolve_edge_cached( + key_set: &HashSet, + resolved_path: Option<&str>, +) -> Option { + let rp = resolved_path?; + let canon = PathBuf::from(rp); + let canon = canon.canonicalize().unwrap_or(canon); + key_set.contains(&canon).then_some(canon) +} diff --git a/crates/perry/src/commands/compile/run_pipeline.rs b/crates/perry/src/commands/compile/run_pipeline.rs index 51e8e150377..1a836ca247a 100644 --- a/crates/perry/src/commands/compile/run_pipeline.rs +++ b/crates/perry/src/commands/compile/run_pipeline.rs @@ -1073,6 +1073,19 @@ pub fn run_with_parse_cache( .unwrap_or_else(|_| args.input.clone()) }); reachability::tree_shake(&mut ctx, &entry_canonical)?; + // DCE layer 2: modules only unreachable `import()` sites load. + let mut roots = vec![entry_canonical.clone()]; + roots.extend( + ctx.worker_url_entries + .iter() + .flat_map(|(l, c)| [l.clone(), c.clone()]), + ); + if let Some(ext_dir) = &args.bundle_extensions { + for (entry_path, _) in super::resolve::discover_extension_entries(ext_dir)? { + roots.push(entry_path.canonicalize().unwrap_or(entry_path)); + } + } + super::dynamic_reach::prune(&mut ctx, &roots)?; } // --- Web/WASM target: emit WASM binary + JS runtime bridge --- @@ -6549,6 +6562,7 @@ pub fn run_with_parse_cache( || ctx.uses_data_url_dynamic_import, &ctx.patched_builtins.dynamic_code_reach, &ctx.cjs_wrap_debug_sources, + super::program_facts::module_roots(&ctx), ); program_step("whole-program facts", Some(facts_started)); #[allow(clippy::type_complexity)] From b163c51bcd0b3904e88749a624815b88c08e39a8 Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:43:18 +0100 Subject: [PATCH 087/144] changelog: record the CompileOptions test-literal repair --- changelog.d/13631-compile-options-test-literals.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13631-compile-options-test-literals.md diff --git a/changelog.d/13631-compile-options-test-literals.md b/changelog.d/13631-compile-options-test-literals.md new file mode 100644 index 00000000000..803d29739dd --- /dev/null +++ b/changelog.d/13631-compile-options-test-literals.md @@ -0,0 +1 @@ +**`main` compiles its test targets again after #13622.** That PR added `CompileOptions::namespace_self_import`, and the field is required in a struct literal, but the fixtures across `perry-codegen`'s and `perry`'s test targets name their fields explicitly instead of spreading `Default` — so `cargo check -p perry-codegen --tests` and `cargo check -p perry --tests` stopped compiling on `main` (42 sites). Each one now sets `namespace_self_import: false`, which is what every fixture means: the flag is false in every test path, exactly as a defaulted field would be, so there is no behaviour change. The regression was invisible to the checks #13622 ran (`cargo check -p perry --bins` and the gap harness do not build the test targets), which is why the repair is verified by exit code this time: both commands exit 0. From b9de6d49d1a54861feeef87f32cfeca2c0fcc7c0 Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:11:41 +0100 Subject: [PATCH 088/144] fix(worker_threads): share receiver-owned MessagePort operations --- .../unreleased-worker-port-receivers.md | 3 + .../perry-codegen/src/wasm32/runtime_abi.tsv | 1 + crates/perry-runtime/src/messaging.rs | 91 ++------ .../src/messaging/port_operations.rs | 158 ++++++++++++++ .../src/messaging/port_operations_tests.rs | 52 +++++ .../perry-runtime/src/object/field_get_set.rs | 1 - .../ic_miss/private_storage_cache.rs | 1 - .../src/object/runtime_layouts.rs | 1 + .../perry-stdlib/src/common/dispatch/init.rs | 3 + crates/perry-stdlib/src/worker_threads.rs | 7 +- .../src/worker_threads/message_port.rs | 197 ++++++------------ .../message_port_receiver_tests.rs | 41 ++++ .../src/worker_threads/parent_port_tests.rs | 5 +- docs/src/threading/overview.md | 6 + .../message-port/receiver-operations.ts | 61 ++++++ 15 files changed, 417 insertions(+), 211 deletions(-) create mode 100644 changelog.d/unreleased-worker-port-receivers.md create mode 100644 crates/perry-runtime/src/messaging/port_operations.rs create mode 100644 crates/perry-runtime/src/messaging/port_operations_tests.rs create mode 100644 crates/perry-stdlib/src/worker_threads/message_port_receiver_tests.rs create mode 100644 test-parity/node-suite/worker_threads/message-port/receiver-operations.ts diff --git a/changelog.d/unreleased-worker-port-receivers.md b/changelog.d/unreleased-worker-port-receivers.md new file mode 100644 index 00000000000..52dcb70b85c --- /dev/null +++ b/changelog.d/unreleased-worker-port-receivers.md @@ -0,0 +1,3 @@ +### Fixed + +- Share MessagePort core operations between prototypes and instances, honoring borrowed-method receivers and rejecting invalid receivers before message cloning. Keep port identity in existing intrinsic private storage rather than a mutable public field. diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index 154680e70cb..d68b56ce1e9 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -3612,6 +3612,7 @@ js_register_text_id_handler void ptr js_register_tls_handle_kind_probe void ptr js_register_widget_hidden_handler void ptr js_register_worker_threads_environment_data void ptr,ptr +js_register_worker_threads_message_port_dispatch void ptr js_register_worker_threads_messaging_constructors void ptr,ptr js_register_worker_threads_namespace_getters void ptr,ptr,ptr,ptr,ptr,ptr js_register_worker_threads_worker_constructor void ptr diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index 94136190077..456323c49f1 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -11,6 +11,11 @@ use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::Mutex; +mod port_operations; +pub use port_operations::install_core_methods as install_message_port_core_methods; +pub use port_operations::js_register_worker_threads_message_port_dispatch; +pub use port_operations::{message_port_id, message_port_object_with_id, MessagePortOperation}; + type MessageChannelFactory = extern "C" fn() -> f64; type BroadcastChannelFactory = extern "C" fn(f64) -> f64; @@ -114,19 +119,6 @@ extern "C" fn noop1( js_undefined() } -extern "C" fn noop2( - _closure: *const ClosureHeader, - _this: crate::closure::JsThis, - _arg0: f64, - _arg1: f64, -) -> f64 { - js_undefined() -} - -extern "C" fn has_ref(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - js_bool(false) -} - fn install_method( obj: *mut ObjectHeader, name: &str, @@ -163,6 +155,7 @@ struct PortState { started: bool, /// `.close()` stops further delivery. closed: bool, + refed: bool, /// The current `onmessage` handler value (callable or null/undefined). onmessage: f64, /// `addEventListener("message", fn)` listeners (closure values). @@ -462,7 +455,9 @@ fn start_port(self_ptr: usize) { return; } let pending = with_port_states(|map| { - let state = map.entry(self_ptr).or_default(); + let Some(state) = map.get_mut(&self_ptr) else { + return 0; + }; if state.closed { return 0; } @@ -597,32 +592,7 @@ fn is_message_type(type_value: f64) -> bool { /// Install the delivery-capable method set + onmessage accessor on a port. fn install_port_methods(obj: *mut ObjectHeader) { - install_method( - obj, - "postMessage", - crate::fn_info!(port_post_message, 2; with_declared(2)), - 2, - ); - install_method( - obj, - "start", - crate::fn_info!(port_start, 0; with_declared(0)), - 0, - ); - install_method( - obj, - "close", - crate::fn_info!(port_close, 0; with_declared(0)), - 0, - ); - install_method(obj, "ref", crate::fn_info!(#[realm(realm_decl! { BroadcastChannel.prototype.close, constructible = true; BroadcastChannel.prototype.ref, constructible = true; BroadcastChannel.prototype.unref, constructible = true; MessagePort.prototype.close, constructible = true; MessagePort.prototype.ref, constructible = true; MessagePort.prototype.start, constructible = true; MessagePort.prototype.unref, constructible = true; })] export crate::messaging::noop0, 0; with_declared(0)), 0); - install_method(obj, "unref", crate::fn_info!(noop0, 0; with_declared(0)), 0); - install_method( - obj, - "hasRef", - crate::fn_info!(#[realm(MessagePort.prototype.hasRef, constructible = true)] export crate::messaging::has_ref, 0; with_declared(0)), - 0, - ); + port_operations::install_core_methods(obj); install_method( obj, "addEventListener", @@ -654,9 +624,8 @@ fn install_port_methods(obj: *mut ObjectHeader) { } /// Install the Node-shaped prototype members for the three messaging -/// constructors. The method bodies are intentionally small no-ops here; this -/// slice is about constructor identity and shape, while full message delivery -/// remains worker_threads parity follow-up work. +/// constructors. MessagePort core methods share receiver-owned dispatch with +/// both channel implementations. pub fn populate_messaging_prototype(builtin_name: &str, proto: *mut ObjectHeader, ctor: f64) { if proto.is_null() { return; @@ -670,39 +639,9 @@ pub fn populate_messaging_prototype(builtin_name: &str, proto: *mut ObjectHeader match builtin_name { "MessagePort" => { - install_method( - proto, - "postMessage", - crate::fn_info!(#[realm(MessagePort.prototype.postMessage, constructible = true)] export crate::messaging::noop2, 2; with_declared(2)), - 2, - ); - install_method( - proto, - "start", - crate::fn_info!(noop0, 0; with_declared(0)), - 0, - ); - install_method(proto, "ref", crate::fn_info!(noop0, 0; with_declared(0)), 0); - install_method( - proto, - "unref", - crate::fn_info!(noop0, 0; with_declared(0)), - 0, - ); - install_method( - proto, - "hasRef", - crate::fn_info!(has_ref, 0; with_declared(0)), - 0, - ); + port_operations::install_core_methods(proto); set_field(proto, "onmessage", js_null()); set_field(proto, "onmessageerror", js_null()); - install_method( - proto, - "close", - crate::fn_info!(noop0, 0; with_declared(0)), - 0, - ); } "MessageChannel" => {} "BroadcastChannel" => { @@ -710,7 +649,7 @@ pub fn populate_messaging_prototype(builtin_name: &str, proto: *mut ObjectHeader install_method( proto, "close", - crate::fn_info!(noop0, 0; with_declared(0)), + crate::fn_info!(#[realm(realm_decl! { BroadcastChannel.prototype.close, constructible = true; BroadcastChannel.prototype.ref, constructible = true; BroadcastChannel.prototype.unref, constructible = true; })] export crate::messaging::noop0, 0; with_declared(0)), 0, ); install_method( @@ -734,7 +673,7 @@ pub fn populate_messaging_prototype(builtin_name: &str, proto: *mut ObjectHeader } fn message_port_object() -> *mut ObjectHeader { - let obj = object::js_object_alloc(0, 0); + let obj = message_port_object_with_id(0); let proto = constructor_prototype("MessagePort"); set_object_prototype(obj, proto); set_field(obj, "constructor", get_global_constructor("MessagePort")); diff --git a/crates/perry-runtime/src/messaging/port_operations.rs b/crates/perry-runtime/src/messaging/port_operations.rs new file mode 100644 index 00000000000..69c6f2003f2 --- /dev/null +++ b/crates/perry-runtime/src/messaging/port_operations.rs @@ -0,0 +1,158 @@ +use super::*; +use crate::closure::JsThis; +use crate::feature_hooks::Hook; + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum MessagePortOperation { + PostMessage, + Start, + Close, + Ref, + Unref, + HasRef, +} + +type MessagePortDispatch = + unsafe extern "C" fn(MessagePortOperation, f64, f64, f64, *mut f64) -> bool; +static DISPATCH: Hook = Hook::empty(); +static PORT_ID: object::IntrinsicPrivateReadSite = object::IntrinsicPrivateReadSite::new( + &object::runtime_layouts::intrinsic_private_atoms::MESSAGE_PORT_ID, +); + +#[no_mangle] +pub extern "C" fn js_register_worker_threads_message_port_dispatch(dispatch: MessagePortDispatch) { + DISPATCH.set(dispatch); +} + +/// The private ID remains after close, without retaining the port in a root table. +/// Zero identifies the runtime-only implementation; stdlib IDs start at one. +pub fn message_port_object_with_id(id: u64) -> *mut ObjectHeader { + let scope = crate::gc::RuntimeHandleScope::new(); + let object = scope.root_raw_mut_ptr(object::js_object_alloc(0, 0)); + object::intrinsic_private_add( + crate::value::js_nanbox_pointer(object.get_raw_mut_ptr::() as i64), + "[[MessagePortId]]", + f64::from_bits(id), + ); + object.get_raw_mut_ptr() +} + +pub fn message_port_id(receiver: f64) -> Option { + let value = JSValue::from_bits(receiver.to_bits()); + if !value.is_pointer() { + return None; + } + let address = crate::value::js_nanbox_get_pointer(receiver) as usize; + match unsafe { crate::value::addr_class::try_read_gc_header(address) } { + Some(header) if crate::gc_type_is_object!(header.obj_type) => {} + _ => return None, + } + PORT_ID.read(receiver).map(f64::to_bits) +} + +fn dispatch(operation: MessagePortOperation, this: JsThis, data: f64, transfer: f64) -> f64 { + let receiver = this.as_f64(); + let Some(id) = message_port_id(receiver) else { + invalid_receiver(); + }; + if id != 0 { + if let Some(provider) = DISPATCH.peek() { + let mut result = js_undefined(); + if unsafe { provider(operation, receiver, data, transfer, &mut result) } { + return result; + } + } + invalid_receiver(); + } + let ptr = crate::value::js_nanbox_get_pointer(receiver) as usize; + match operation { + MessagePortOperation::PostMessage => { + port_post_message(std::ptr::null(), this, data, transfer) + } + MessagePortOperation::Start => port_start(std::ptr::null(), this), + MessagePortOperation::Close => port_close(std::ptr::null(), this), + MessagePortOperation::Ref | MessagePortOperation::Unref => { + with_port_states(|map| { + if let Some(state) = map.get_mut(&ptr) { + state.refed = !state.closed && matches!(operation, MessagePortOperation::Ref); + } + }); + js_undefined() + } + MessagePortOperation::HasRef => js_bool(with_port_states(|map| { + map.get(&ptr) + .is_some_and(|state| !state.closed && state.refed) + })), + } +} + +fn invalid_receiver() -> ! { + let message = key("Illegal invocation"); + let error = crate::error::js_typeerror_new(message); + crate::exception::js_throw(crate::value::js_nanbox_pointer(error as i64)) +} + +extern "C" fn post_message( + _closure: *const ClosureHeader, + this: JsThis, + data: f64, + transfer: f64, +) -> f64 { + dispatch(MessagePortOperation::PostMessage, this, data, transfer) +} + +extern "C" fn start(_closure: *const ClosureHeader, this: JsThis) -> f64 { + dispatch( + MessagePortOperation::Start, + this, + js_undefined(), + js_undefined(), + ) +} +extern "C" fn close(_closure: *const ClosureHeader, this: JsThis) -> f64 { + dispatch( + MessagePortOperation::Close, + this, + js_undefined(), + js_undefined(), + ) +} +extern "C" fn ref_port(_closure: *const ClosureHeader, this: JsThis) -> f64 { + dispatch( + MessagePortOperation::Ref, + this, + js_undefined(), + js_undefined(), + ) +} +extern "C" fn unref_port(_closure: *const ClosureHeader, this: JsThis) -> f64 { + dispatch( + MessagePortOperation::Unref, + this, + js_undefined(), + js_undefined(), + ) +} +extern "C" fn has_ref_port(_closure: *const ClosureHeader, this: JsThis) -> f64 { + dispatch( + MessagePortOperation::HasRef, + this, + js_undefined(), + js_undefined(), + ) +} + +pub fn install_core_methods(object: *mut ObjectHeader) { + let _no_move = crate::gc::GcSuppressScope::new(); + install_method(object, "postMessage", crate::fn_info!(#[realm(MessagePort.prototype.postMessage, constructible = true)] export crate::messaging::port_operations::post_message, 2; with_declared(2)), 2); + install_method(object, "start", crate::fn_info!(#[realm(MessagePort.prototype.start, constructible = true)] export crate::messaging::port_operations::start, 0; with_declared(0)), 0); + install_method(object, "close", crate::fn_info!(#[realm(MessagePort.prototype.close, constructible = true)] export crate::messaging::port_operations::close, 0; with_declared(0)), 0); + install_method(object, "ref", crate::fn_info!(#[realm(MessagePort.prototype.ref, constructible = true)] export crate::messaging::port_operations::ref_port, 0; with_declared(0)), 0); + install_method(object, "unref", crate::fn_info!(#[realm(MessagePort.prototype.unref, constructible = true)] export crate::messaging::port_operations::unref_port, 0; with_declared(0)), 0); + install_method(object, "hasRef", crate::fn_info!(#[realm(MessagePort.prototype.hasRef, constructible = true)] export crate::messaging::port_operations::has_ref_port, 0; with_declared(0)), 0); +} + +#[cfg(test)] +#[path = "port_operations_tests.rs"] +mod tests; diff --git a/crates/perry-runtime/src/messaging/port_operations_tests.rs b/crates/perry-runtime/src/messaging/port_operations_tests.rs new file mode 100644 index 00000000000..57eea93a016 --- /dev/null +++ b/crates/perry-runtime/src/messaging/port_operations_tests.rs @@ -0,0 +1,52 @@ +use super::*; + +#[test] +fn private_message_port_identity_survives_public_shadow_and_shape_changes() { + crate::gc::gc_init(); + let scope = crate::gc::RuntimeHandleScope::new(); + let port = scope.root_raw_mut_ptr(message_port_object_with_id(17)); + let receiver = + || crate::value::js_nanbox_pointer(port.get_raw_mut_ptr::() as i64); + assert_eq!(message_port_id(receiver()), Some(17)); + let spelling = key("#"); + object::js_object_set_field_by_name(port.get_raw_mut_ptr(), spelling, 33.0); + assert_eq!(message_port_id(receiver()), Some(17)); + let fake = object::js_object_alloc(0, 0); + object::js_object_set_field_by_name(fake, spelling, 17.0); + assert_eq!(message_port_id(boxed_object(fake)), None); + assert_eq!(message_port_id(js_undefined()), None); +} + +#[test] +fn runtime_port_core_operations_preserve_closed_identity_without_retaining_state() { + crate::gc::gc_init(); + let scope = crate::gc::RuntimeHandleScope::new(); + let first = scope.root_raw_mut_ptr(message_port_object_with_id(0)); + let second = scope.root_raw_mut_ptr(message_port_object_with_id(0)); + entangle_ports(first.get_raw_mut_ptr(), second.get_raw_mut_ptr()); + let receiver = |port: &crate::gc::RuntimeHandle<'_>| { + JsThis::from_f64(boxed_object(port.get_raw_mut_ptr())) + }; + assert_eq!( + ref_port(std::ptr::null(), receiver(&first)).to_bits(), + js_undefined().to_bits() + ); + assert_eq!( + has_ref_port(std::ptr::null(), receiver(&first)).to_bits(), + js_bool(true).to_bits() + ); + close(std::ptr::null(), receiver(&first)); + close(std::ptr::null(), receiver(&second)); + assert!(!with_port_states(|map| map.contains_key( + &(first.get_raw_mut_ptr::() as usize) + ))); + start(std::ptr::null(), receiver(&first)); + assert_eq!(message_port_id(receiver(&first).as_f64()), Some(0)); + assert_eq!( + has_ref_port(std::ptr::null(), receiver(&first)).to_bits(), + js_bool(false).to_bits() + ); + assert!(!with_port_states(|map| map.contains_key( + &(first.get_raw_mut_ptr::() as usize) + ))); +} diff --git a/crates/perry-runtime/src/object/field_get_set.rs b/crates/perry-runtime/src/object/field_get_set.rs index bd4e031c280..f758043ba34 100644 --- a/crates/perry-runtime/src/object/field_get_set.rs +++ b/crates/perry-runtime/src/object/field_get_set.rs @@ -333,7 +333,6 @@ pub(crate) use has_property::{ pub use has_property::{js_in_operator, js_object_has_property}; #[cfg(test)] pub(crate) use ic_miss::get_field_miss; -#[cfg(feature = "regex-engine")] pub(crate) use ic_miss::intrinsic_private_add; #[cfg(feature = "regex-engine")] pub(crate) use ic_miss::intrinsic_private_set; diff --git a/crates/perry-runtime/src/object/field_get_set/ic_miss/private_storage_cache.rs b/crates/perry-runtime/src/object/field_get_set/ic_miss/private_storage_cache.rs index 73f1d0bd9bc..4da13673969 100644 --- a/crates/perry-runtime/src/object/field_get_set/ic_miss/private_storage_cache.rs +++ b/crates/perry-runtime/src/object/field_get_set/ic_miss/private_storage_cache.rs @@ -643,7 +643,6 @@ pub(crate) fn intrinsic_private_set(receiver: f64, name: &'static str, value: f6 /// Install the private entry before the intrinsic publishes its receiver. /// A property with the qualified spelling is never a private field. -#[cfg(feature = "regex-engine")] pub(crate) fn intrinsic_private_add(receiver: f64, name: &'static str, value: f64) { let scope = crate::gc::RuntimeHandleScope::new(); let receiver = diff --git a/crates/perry-runtime/src/object/runtime_layouts.rs b/crates/perry-runtime/src/object/runtime_layouts.rs index 3337e7606b7..f4595aa77f3 100644 --- a/crates/perry-runtime/src/object/runtime_layouts.rs +++ b/crates/perry-runtime/src/object/runtime_layouts.rs @@ -217,6 +217,7 @@ include!("runtime_layouts/ascii_chars.rs"); /// the receiver's layout: no spelling is built, hashed or looked up per read. pub(crate) mod intrinsic_private_atoms { use super::*; + runtime_atom!(MESSAGE_PORT_ID, "#", "233c70657272793a707269766174652d76616c75653a303a40303a5b5b4d657373616765506f727449645d5d3e"); runtime_atom!( REGEXP_MATCHER, "#", diff --git a/crates/perry-stdlib/src/common/dispatch/init.rs b/crates/perry-stdlib/src/common/dispatch/init.rs index 92eccda996e..b81a151ddef 100644 --- a/crates/perry-stdlib/src/common/dispatch/init.rs +++ b/crates/perry-stdlib/src/common/dispatch/init.rs @@ -210,6 +210,9 @@ pub unsafe extern "C" fn js_stdlib_init_dispatch() { crate::worker_threads::js_worker_threads_message_channel_new, crate::worker_threads::js_worker_threads_broadcast_channel_new, ); + perry_runtime::messaging::js_register_worker_threads_message_port_dispatch( + crate::worker_threads::dispatch_message_port_core_operation, + ); perry_runtime::js_set_native_querystring_dispatch( crate::querystring::js_querystring_native_dispatch, ); diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index 93befc94ac3..fcabf7b8479 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -30,6 +30,9 @@ mod entry_table; mod environment_data; mod inbox; mod message_port; +#[cfg(test)] +mod message_port_receiver_tests; +pub(crate) use message_port::dispatch_core_operation as dispatch_message_port_core_operation; mod parent_port; #[cfg(test)] mod parent_port_tests; @@ -1282,9 +1285,9 @@ pub extern "C" fn js_worker_threads_receive_message_on_port(port: f64) -> f64 { } } -/// Recover a port id from a MessagePort JS object (the hidden `__perryPortId`). +/// Recover the intrinsic private ID without observable property access. fn port_id_from_object(port: f64) -> Option { - object_u64_field(port, "__perryPortId") + perry_runtime::messaging::message_port_id(port).filter(|id| *id != 0) } fn broadcast_channel_id_from_object(channel: f64) -> Option { diff --git a/crates/perry-stdlib/src/worker_threads/message_port.rs b/crates/perry-stdlib/src/worker_threads/message_port.rs index d3ac0fafcf9..d59e5ce8420 100644 --- a/crates/perry-stdlib/src/worker_threads/message_port.rs +++ b/crates/perry-stdlib/src/worker_threads/message_port.rs @@ -7,145 +7,96 @@ use super::*; -/// Build a MessagePort JS object for a same-process channel. The id is also -/// stored on the object (hidden `__perryPortId` field) so `receiveMessageOnPort` -/// can recover it from the object reference. +/// Build a MessagePort around its existing state ID. pub(super) fn message_port_object(port_id: u64) -> *mut perry_runtime::object::ObjectHeader { - let obj = perry_runtime::object::js_object_alloc(0, 0); - set_object_prototype(obj, constructor_prototype("MessagePort")); - let object_bits = object_value(obj).to_bits(); - set_object_field(obj, "constructor", get_global_constructor("MessagePort")); - set_object_field( - obj, - "postMessage", - port_bound_closure( - perry_runtime::fn_info!(port_post_message, 2; with_declared(2)), - port_id, - ), - ); - set_object_field( - obj, + let scope = perry_runtime::gc::RuntimeHandleScope::new(); + let object = scope.root_raw_mut_ptr(perry_runtime::messaging::message_port_object_with_id( + port_id, + )); + let prototype = constructor_prototype("MessagePort"); + set_object_prototype(object.get_raw_mut_ptr(), prototype); + let set = |name: &str, value: f64| { + let value = scope.root_nanbox_f64(value); + let key = js_string_from_bytes(name.as_ptr(), name.len() as u32); + perry_runtime::object::js_object_set_field_by_name( + object.get_raw_mut_ptr(), + key, + value.get_nanbox_f64(), + ); + }; + set("constructor", get_global_constructor("MessagePort")); + + set( "on", port_bound_closure( perry_runtime::fn_info!(port_on, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, + set( "addListener", port_bound_closure( perry_runtime::fn_info!(port_on, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, + set( "once", port_bound_closure( perry_runtime::fn_info!(port_once, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, + set( "off", port_bound_closure( perry_runtime::fn_info!(port_off, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, + set( "removeListener", port_bound_closure( perry_runtime::fn_info!(port_off, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, + set( "listenerCount", port_bound_closure( perry_runtime::fn_info!(port_listener_count, 1; with_declared(1)), port_id, ), ); - set_object_field( - obj, + set( "addEventListener", port_bound_closure( perry_runtime::fn_info!(port_add_event_listener, 3; with_declared(3)), port_id, ), ); - set_object_field( - obj, + set( "removeEventListener", port_bound_closure( perry_runtime::fn_info!(port_remove_event_listener, 2; with_declared(2)), port_id, ), ); - set_object_field( - obj, - "close", - port_bound_closure( - perry_runtime::fn_info!(port_close, 0; with_declared(0)), - port_id, - ), - ); - set_object_field( - obj, - "start", - port_bound_closure( - perry_runtime::fn_info!(port_start, 0; with_declared(0)), - port_id, - ), - ); - set_object_field( - obj, - "ref", - port_bound_closure( - perry_runtime::fn_info!(port_ref, 0; with_declared(0)), - port_id, - ), - ); - set_object_field( - obj, - "unref", - port_bound_closure( - perry_runtime::fn_info!(port_unref, 0; with_declared(0)), - port_id, - ), - ); - set_object_field( - obj, - "hasRef", - port_bound_closure( - perry_runtime::fn_info!(port_has_ref, 0; with_declared(0)), - port_id, - ), - ); - set_object_field(obj, "__perryPortId", f64::from_bits(port_id)); - set_object_field(obj, "onmessage", js_null()); - set_object_field(obj, "onmessageerror", js_null()); + + perry_runtime::messaging::install_message_port_core_methods(object.get_raw_mut_ptr()); + set("onmessage", js_null()); + set("onmessageerror", js_null()); + let object_bits = object_value(object.get_raw_mut_ptr()).to_bits(); MESSAGE_PORTS.with(|ports| { if let Some(state) = ports.borrow_mut().get_mut(&port_id) { state.object_bits = object_bits; } }); - obj + object.get_raw_mut_ptr() } /// port.postMessage(value) — deliver to the peer port's inbox (#3157). -extern "C" fn port_post_message( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, - value: f64, - transfer: f64, -) -> f64 { - let port_id = port_id_from_closure(closure); +fn port_post_message(port_id: u64, value: f64, transfer: f64) -> f64 { if port_id == PARENT_PORT_HANDLE as u64 && CURRENT_WORKER_ID.with(|id| id.get()) != 0 { return js_worker_threads_post_message(value, transfer); } @@ -429,11 +380,7 @@ pub(super) fn remove_event_listener(port_id: u64, event: f64, callback: f64) -> } /// port.start() — enable delivery of queued messages to the listener (#3157). -extern "C" fn port_start( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, -) -> f64 { - let port_id = port_id_from_closure(closure); +fn port_start(port_id: u64) -> f64 { MESSAGE_PORTS.with(|ports| { if let Some(state) = ports.borrow_mut().get_mut(&port_id) { state.started = true; @@ -442,33 +389,7 @@ extern "C" fn port_start( js_undefined() } -extern "C" fn port_ref( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, -) -> f64 { - let port_id = port_id_from_closure(closure); - let has_handler = MESSAGE_PORTS.with(|ports| { - ports - .borrow() - .get(&port_id) - .is_some_and(|state| object_event_handler(state.object_bits, "onmessage").is_some()) - }); - MESSAGE_PORTS.with(|ports| { - if let Some(state) = ports.borrow_mut().get_mut(&port_id) { - if !state.closed { - state.refed = true; - state.onmessage_callable = has_handler; - } - } - }); - js_undefined() -} - -extern "C" fn port_unref( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, -) -> f64 { - let port_id = port_id_from_closure(closure); +fn set_port_ref(port_id: u64, refed: bool) -> f64 { let has_handler = MESSAGE_PORTS.with(|ports| { ports .borrow() @@ -478,7 +399,7 @@ extern "C" fn port_unref( MESSAGE_PORTS.with(|ports| { if let Some(state) = ports.borrow_mut().get_mut(&port_id) { if !state.closed { - state.refed = false; + state.refed = refed; state.onmessage_callable = has_handler; } } @@ -486,14 +407,6 @@ extern "C" fn port_unref( js_undefined() } -extern "C" fn port_has_ref( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, -) -> f64 { - let port_id = port_id_from_closure(closure); - js_bool(port_is_refed(port_id)) -} - pub(super) fn port_is_refed(port_id: u64) -> bool { let has_handler = MESSAGE_PORTS.with(|ports| { let ports = ports.borrow(); @@ -516,13 +429,6 @@ pub(super) fn port_is_refed(port_id: u64) -> bool { } /// port.close() — mark closed and queue `close` events on both ends (#3157). -extern "C" fn port_close( - closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, -) -> f64 { - close_port(port_id_from_closure(closure)) -} - pub(super) fn close_port(port_id: u64) -> f64 { let peer_id = MESSAGE_PORTS.with(|ports| ports.borrow().get(&port_id).map(|state| state.peer)); MESSAGE_PORTS.with(|ports| { @@ -549,3 +455,34 @@ pub(super) fn close_port(port_id: u64) -> f64 { perry_runtime::event_pump::js_notify_main_thread(); js_undefined() } + +pub(crate) unsafe extern "C" fn dispatch_core_operation( + operation: perry_runtime::messaging::MessagePortOperation, + receiver: f64, + value: f64, + transfer: f64, + result: *mut f64, +) -> bool { + let Some(id) = port_id_from_object(receiver) else { + return false; + }; + let recognized = MESSAGE_PORTS.with(|ports| { + ports + .borrow() + .get(&id) + .is_some_and(|state| state.object_bits == receiver.to_bits()) + }); + if !recognized { + return false; + } + use perry_runtime::messaging::MessagePortOperation; + *result = match operation { + MessagePortOperation::PostMessage => port_post_message(id, value, transfer), + MessagePortOperation::Start => port_start(id), + MessagePortOperation::Close => close_port(id), + MessagePortOperation::Ref => set_port_ref(id, true), + MessagePortOperation::Unref => set_port_ref(id, false), + MessagePortOperation::HasRef => js_bool(port_is_refed(id)), + }; + true +} diff --git a/crates/perry-stdlib/src/worker_threads/message_port_receiver_tests.rs b/crates/perry-stdlib/src/worker_threads/message_port_receiver_tests.rs new file mode 100644 index 00000000000..3f2884646b9 --- /dev/null +++ b/crates/perry-stdlib/src/worker_threads/message_port_receiver_tests.rs @@ -0,0 +1,41 @@ +use super::*; + +#[test] +fn duplicate_private_id_cannot_impersonate_registered_port_identity() { + std::thread::spawn(|| { + let agent = perry_runtime::agent::enter_worker_agent(); + perry_runtime::gc::gc_init(); + ensure_environment_data_gc_scanner(); + let id = 999; + MESSAGE_PORTS.with(|ports| { + ports.borrow_mut().insert(id, MessagePortState::default()); + }); + let scope = perry_runtime::gc::RuntimeHandleScope::new(); + let port = scope.root_raw_mut_ptr(message_port::message_port_object(id)); + let impostor = + scope.root_raw_mut_ptr(perry_runtime::messaging::message_port_object_with_id(id)); + let mut result = js_undefined(); + let invoke = |receiver, result: &mut f64| unsafe { + message_port::dispatch_core_operation( + perry_runtime::messaging::MessagePortOperation::Ref, + receiver, + js_undefined(), + js_undefined(), + result, + ) + }; + assert!(!invoke( + object_value(impostor.get_raw_mut_ptr()), + &mut result + )); + assert!(!message_port::port_is_refed(id)); + assert!(invoke(object_value(port.get_raw_mut_ptr()), &mut result)); + assert!(message_port::port_is_refed(id)); + MESSAGE_PORTS.with(|ports| { + ports.borrow_mut().remove(&id); + }); + perry_runtime::agent::retire_agent(agent); + }) + .join() + .unwrap(); +} diff --git a/crates/perry-stdlib/src/worker_threads/parent_port_tests.rs b/crates/perry-stdlib/src/worker_threads/parent_port_tests.rs index 971d70dd445..e4b0af77f96 100644 --- a/crates/perry-stdlib/src/worker_threads/parent_port_tests.rs +++ b/crates/perry-stdlib/src/worker_threads/parent_port_tests.rs @@ -68,6 +68,9 @@ fn parent_port_once_consumes_its_reference_before_calling_js() { #[test] fn parent_port_close_and_unref_use_the_message_port_handle_state() { isolated(|| { + unsafe { + crate::common::dispatch::js_stdlib_init_dispatch(); + } let port = object_value(parent_port::worker_parent_port_object()); let scope = perry_runtime::gc::RuntimeHandleScope::new(); let port = scope.root_nanbox_f64(port); @@ -82,7 +85,7 @@ fn parent_port_close_and_unref_use_the_message_port_handle_state() { perry_runtime::value::js_nanbox_get_pointer(closure) as *const ClosureHeader; perry_runtime::closure::js_closure_call0( closure, - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(port.get_nanbox_f64()), ); assert_eq!( message_port::port_is_refed(PARENT_PORT_HANDLE as u64), diff --git a/docs/src/threading/overview.md b/docs/src/threading/overview.md index 97fb48cb2c9..7da0a3c31e6 100644 --- a/docs/src/threading/overview.md +++ b/docs/src/threading/overview.md @@ -242,6 +242,12 @@ Core-module messaging constructor exports retain the original thread-realm MessageChannel, MessagePort and BroadcastChannel objects even after global reassignment. Explicit CommonJS export overrides and named-export synchronization remain separate; vm/eval global population does not replace these thread-core values. +MessagePort core methods (`postMessage`, `start`, `close`, `ref`, `unref`, +`hasRef`) operate on their actual receiver, including calls borrowed from another +port or its prototype. A private ID supplies the brand without observable getters +or proxy traps; closing a port preserves its brand without retaining a closed +runtime port in the delivery table. Listener methods and realm movement remain +separate parity work. Other worker API parity gaps remain. ## Next Steps diff --git a/test-parity/node-suite/worker_threads/message-port/receiver-operations.ts b/test-parity/node-suite/worker_threads/message-port/receiver-operations.ts new file mode 100644 index 00000000000..1008980ea72 --- /dev/null +++ b/test-parity/node-suite/worker_threads/message-port/receiver-operations.ts @@ -0,0 +1,61 @@ +import { MessageChannel, MessagePort, receiveMessageOnPort } from "node:worker_threads"; + +const prototype = MessagePort.prototype; +const post = prototype.postMessage; +const first = new MessageChannel(); +const second = new MessageChannel(); +console.log("initial", Reflect.apply(prototype.hasRef, first.port1, [])); +Reflect.apply(prototype.ref, first.port1, []); +console.log("ref", Reflect.apply(prototype.hasRef, first.port1, [])); +Reflect.apply(prototype.unref, first.port1, []); +console.log("unref", Reflect.apply(prototype.hasRef, first.port1, [])); +Reflect.apply(prototype.start, first.port1, []); +Reflect.apply(post, first.port1, ["prototype"]); +console.log("prototype", receiveMessageOnPort(first.port2)?.message); +const borrowed = first.port1.postMessage; +Reflect.apply(borrowed, second.port1, ["borrowed"]); +console.log("receiver", receiveMessageOnPort(second.port2)?.message); +console.log("owner", receiveMessageOnPort(first.port2) === undefined); +first.port1.postMessage("direct"); +console.log("direct", receiveMessageOnPort(first.port2)?.message); + +let effects = 0; +const fake = Object.create(MessagePort.prototype); +Object.defineProperty(fake, "__perryPortId", { + get() { effects++; throw new Error("brand getter"); }, +}); +const proxy = new Proxy(first.port1, { + get() { effects++; throw new Error("proxy getter"); }, +}); +const copied = { __perryPortId: (first.port1 as any).__perryPortId }; +const invalid = [undefined, null, {}, fake, copied, proxy]; +const payload = { get value() { effects++; return 1; } }; +const options = { get transfer() { effects++; return []; } }; +for (const name of ["postMessage", "start", "close", "ref", "unref", "hasRef"]) { + let rejected = 0; + for (const receiver of invalid) { + try { + Reflect.apply((prototype as any)[name], receiver, [payload, options]); + } catch (error: any) { + if (error.name === "TypeError" && error.code === undefined) rejected++; + } + } + console.log(name, rejected); +} +console.log("effects", effects); +(first.port1 as any).__perryPortId = 99; +Reflect.apply(post, first.port1, ["mutated"]); +console.log("mutated", receiveMessageOnPort(first.port2)?.message); +delete (first.port1 as any).__perryPortId; +(first.port1 as any)["#"] = 55; +Reflect.apply(post, first.port1, ["private"]); +console.log("private", receiveMessageOnPort(first.port2)?.message); +delete (first.port1 as any)["#"]; +Reflect.apply(prototype.close, first.port1, []); +Reflect.apply(prototype.close, first.port2, []); +console.log("closed", Reflect.apply(prototype.hasRef, first.port1, [])); +Reflect.apply(prototype.start, first.port1, []); +Reflect.apply(post, first.port1, ["closed"]); +console.log("closed delivery", receiveMessageOnPort(first.port2) === undefined); +second.port1.close(); +second.port2.close(); From 9ff1a83d3a61aaae26ab55a0a11315848c5e917b Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:11:56 +0100 Subject: [PATCH 089/144] docs(changelog): number MessagePort receiver fix fragment --- ...ed-worker-port-receivers.md => 13624-worker-port-receivers.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{unreleased-worker-port-receivers.md => 13624-worker-port-receivers.md} (100%) diff --git a/changelog.d/unreleased-worker-port-receivers.md b/changelog.d/13624-worker-port-receivers.md similarity index 100% rename from changelog.d/unreleased-worker-port-receivers.md rename to changelog.d/13624-worker-port-receivers.md From 30b4d93f9647808fa8e57a244d57cccb42ed070b Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:42:20 +0100 Subject: [PATCH 090/144] fix(codegen): instanceof consults a computed static Symbol.hasInstance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bare-identifier instanceof RHS resolved a user class to its class id and emitted the static js_instanceof(v, ) compare. That answer ignores the function object, so a computed static member — @@hasInstance, including one installed with Object.defineProperty on the class — was never consulted: the observable GetMethod was lost, and it was lost even for a primitive LHS. The class case now materializes the declaration's function object (emit_class_value, which is non-collecting and returns a pinned value) and calls js_instanceof_dynamic, which performs GetMethod on it. Class metadata that is not this lexical binding never claims it, as before. Ported from #12929's instanceof arm; the PR also carried two unrelated fixes and a version bump, which are not part of this change. --- crates/perry-codegen/src/expr/instance_misc1.rs | 14 ++++++++++++-- .../src/expr/instanceof_imported_rhs_tests.rs | 14 +++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/perry-codegen/src/expr/instance_misc1.rs b/crates/perry-codegen/src/expr/instance_misc1.rs index d91bd19ab1f..5215f267ae8 100644 --- a/crates/perry-codegen/src/expr/instance_misc1.rs +++ b/crates/perry-codegen/src/expr/instance_misc1.rs @@ -483,9 +483,19 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // user classes (local `hir.classes` + cross-module imported // user classes); plain built-in names never appear in it, so // unshadowed built-ins keep their reserved ids. - let user_cid = ctx.class_ids.get(ty).copied(); + if let Some(cid) = ctx.class_ids.get(ty).copied() { + // The declaration's function object owns computed static + // members, including @@hasInstance. A class id alone loses that + // observable GetMethod (even for a primitive LHS). + // `js_class_value` is non-collecting and returns a pinned value. + let rhs = super::emit_class_value(ctx.block(), cid); + return Ok(ctx.block().call( + DOUBLE, + "js_instanceof_dynamic", + &[(DOUBLE, &v), (DOUBLE, &rhs)], + )); + } let cid = match ty.as_str() { - _ if user_cid.is_some() => user_cid.unwrap(), "Error" => 0xFFFF0001u32, "TypeError" => 0xFFFF0010u32, "RangeError" => 0xFFFF0011u32, diff --git a/crates/perry-codegen/src/expr/instanceof_imported_rhs_tests.rs b/crates/perry-codegen/src/expr/instanceof_imported_rhs_tests.rs index d3cae55a9d5..2ba828bc9d6 100644 --- a/crates/perry-codegen/src/expr/instanceof_imported_rhs_tests.rs +++ b/crates/perry-codegen/src/expr/instanceof_imported_rhs_tests.rs @@ -88,6 +88,7 @@ fn compile(module: &Module, opts: CompileOptions) -> String { #[test] fn imported_function_constructor_rhs_resolves_the_constructor_value() { let mut opts = CompileOptions { + namespace_self_import: false, emit_ir_only: true, ..Default::default() }; @@ -108,8 +109,9 @@ fn imported_function_constructor_rhs_resolves_the_constructor_value() { } #[test] -fn imported_class_rhs_keeps_the_static_class_id_check() { +fn imported_class_rhs_resolves_its_constructor_object() { let mut opts = CompileOptions { + namespace_self_import: false, emit_ir_only: true, ..Default::default() }; @@ -119,12 +121,12 @@ fn imported_class_rhs_keeps_the_static_class_id_check() { let ir = compile(&instanceof_imported("Klass"), opts); assert!( - ir.contains(STATIC_CALL), - "an imported class must keep the static class-id check:\n{ir}" + ir.contains("call double @js_class_value(i32 7701)") && ir.contains(DYNAMIC_CALL), + "an imported class must read @@hasInstance from its function object:\n{ir}" ); assert!( - !ir.contains(DYNAMIC_CALL), - "the dynamic helper would only unpack the same class id back out:\n{ir}" + !ir.contains(STATIC_CALL), + "the class-id check misses computed static hooks:\n{ir}" ); } @@ -137,6 +139,7 @@ fn unresolved_import_rhs_keeps_its_reserved_builtin_id() { let ir = compile( &instanceof_imported("Error"), CompileOptions { + namespace_self_import: false, emit_ir_only: true, ..Default::default() }, @@ -166,6 +169,7 @@ fn weak_wrapper_constructors_name_their_instance_class_ids() { let ir = compile( &instanceof_imported(name), CompileOptions { + namespace_self_import: false, emit_ir_only: true, ..Default::default() }, From adcb8545316583fb31efac979e70f2de4d20d32c Mon Sep 17 00:00:00 2001 From: Sergi Gonzalez <31130069+TheHypnoo@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:49:47 +0100 Subject: [PATCH 091/144] changelog: record the instanceof Symbol.hasInstance fix --- changelog.d/13632-instanceof-hasinstance.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13632-instanceof-hasinstance.md diff --git a/changelog.d/13632-instanceof-hasinstance.md b/changelog.d/13632-instanceof-hasinstance.md new file mode 100644 index 00000000000..a0024e7c1c3 --- /dev/null +++ b/changelog.d/13632-instanceof-hasinstance.md @@ -0,0 +1 @@ +**`instanceof` now consults a computed static `Symbol.hasInstance`.** When the right-hand side was a bare identifier naming a user class, codegen resolved it to the class id and emitted the static `js_instanceof(v, )` compare. That answer never looks at the function object, so a computed static member — `@@hasInstance`, including one installed with `Object.defineProperty` on the class — was silently ignored, and it was ignored even for a primitive left-hand side. The class case now materializes the declaration's function object (`emit_class_value`, which is non-collecting and returns a pinned value) and calls `js_instanceof_dynamic`, which performs the observable `GetMethod` on it; class metadata that is not this lexical binding never claims it, as before. The regression asserts the emitted IR reads `@@hasInstance` from that object and no longer emits the static class-id compare — red against the previous lowering, which reported "an imported class must read @@hasInstance from its function object". From 8eb99d6d5fb3b986807a7acb65f3a416b4b9f95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 22:40:48 +0100 Subject: [PATCH 092/144] gc: walk x86-64 Linux stacks by frame kind, without the system unwinder (#13582) --- .../13582-gcw-walk-without-unwinder.md | 15 + crates/perry-runtime/src/arena/allocators.rs | 27 +- crates/perry-runtime/src/arena/inline.rs | 38 ++ crates/perry-runtime/src/arena/mod.rs | 5 +- crates/perry-runtime/src/eh_walker.rs | 12 + crates/perry-runtime/src/eh_walker_trace.rs | 310 ++++++++++++-- crates/perry-runtime/src/gc/mod.rs | 4 +- .../perry-runtime/src/gc/native_stack_scan.rs | 15 +- crates/perry-runtime/src/gc/policy.rs | 70 ++- crates/perry-runtime/src/gc/poll_arm.rs | 6 + crates/perry-runtime/src/gc/roots.rs | 4 + .../perry-runtime/src/gc/roots/stack_maps.rs | 74 +++- .../src/gc/roots/stack_maps_decode_tests.rs | 2 + .../src/gc/roots/stack_maps_frame_walk.rs | 270 ++++++++++++ .../gc/roots/stack_maps_frame_walk_tests.rs | 405 ++++++++++++++++++ .../src/gc/roots/stack_maps_index.rs | 10 + .../src/gc/roots/stack_maps_lazy.rs | 55 ++- .../src/gc/roots/stack_maps_verify.rs | 11 +- .../perry-runtime/src/gc/roots/walk_origin.rs | 152 +++++++ .../perry-runtime/src/object/layout_desc.rs | 49 ++- .../src/object/layout_desc/literal_birth.rs | 31 +- crates/perry-runtime/src/test_support.rs | 13 + scripts/gc_runtime_root_holders.json | 4 +- scripts/global_sink_asserted_reviews.json | 6 +- 24 files changed, 1525 insertions(+), 63 deletions(-) create mode 100644 changelog.d/13582-gcw-walk-without-unwinder.md create mode 100644 crates/perry-runtime/src/gc/roots/stack_maps_frame_walk.rs create mode 100644 crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs create mode 100644 crates/perry-runtime/src/gc/roots/walk_origin.rs diff --git a/changelog.d/13582-gcw-walk-without-unwinder.md b/changelog.d/13582-gcw-walk-without-unwinder.md new file mode 100644 index 00000000000..a70ad73b313 --- /dev/null +++ b/changelog.d/13582-gcw-walk-without-unwinder.md @@ -0,0 +1,15 @@ +x86-64 Linux: the GC root scan no longer walks the stack with the system +unwinder. JS frames step by their frame record (two loads, exact `sp` and +`rbp`); Rust, shim and foreign frames between them step by their unwind row +through the runtime's own direct decoder; the walk ends after `main` or at the +worker base trap. The back-edge poll, `gc()` and the inline allocation slow +paths are naked shims that hand their caller's frame to the collections they +run, so those walks start at the JS frame and never step the collector's own +frames (allocation offers it only on the arena slow path, the poll only +when it collects). `match_records` rejects a pc past a function's last record in one +search. On binary-trees a walk drops from 9.1 frames and ~28.7k instructions +to 1.1 frames and ~8.5k; `_Unwind_Find_FDE` is no longer called by any scan. + +A row the walk cannot follow fails closed to `_Unwind_Backtrace`, and when +that also stops before the end of the stack the process now aborts naming the +module, instead of silently scanning a truncated stack. diff --git a/crates/perry-runtime/src/arena/allocators.rs b/crates/perry-runtime/src/arena/allocators.rs index 9cdffd3841b..80c961bc7a8 100644 --- a/crates/perry-runtime/src/arena/allocators.rs +++ b/crates/perry-runtime/src/arena/allocators.rs @@ -556,9 +556,18 @@ pub fn arena_alloc_gc(size: usize, align: usize, obj_type: u8) -> *mut u8 { /// fills by; the image is read through the pointer on the fast arm only, so the /// large and slow arms pay nothing for it. /// +/// `origin` is the JS caller's frame when the entry passed one: only the slow +/// arm, the one that can collect, offers it (`gc::roots::walk_origin`), so the +/// bump path stays as it was. +/// /// # Safety /// `word` points at the descriptor's header image. -pub(crate) unsafe fn arena_alloc_gc_object_word(word: *const u64, slots: u32) -> *mut u8 { +#[inline(always)] +pub(crate) unsafe fn arena_alloc_gc_object_word_offering( + word: *const u64, + slots: u32, + origin: Option, +) -> *mut u8 { use crate::gc::GC_TYPE_OBJECT; let size = crate::object::header::meta_form_alloc_bytes(slots as usize); let total = gc_padded_total_size(size, 8); @@ -573,7 +582,21 @@ pub(crate) unsafe fn arena_alloc_gc_object_word(word: *const u64, slots: u32) -> return init_young_header_word(raw, *word, GC_TYPE_OBJECT); } } - arena_alloc_gc_slow(size, 8, GC_TYPE_OBJECT, total) + match origin { + Some(origin) => arena_alloc_gc_slow_offering(size, total, origin), + None => arena_alloc_gc_slow(size, 8, GC_TYPE_OBJECT, total), + } +} + +#[cold] +#[inline(never)] +unsafe fn arena_alloc_gc_slow_offering( + size: usize, + total: usize, + origin: crate::gc::WalkOrigin, +) -> *mut u8 { + let _offer = crate::gc::EntryWalkOrigin::enter(&origin); + arena_alloc_gc_slow(size, 8, crate::gc::GC_TYPE_OBJECT, total) } /// The current live birth color is read at the allocation, never cached diff --git a/crates/perry-runtime/src/arena/inline.rs b/crates/perry-runtime/src/arena/inline.rs index 3f9b7ad64ce..98417584986 100644 --- a/crates/perry-runtime/src/arena/inline.rs +++ b/crates/perry-runtime/src/arena/inline.rs @@ -86,11 +86,49 @@ pub(crate) unsafe fn prove_inline_arena_state(state: *const InlineArenaState) -> /// Returns the raw pointer (the codegen writes the GcHeader at this /// address and the ObjectHeader at +8 — same layout the inline path /// produces). +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] #[no_mangle] pub extern "C" fn js_inline_arena_slow_alloc( state: *mut InlineArenaState, size: usize, align: usize, +) -> *mut u8 { + js_inline_arena_slow_alloc_body(state, size, align) +} + +/// x86-64 Linux: an allocation slow path JS calls directly, entered through a +/// shim that hands its caller's frame to the collections it can run +/// (`gc::roots::walk_origin`). Allocation runs no JS, which is what lets it +/// offer. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[unsafe(naked)] +#[no_mangle] +pub extern "C" fn js_inline_arena_slow_alloc( + state: *mut InlineArenaState, + size: usize, + align: usize, +) -> *mut u8 { + crate::gc::offering_entry_asm!(3, js_inline_arena_slow_alloc_from) +} + +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +extern "C" fn js_inline_arena_slow_alloc_from( + state: *mut InlineArenaState, + size: usize, + align: usize, + pc: usize, + sp: usize, + fp: usize, +) -> *mut u8 { + let origin = crate::gc::WalkOrigin { pc, sp, fp }; + let _origin = crate::gc::EntryWalkOrigin::enter(&origin); + js_inline_arena_slow_alloc_body(state, size, align) +} + +fn js_inline_arena_slow_alloc_body( + state: *mut InlineArenaState, + size: usize, + align: usize, ) -> *mut u8 { // The nursery arena through the hot cache (`hot_arena`), not // `ARENA.with`: std's `LocalKey::with` reads the arena through the key's diff --git a/crates/perry-runtime/src/arena/mod.rs b/crates/perry-runtime/src/arena/mod.rs index e76c2d0abae..a8cceb598dd 100644 --- a/crates/perry-runtime/src/arena/mod.rs +++ b/crates/perry-runtime/src/arena/mod.rs @@ -133,8 +133,9 @@ pub use allocators::{ }; pub(crate) use allocators::{ arena_alloc_gc_collecting_holder, arena_alloc_gc_large_young_no_collect, - arena_alloc_gc_no_collect, arena_alloc_gc_object_word, arena_alloc_gc_old_born_tenured, - arena_alloc_gc_old_excluding_pages, arena_alloc_gc_promoted, arena_alloc_gc_survivor, + arena_alloc_gc_no_collect, arena_alloc_gc_object_word_offering, + arena_alloc_gc_old_born_tenured, arena_alloc_gc_old_excluding_pages, arena_alloc_gc_promoted, + arena_alloc_gc_survivor, }; // walk.rs diff --git a/crates/perry-runtime/src/eh_walker.rs b/crates/perry-runtime/src/eh_walker.rs index 83d885004cb..b698cac8da2 100644 --- a/crates/perry-runtime/src/eh_walker.rs +++ b/crates/perry-runtime/src/eh_walker.rs @@ -1164,6 +1164,16 @@ pub(crate) fn function_start_for(pc: u64) -> Option { mod trace; #[cfg(all(target_arch = "x86_64", target_os = "linux"))] pub(crate) use trace::{capture_js_return_addresses, capture_return_addresses}; +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +pub(crate) use trace::{ + cfi_function_extent, executable_main, gc_cfi_object, gc_cfi_step, module_name, GcCfiObject, + GcCfiScratch, GcCfiStep, +}; +/// Indices of the stack and frame pointer in [`WalkRegs::regs`]. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +pub(crate) const SP_INDEX: usize = SP; +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +pub(crate) const FP_INDEX: usize = FP; #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] pub(crate) fn walk_pcs_from_here(_max: usize) -> Option> { @@ -1747,6 +1757,7 @@ mod tests { #[test] #[inline(never)] fn second_walk_hits_cache_and_agrees() { + let _loader = crate::test_support::loaded_objects_test_lock(); let a = walk_pcs_from_here(32).expect("walker"); let b = walk_pcs_from_here(32).expect("walker"); // The chains differ only at THIS function's frame (two distinct @@ -1769,6 +1780,7 @@ mod tests { #[cfg(all(target_os = "linux", target_env = "gnu"))] #[test] fn image_index_follows_dlopen_and_dlclose() { + let _loader = crate::test_support::loaded_objects_test_lock(); use std::ffi::CStr; /// An executable address of the loaded object whose path ends in diff --git a/crates/perry-runtime/src/eh_walker_trace.rs b/crates/perry-runtime/src/eh_walker_trace.rs index 39d2c768768..5272418e97d 100644 --- a/crates/perry-runtime/src/eh_walker_trace.rs +++ b/crates/perry-runtime/src/eh_walker_trace.rs @@ -273,7 +273,7 @@ pub(crate) fn capture_return_addresses(out: &mut [usize], top: usize) -> usize { /// without `main` (a test harness loaded some other way, a shared object) /// links and simply walks to the bottom of the stack. #[cfg(not(test))] -pub(super) fn executable_main() -> u64 { +pub(crate) fn executable_main() -> u64 { extern "C" { #[linkage = "extern_weak"] static main: *const u8; @@ -286,7 +286,7 @@ pub(super) fn executable_main() -> u64 { // Rust synthesizes the test harness's main in this crate; redeclaring that // entry as weak data conflicts with its generated function during LTO. #[cfg(test)] -pub(super) fn executable_main() -> u64 { +pub(crate) fn executable_main() -> u64 { 0 } @@ -337,7 +337,7 @@ pub(crate) fn capture_js_return_addresses( // `Some(Some(..))`: the function's start and FDE; `Some(None)`: no // table covers the site; `None`: only gimli can read this image. let covering = match image { - Some(image) => hdr_function(&image.image, site), + Some(image) => hdr_function(&image.image.cfi_tables(), site), None => Some(None), }; let start = match covering { @@ -361,7 +361,13 @@ pub(crate) fn capture_js_return_addresses( let row = match (image, covering) { _ if js => Row::NoFde, (Some(image), Some(Some((start, fde)))) => { - match fde_row(&image.image, site, start, fde, &mut scratch.cies) { + match fde_row( + &image.image.cfi_tables(), + site, + start, + fde, + &mut scratch.cies, + ) { Some(row) => row, None => gimli_row( image, @@ -850,40 +856,62 @@ fn hdr_search(hdr: &[u8], rel: i64) -> Option> { /// The row for `site` read directly from `image`'s tables, or `None` to let /// gimli decide (any table shape other than the one LLVM emits). fn direct_row(image: &EhFrameImage, site: u64, cies: &mut CieMemo) -> Option { - let Some((func_start, fde_addr)) = hdr_function(image, site)? else { + let tables = image.cfi_tables(); + let Some((func_start, fde_addr)) = hdr_function(&tables, site)? else { return Some(Row::NoFde); }; - fde_row(image, site, func_start, fde_addr, cies) + fde_row(&tables, site, func_start, fde_addr, cies) +} + +/// The two tables the direct decoder reads, as plain slices: an image the +/// `Error` walker enumerated, or a loaded object the collector's frame walk +/// stepped into (`gc_cfi_step`). +#[derive(Clone, Copy)] +struct CfiTables { + /// `.eh_frame`, from its first byte to (at most) the end of its object. + eh: &'static [u8], + eh_frame_addr: u64, + /// `.eh_frame_hdr`, likewise bounded by its object. + hdr: &'static [u8], + hdr_addr: u64, +} + +impl EhFrameImage { + fn cfi_tables(&self) -> CfiTables { + CfiTables { + eh: self.bytes, + eh_frame_addr: self.eh_frame_addr, + hdr: self.hdr_bytes, + hdr_addr: self.hdr_addr, + } + } } /// The function `.eh_frame_hdr` says covers `site`: `(start, FDE address)`; /// `Some(None)` when no table entry starts at or below it; `None` for a /// header layout this decoder does not read. #[inline(always)] -fn hdr_function(image: &EhFrameImage, site: u64) -> Option> { - let rel = site as i64 - image.hdr_addr as i64; - let Some((start_rel, fde_rel)) = hdr_search(image.hdr_bytes, rel)? else { +fn hdr_function(tables: &CfiTables, site: u64) -> Option> { + let rel = site as i64 - tables.hdr_addr as i64; + let Some((start_rel, fde_rel)) = hdr_search(tables.hdr, rel)? else { return Some(None); }; Some(Some(( - (image.hdr_addr as i64).checked_add(start_rel)? as u64, - (image.hdr_addr as i64).checked_add(fde_rel)? as u64, + (tables.hdr_addr as i64).checked_add(start_rel)? as u64, + (tables.hdr_addr as i64).checked_add(fde_rel)? as u64, ))) } -/// The row at `site` from the FDE at `fde_addr` (covering the function at -/// `func_start`), or `None` to let gimli decide. -fn fde_row( - image: &EhFrameImage, - site: u64, - func_start: u64, +/// The FDE at `fde_addr`, read up to its program: a cursor at the first +/// instruction, the FDE's end, its `pc_range` and its CIE. +#[inline(always)] +fn fde_header( + tables: &CfiTables, fde_addr: u64, cies: &mut CieMemo, -) -> Option { - let eh = image.bytes; - let fde_off = usize::try_from(fde_addr.checked_sub(image.eh_frame_addr)?).ok()?; - - // FDE header. +) -> Option<(Cursor<'static>, usize, u64, Cie)> { + let eh = tables.eh; + let fde_off = usize::try_from(fde_addr.checked_sub(tables.eh_frame_addr)?).ok()?; let mut c = Cursor::new(eh, fde_off); let len = c.u32()?; if len == 0 || len == u32::MAX { @@ -896,15 +924,26 @@ fn fde_row( let cie_ptr_at = c.at; let cie_off = cie_ptr_at.checked_sub(c.u32()? as usize)?; let cie = cies.get(eh, cie_off)?; - - // FDE body: pc_begin (already known from the table), pc_range, the - // augmentation data, then the program. + // pc_begin (already known from the table), then pc_range. c.skip(cie.ptr_size)?; let range = match cie.ptr_size { 8 => u64::from_le_bytes(c.take()?), 4 => c.u32()? as u64, _ => c.u16()? as u64, }; + Some((c, fde_end, range, cie)) +} + +/// The row at `site` from the FDE at `fde_addr` (covering the function at +/// `func_start`), or `None` to let gimli decide. +fn fde_row( + tables: &CfiTables, + site: u64, + func_start: u64, + fde_addr: u64, + cies: &mut CieMemo, +) -> Option { + let (mut c, fde_end, range, cie) = fde_header(tables, fde_addr, cies)?; if site >= func_start.checked_add(range)? { return Some(Row::NoFde); } @@ -948,6 +987,226 @@ fn fde_row( }) } +// --------------------------------------------------------------------------- +// The collector's CFI step (`gc/roots/stack_maps_frame_walk.rs`). +// +// The root scan steps a Rust, shim or foreign frame by its row here, and +// nothing else: JS frames step by their frame record and never reach this. +// The runtime's own object is resolved once, with the stack-map index; a pc +// outside it (libc, an addon, a GTK main loop) finds its object through +// `dl_iterate_phdr`, which also sees objects loaded after the index was +// built. Nothing is kept between walks. Every row is read directly; a shape +// the direct decoder declines is `Unreadable`, and the collector then fails +// closed to the system unwinder. +// --------------------------------------------------------------------------- + +/// The unwind tables of one loaded object and the range of its executable +/// segments. `Default`: no object. +#[cfg(target_env = "gnu")] +#[derive(Clone, Copy, Default)] +pub(crate) struct GcCfiObject { + text: (u64, u64), + tables: Option, +} + +#[cfg(target_env = "gnu")] +impl core::fmt::Debug for GcCfiObject { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "GcCfiObject({:#x}..{:#x}, tables: {})", + self.text.0, + self.text.1, + self.tables.is_some() + ) + } +} + +#[cfg(target_env = "gnu")] +impl GcCfiObject { + #[inline(always)] + fn contains(&self, pc: u64) -> bool { + pc.wrapping_sub(self.text.0) < self.text.1.wrapping_sub(self.text.0) + } +} + +/// The loaded object whose executable segments hold `pc`, with its tables +/// when it has an `.eh_frame_hdr` this decoder reads. +#[cfg(target_env = "gnu")] +pub(crate) fn gc_cfi_object(pc: u64) -> GcCfiObject { + struct Query { + pc: u64, + found: GcCfiObject, + } + unsafe extern "C" fn each( + info: *mut libc::dl_phdr_info, + _size: libc::size_t, + data: *mut libc::c_void, + ) -> libc::c_int { + let query = unsafe { &mut *(data as *mut Query) }; + let info = unsafe { &*info }; + if info.dlpi_phdr.is_null() || info.dlpi_phnum == 0 { + return 0; + } + let base = info.dlpi_addr as u64; + let phdrs = + unsafe { core::slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize) }; + let (mut text, mut end, mut holds, mut hdr_addr) = ((u64::MAX, 0u64), 0u64, false, 0u64); + for ph in phdrs { + let lo = base.wrapping_add(ph.p_vaddr as u64); + let hi = lo.wrapping_add(ph.p_memsz as u64); + if ph.p_type == libc::PT_LOAD { + end = end.max(hi); + if ph.p_flags & libc::PF_X != 0 { + text = (text.0.min(lo), text.1.max(hi)); + holds |= query.pc >= lo && query.pc < hi; + } + } else if ph.p_type == libc::PT_GNU_EH_FRAME { + hdr_addr = lo; + } + } + if !holds { + return 0; + } + query.found = GcCfiObject { + text, + tables: tables_at(hdr_addr, end), + }; + 1 + } + let mut query = Query { + pc, + found: GcCfiObject::default(), + }; + unsafe { libc::dl_iterate_phdr(Some(each), &mut query as *mut Query as *mut libc::c_void) }; + query.found +} + +/// The tables whose `.eh_frame_hdr` is at `hdr_addr`, in an object mapped up +/// to `end`. +#[cfg(target_env = "gnu")] +fn tables_at(hdr_addr: u64, end: u64) -> Option { + if hdr_addr == 0 || hdr_addr >= end { + return None; + } + // SAFETY: inside the object's mapping; every read below is bounded by + // the header's own count and the FDE lengths, all checked against this. + let hdr: &'static [u8] = + unsafe { core::slice::from_raw_parts(hdr_addr as *const u8, (end - hdr_addr) as usize) }; + // eh_frame_ptr is pcrel|sdata4 (`hdr_search` checks the remaining bytes). + if hdr.len() < 12 || hdr[0] != 1 || hdr[1] != 0x1b { + return None; + } + let rel = i32::from_le_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]) as i64; + let eh_frame_addr = (hdr_addr as i64).checked_add(4)?.checked_add(rel)? as u64; + if eh_frame_addr >= end { + return None; + } + // SAFETY: as above. + let eh: &'static [u8] = unsafe { + core::slice::from_raw_parts(eh_frame_addr as *const u8, (end - eh_frame_addr) as usize) + }; + Some(CfiTables { + eh, + eh_frame_addr, + hdr, + hdr_addr, + }) +} + +/// One CFI step of the collector's walk. +#[cfg(target_env = "gnu")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum GcCfiStep { + /// The caller's frame: its pc (a return address), its sp at the call + /// (this frame's CFA) and its rbp. + Caller { pc: u64, rsp: u64, rbp: u64 }, + /// The tables mark this frame the outermost one (undefined return + /// address: `_start`, a thread's `clone3`). + Outermost, + /// No table covers the pc, or not with a row this step can follow. + Unreadable, +} + +/// The CIEs one collector walk has decoded; on the walk's stack, gone after. +#[cfg(target_env = "gnu")] +#[derive(Default)] +pub(crate) struct GcCfiScratch { + cies: CieMemo, +} + +/// Step the frame whose pc is the return address `pc` (with `rsp` its sp at +/// that call and `rbp` its rbp) to its caller. Reads stay in `[low, top)`. +/// `runtime` is the runtime's own object, resolved with the index. +#[cfg(target_env = "gnu")] +#[allow(clippy::too_many_arguments)] +pub(crate) fn gc_cfi_step( + pc: u64, + rsp: u64, + rbp: u64, + low: u64, + top: u64, + runtime: &GcCfiObject, + scratch: &mut GcCfiScratch, +) -> GcCfiStep { + // The pc is a return address; the row that describes the frame is the + // one covering the call instruction. + let site = pc.wrapping_sub(1); + let tables = if runtime.contains(site) { + runtime.tables + } else { + gc_cfi_object(site).tables + }; + let Some(tables) = tables else { + return GcCfiStep::Unreadable; + }; + let Some(Some((start, fde))) = hdr_function(&tables, site) else { + return GcCfiStep::Unreadable; + }; + let Some(row) = fde_row(&tables, site, start, fde, &mut scratch.cies) else { + return GcCfiStep::Unreadable; + }; + match row { + Row::Step { ra_off: None, .. } => GcCfiStep::Outermost, + Row::Step { .. } => match apply(row, Frame { pc, rsp, rbp }, top, low) { + Some(caller) => GcCfiStep::Caller { + pc: caller.pc, + rsp: caller.rsp, + rbp: caller.rbp, + }, + None => GcCfiStep::Unreadable, + }, + Row::NoFde | Row::Stop => GcCfiStep::Unreadable, + } +} + +/// `(start, end)` of the function the unwind tables say contains `addr`. +#[cfg(target_env = "gnu")] +pub(crate) fn cfi_function_extent(addr: u64) -> Option<(u64, u64)> { + let tables = gc_cfi_object(addr).tables?; + let (start, fde) = hdr_function(&tables, addr)??; + let (_, _, range, _) = fde_header(&tables, fde, &mut CieMemo::default())?; + let end = start.checked_add(range)?; + (addr < end).then_some((start, end)) +} + +/// The module that holds `pc`, for a diagnostic: its path, or `None`. +#[cfg(target_env = "gnu")] +pub(crate) fn module_name(pc: u64) -> Option { + // SAFETY: plain-old-data out parameter; dladdr only reads loader state. + let mut info: libc::Dl_info = unsafe { core::mem::zeroed() }; + if unsafe { libc::dladdr(pc as *const core::ffi::c_void, &mut info) } == 0 + || info.dli_fname.is_null() + { + return None; + } + Some( + unsafe { std::ffi::CStr::from_ptr(info.dli_fname) } + .to_string_lossy() + .into_owned(), + ) +} + #[cfg(test)] mod tests { use super::*; @@ -959,6 +1218,7 @@ mod tests { /// own shapes): the start, a few prologue offsets, the middle, the end. #[test] fn direct_rows_match_gimli_across_every_fde() { + let _loader = crate::test_support::loaded_objects_test_lock(); let walker = locked_walker().expect("walker"); let mut ctx: UnwindContext = UnwindContext::new_in(); let mut cies = CieMemo::default(); diff --git a/crates/perry-runtime/src/gc/mod.rs b/crates/perry-runtime/src/gc/mod.rs index 339946e539b..ffbb357bfc9 100644 --- a/crates/perry-runtime/src/gc/mod.rs +++ b/crates/perry-runtime/src/gc/mod.rs @@ -311,8 +311,10 @@ pub(crate) mod poll_arm; pub(crate) mod schedule; pub(crate) use instruments::note_loop_poll_reached; pub use instruments::{copying_minor_cycles, loop_polls_reached, moved_objects_total}; +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] +pub(crate) use poll_arm::poll_armed; pub use poll_arm::PERRY_GC_POLL_ARMED; -pub(crate) use poll_arm::{arm_poll, disarm_poll, poll_armed, resolve_poll_seed}; +pub(crate) use poll_arm::{arm_poll, disarm_poll, resolve_poll_seed}; pub use schedule::{ gc_schedule_forced_collections, gc_schedule_safepoints, schedule_liveness_report, schedule_polls_paced, diff --git a/crates/perry-runtime/src/gc/native_stack_scan.rs b/crates/perry-runtime/src/gc/native_stack_scan.rs index 7347c42d7c5..6f80050cb4b 100644 --- a/crates/perry-runtime/src/gc/native_stack_scan.rs +++ b/crates/perry-runtime/src/gc/native_stack_scan.rs @@ -59,10 +59,23 @@ pub(super) fn run_native_stack_scan() { let size = unsafe { libc::pthread_get_stacksize_np(libc::pthread_self()) } as usize; (top - size, top) }; - #[cfg(not(target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "android"))] // `_stack_lo`: same unused-on-this-arm shape #8298 underscore-fixed on the // macOS arm; the Linux arm kept the bare name and turned `warnings` // (-D warnings, host-compatible scope) red on main. + let (_stack_lo, stack_hi) = { + // The thread's real stack top. A fixed 256 KB above this frame ran + // off the top of a shallow main-thread stack and faulted on the first + // scheduled collection, so a run with the abort knob crashed before + // it could report anything. + let top = crate::native_stack::stack_top(); + if top == 0 { + eprintln!("[native-stack-scan] skipped: this thread's stack bounds are unknown"); + return; + } + (0usize, top) + }; + #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "android")))] let (_stack_lo, stack_hi) = { // Fallback: use a local variable address and scan 256KB upward. let marker: usize = 0; diff --git a/crates/perry-runtime/src/gc/policy.rs b/crates/perry-runtime/src/gc/policy.rs index 361db17ab6c..40048b2b462 100644 --- a/crates/perry-runtime/src/gc/policy.rs +++ b/crates/perry-runtime/src/gc/policy.rs @@ -4165,18 +4165,57 @@ pub(crate) fn set_safepoint_pending(pending: bool) { /// either the word was armed or the module came from a path that emits the /// bare call. Both must still work, which is why the check is repeated here /// rather than delegated to the compiler. +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] #[no_mangle] pub extern "C" fn js_gc_loop_safepoint() { if !super::poll_armed() { return; } - js_gc_loop_safepoint_armed(); + js_gc_loop_safepoint_armed(0, 0, 0); +} + +/// x86-64 Linux: the same unarmed path written out (load the word through the +/// GOT, compare, return: one instruction fewer than rustc's load, test, +/// branch), so the armed path can hand the body its caller's frame +/// (`roots::walk_origin`) in the argument registers and tail-jump to it. A +/// collection the poll runs then walks from the JS frame that polled instead +/// of stepping the collector's own frames. The shim never owns a frame: both +/// paths leave through `ret` or `jmp` with the stack as the caller made it. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[unsafe(naked)] +#[no_mangle] +pub extern "C" fn js_gc_loop_safepoint() { + core::arch::naked_asm!( + "mov rax, qword ptr [rip + {armed}@GOTPCREL]", + "cmp dword ptr [rax], 0", + "jne 2f", + "ret", + "2:", + "mov rdi, qword ptr [rsp]", + "lea rsi, [rsp + 8]", + "mov rdx, rbp", + "jmp {body}", + armed = sym super::PERRY_GC_POLL_ARMED, + body = sym js_gc_loop_safepoint_armed, + ); +} + +/// The moving minor of an armed poll, walking from the poll's caller when the +/// shim passed one (`pc != 0`). Offered only here, where a collection can +/// start: most armed polls return before it. +#[inline(always)] +fn gc_safepoint_moving_minor_from(pc: usize, sp: usize, fp: usize) -> bool { + let origin = super::WalkOrigin { pc, sp, fp }; + let _origin = (pc != 0).then(|| super::EntryWalkOrigin::enter(&origin)); + gc_safepoint_moving_minor() } /// Out of line so the hot entry point above stays a load, a compare and a -/// return — no frame, no spills. +/// return — no frame, no spills. `(pc, sp, fp)` is the poll's caller frame +/// on x86-64 Linux, where the shim tail-jumps here with it in the argument +/// registers, and `(0, 0, 0)` elsewhere. #[inline(never)] -fn js_gc_loop_safepoint_armed() { +extern "C" fn js_gc_loop_safepoint_armed(pc: usize, sp: usize, fp: usize) { // Releases the startup seed unless a resolved seed wants the poll kept // reachable. Must run before the opt-in check below: a build with the polls // killed still has to get the word back to zero, or every back-edge keeps @@ -4227,14 +4266,14 @@ fn js_gc_loop_safepoint_armed() { // candidate costs a full stride of new allocation on top of whatever // survived — see `gc/schedule.rs` for why this is a high-water mark and // not a delta. - if gc_safepoint_moving_minor() { + if gc_safepoint_moving_minor_from(pc, sp, fp) { super::schedule::note_schedule_poll_collection( crate::arena::copying_from_space_in_use_bytes(), ); } return; } - gc_safepoint_moving_minor(); + gc_safepoint_moving_minor_from(pc, sp, fp); } struct BudgetedGcStepGuard; @@ -5102,8 +5141,29 @@ pub(super) static GC_UNSAFE_WARNED: std::sync::atomic::AtomicBool = /// Manual GC trigger (callable from TypeScript as `gc()`). Skipped when /// worker threads are active (see GC_UNSAFE_ZONES). +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] #[no_mangle] pub extern "C" fn js_gc_collect() { + js_gc_collect_body(); +} + +/// x86-64 Linux: `gc()` hands its caller's frame to the collections it runs, +/// exactly as the back-edge poll does (see `js_gc_loop_safepoint`). +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[unsafe(naked)] +#[no_mangle] +pub extern "C" fn js_gc_collect() { + super::offering_entry_asm!(0, js_gc_collect_from) +} + +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +extern "C" fn js_gc_collect_from(pc: usize, sp: usize, fp: usize) { + let origin = super::WalkOrigin { pc, sp, fp }; + let _origin = super::EntryWalkOrigin::enter(&origin); + js_gc_collect_body(); +} + +fn js_gc_collect_body() { if manual_gc_blocked_by_unsafe_zone() { return; } diff --git a/crates/perry-runtime/src/gc/poll_arm.rs b/crates/perry-runtime/src/gc/poll_arm.rs index 70059456211..b8cd02ddd65 100644 --- a/crates/perry-runtime/src/gc/poll_arm.rs +++ b/crates/perry-runtime/src/gc/poll_arm.rs @@ -78,6 +78,12 @@ pub static PERRY_GC_POLL_ARMED: AtomicU32 = AtomicU32::new(1); /// The whole fast path: one relaxed load of an ordinary global. No TLS, no /// acquire fence, no read-modify-write. +/// +/// x86-64 Linux reads the word in `js_gc_loop_safepoint`'s own assembly. +#[cfg(any( + test, + not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")) +))] #[inline] pub(crate) fn poll_armed() -> bool { PERRY_GC_POLL_ARMED.load(Ordering::Relaxed) != 0 diff --git a/crates/perry-runtime/src/gc/roots.rs b/crates/perry-runtime/src/gc/roots.rs index 7d9d34d2ba5..7cfc01c2706 100644 --- a/crates/perry-runtime/src/gc/roots.rs +++ b/crates/perry-runtime/src/gc/roots.rs @@ -16,12 +16,16 @@ pub(super) use stack_maps::install_verify_walker; mod stack_roots; pub(crate) use stack_maps::census_rows::stack_map_index_census; mod temp_roots; +mod walk_origin; pub(super) use stack_maps::ensure_built as ensure_stack_maps_built; pub(super) use stack_maps::initialize as initialize_stack_maps; pub(super) use stack_maps::native_maps_active as native_stack_maps_active; pub(super) use stack_maps::publish_rewrite_walk_stats as stack_maps_publish_rewrite_walk_stats; pub(super) use stack_maps::record_native_stack_walk_source; pub(super) use stack_maps::verify_native_slots_post_walk as stack_maps_native_slot_verify; +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +pub(crate) use walk_origin::offering_entry_asm; +pub(crate) use walk_origin::{EntryOrigin as EntryWalkOrigin, WalkOrigin}; pub use rooted_values::RootedValues; pub(super) use runtime_handles::{ diff --git a/crates/perry-runtime/src/gc/roots/stack_maps.rs b/crates/perry-runtime/src/gc/roots/stack_maps.rs index fff66b5fb94..8d5823c9727 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps.rs @@ -136,6 +136,9 @@ struct StackMapIndex { /// can share a relocated address and each brings its own records. functions: Vec, eager: Option, + /// Where the x86-64 frame walk ends; resolved with the index. + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + root_frames: frame_walk::RootFrames, } /// The v4 whole-section index. @@ -741,6 +744,10 @@ pub(in crate::gc) struct NativeStackWalkStats { pub(in crate::gc) locations_visited: usize, pub(in crate::gc) fp_walks: usize, pub(in crate::gc) fallback_walks: usize, + /// x86-64 frame walk: frames stepped by their unwind row, and JS frames + /// stepped by their frame record. + pub(in crate::gc) cfi_steps: usize, + pub(in crate::gc) record_steps: usize, } #[inline] @@ -923,6 +930,8 @@ fn build_index_from_sections( records, functions, eager, + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + root_frames: frame_walk::RootFrames::resolve(), } } @@ -1351,7 +1360,7 @@ fn visit_fast( // exotic register, and it reuses the mid-walk bail this walker // already performs for `x19_is_body_sp` and an unvalidated // `caller_fp`. - if let Some(stats) = fp_chain::visit(index, &mut |root: ResolvedRoot| { + if let Some(stats) = fast_walk::visit(index, &mut |root: ResolvedRoot| { root.visit_with_context(visit) }) { return stats; @@ -1430,6 +1439,7 @@ mod unwind { index: &'a StackMapIndex, visit: &'a mut F, stats: NativeStackWalkStats, + last_ip: usize, } pub(super) fn visit( @@ -1443,16 +1453,46 @@ mod unwind { walks: 1, ..NativeStackWalkStats::default() }, + last_ip: 0, }; - unsafe { + let code = unsafe { _Unwind_Backtrace( walk_frame::, (&mut state as *mut WalkState<'_, _>).cast::(), - ); + ) + }; + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + if code != URC_END_OF_STACK { + stopped_short(code, state.last_ip); } + #[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] + let _ = code; state.stats } + /// `_Unwind_Backtrace` reached the end of the thread's frames. + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + const URC_END_OF_STACK: i32 = 5; + + /// The unwinder stopped before the stack's end: a frame without usable + /// unwind tables. Every frame outward of it went unscanned, so its roots + /// were never visited, and under a moving collection the mutator would + /// resume on from-space pointers. That is corruption, not a short walk; + /// it ends here, naming the frame and the object it belongs to. + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + #[cold] + fn stopped_short(code: i32, last_ip: usize) -> ! { + // `{module:?}`: `None` when no loaded object holds the pc. + let module = crate::eh_walker::module_name(last_ip as u64); + eprintln!( + "perry: the GC root scan cannot walk this thread's stack past the frame \ + returning to {last_ip:#x} in {module:?} (_Unwind_Backtrace code {code}): that \ + object has no unwind tables the walk can follow there, so every frame \ + outward of it would go unscanned and its roots be lost." + ); + std::process::abort(); + } + fn walk_trace_enabled() -> bool { static ON: std::sync::OnceLock = std::sync::OnceLock::new(); // `env_flag_enabled`, not `var_os(..).is_some()`: presence-testing makes @@ -1471,6 +1511,7 @@ mod unwind { let state = &mut *argument.cast::>(); state.stats.frames_visited = state.stats.frames_visited.saturating_add(1); let ip = _Unwind_GetIP(context); + state.last_ip = ip; if walk_trace_enabled() { let mut info: libc::Dl_info = std::mem::zeroed(); let name = @@ -1741,9 +1782,12 @@ mod fp_chain { } } -#[cfg(not(all( - any(target_vendor = "apple", target_os = "linux"), - target_arch = "aarch64" +#[cfg(not(any( + all( + any(target_vendor = "apple", target_os = "linux"), + target_arch = "aarch64" + ), + all(target_arch = "x86_64", target_os = "linux", target_env = "gnu") )))] mod fp_chain { use super::*; @@ -1756,6 +1800,18 @@ mod fp_chain { } } +// x86-64 Linux has no frame-pointer chain through the runtime: its walk steps +// JS frames by their frame records and everything else by its unwind row. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[path = "stack_maps_frame_walk.rs"] +mod frame_walk; + +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] +use fp_chain as fast_walk; +/// The walk `Fast` mode runs before (if it fails closed) the system unwinder. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +use frame_walk as fast_walk; + // The compact-map decoder. Its own file because this one is close to the // 2000-line cap; the re-export is named rather than a glob because a glob does // not propagate through the transitive re-exports this module sits behind. @@ -1809,6 +1865,12 @@ mod decode_tests; #[path = "stack_maps_walker_agreement.rs"] mod walker_agreement; +// The x86-64 frame walk against the unwinder over probe frames of a chosen +// layout, with its sabotage arms. +#[cfg(all(test, target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[path = "stack_maps_frame_walk_tests.rs"] +mod frame_walk_tests; + pub(crate) mod census_rows; #[cfg(test)] diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs b/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs index 9ce315912ed..acf4edc54f5 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs @@ -361,6 +361,7 @@ mod tests { ); let path = CString::new(library.as_os_str().as_bytes()).expect("NUL-free dylib path"); + let _loader = crate::test_support::loaded_objects_test_lock(); let handle = unsafe { libc::dlopen(path.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL) }; assert!(!handle.is_null(), "dlopen failed"); @@ -441,6 +442,7 @@ mod tests { ); let path = CString::new(library.as_os_str().as_bytes()).expect("NUL-free dylib path"); + let _loader = crate::test_support::loaded_objects_test_lock(); let handle = unsafe { libc::dlopen(path.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL) }; assert!(!handle.is_null(), "dlopen failed"); std::fs::remove_file(&library).expect("unlink loaded dylib"); diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk.rs b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk.rs new file mode 100644 index 00000000000..dec313886c3 --- /dev/null +++ b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk.rs @@ -0,0 +1,270 @@ +//! The x86-64 Linux root walk: frames stepped by kind, not by the system +//! unwinder. +//! +//! `_Unwind_Backtrace` costs one `_Unwind_Find_FDE` search plus a libgcc CFI +//! interpretation for every frame, ~1,570 instructions, and most frames of a +//! collection's stack are not JS at all: the collector's own Rust frames +//! above the poll and the C start frames below `main`. This walk avoids all +//! three costs. +//! +//! * **Start.** A scan made while an entry offers its caller frame +//! ([`super::super::walk_origin`]) starts there, at the JS frame that called +//! the poll or `gc()`. Otherwise the walk starts at its own frame. +//! * **JS frame** (a pc `match_records` finds records for): its roots resolve +//! against its exact `rbp` (FP-based) and `sp` (SP-based: its stack pointer +//! at the call, the callee's CFA). It steps by its frame record, two loads: +//! every generated function is `"frame-pointer"="non-leaf"` and a frame on +//! the stack is at a call. A record based on any other register fails the +//! walk closed, and so does a frame whose map size contradicts its record. +//! * **Any other frame** (Rust, a shim, libc, an addon) steps by its unwind +//! row through the owned direct decoder ([`crate::eh_walker::gc_cfi_step`]): +//! one `.eh_frame_hdr` search in the object `_dl_find_object` names. Only +//! `rsp`, `rbp` and the return address are tracked; nothing outlives the +//! walk. A row the decoder cannot follow fails the walk closed. +//! * **End.** After `main`'s frame, or at the worker base trap +//! `perry_trap_call_base`: everything outward of either is the C runtime or +//! std's thread start. Both ranges are resolved once with the index; a frame +//! costs two compares. A thread entered some other way ends where its +//! tables do, as before. +//! +//! Every frame is validated (aligned, strictly outward, below the stack top) +//! before any of its slots is visited. Failing closed returns `None` and the +//! caller re-walks through the system unwinder; the slots visited so far were +//! real slots of validated frames, and visiting a slot twice is idempotent. + +use super::*; +use crate::eh_walker::{gc_cfi_object, gc_cfi_step, GcCfiObject, GcCfiScratch, GcCfiStep}; + +const DWARF_REG_RBP: u16 = 6; + +/// What the walk needs of the image, resolved once with the index: the +/// frames outward of which no JS can run (`main`'s and the worker base +/// trap's, as `(start, end)` code ranges from the unwind tables; empty where +/// a function is absent, as in a unit test), and the runtime's own object, +/// whose unwind tables every Rust frame's step searches. +#[derive(Clone, Copy, Debug, Default)] +pub(super) struct RootFrames { + ranges: [(usize, usize); 2], + runtime: GcCfiObject, +} + +impl RootFrames { + pub(super) fn resolve() -> Self { + let main = crate::eh_walker::executable_main() as usize; + let base = crate::exception::worker_base_shim_addr(); + let extent = |address: usize| { + if address == 0 { + return (0, 0); + } + crate::eh_walker::cfi_function_extent(address as u64) + .map_or((0, 0), |(start, end)| (start as usize, end as usize)) + }; + Self { + ranges: [extent(main), extent(base)], + runtime: gc_cfi_object(Self::resolve as fn() -> Self as usize as u64), + } + } + + #[cfg(test)] + pub(super) fn of(ranges: [(usize, usize); 2]) -> Self { + Self { + ranges, + ..Self::resolve() + } + } + + #[inline(always)] + fn contains(&self, pc: usize) -> bool { + let [(a, b), (c, d)] = self.ranges; + pc.wrapping_sub(a) < b.wrapping_sub(a) || pc.wrapping_sub(c) < d.wrapping_sub(c) + } +} + +// Sabotage for `stack_maps_frame_walk_tests.rs`: skip the frame one CFI step +// lands on, so the agreement test can show it catches a walk that loses a +// frame. +#[cfg(test)] +crate::perry_thread_local! { + pub(super) static SKIP_ONE_CFI_STEP: std::cell::Cell = const { std::cell::Cell::new(false) }; +} + +#[inline(always)] +fn walk_trace_enabled() -> bool { + static ON: std::sync::OnceLock = std::sync::OnceLock::new(); + *crate::once_init::get_or_init(&ON, || { + crate::gc::env_flag_enabled("PERRY_GC_STACKMAP_TRACE") + }) +} + +// One body for every visitor: not generic, so each collector phase's scan +// does not carry its own copy of the walk. +#[inline(never)] +pub(super) fn visit( + index: &StackMapIndex, + visit: &mut dyn FnMut(ResolvedRoot), +) -> Option { + let top = crate::native_stack::stack_top(); + if top == 0 { + return None; + } + let origin = super::super::walk_origin::offered(); + let (mut pc, mut sp, mut fp) = match origin { + Some(origin) => (origin.pc, origin.sp, origin.fp), + None => { + // This frame stays live for the whole walk, so its own registers + // are a valid start; it is stepped by its unwind row like any + // Rust frame. + let here = crate::eh_walker::capture_here(); + ( + here.pc as usize, + here.regs[crate::eh_walker::SP_INDEX] as usize, + here.regs[crate::eh_walker::FP_INDEX] as usize, + ) + } + }; + let low = sp; + let mut stats = NativeStackWalkStats { + walks: 1, + fp_walks: 1, + ..NativeStackWalkStats::default() + }; + let mut scratch = GcCfiScratch::default(); + let roots = &index.root_frames; + let trace = walk_trace_enabled(); + loop { + if sp & 7 != 0 || sp >= top || pc == 0 { + return None; + } + stats.frames_visited = stats.frames_visited.saturating_add(1); + let is_root = roots.contains(pc); + if let Some(matched) = index.match_records(pc) { + // A JS frame. Its record and its frame size are checked before + // any slot of it is visited. + if fp & 7 != 0 || fp < sp || fp.checked_add(16)? > top { + return None; + } + // The map's frame size covers the frame from `sp` up to and + // including the saved `rbp`: equal for a fixed frame, smaller + // when the frame realigns or adjusts `sp` around the call. More + // means this `rbp` is not this frame's. + if let Some(size) = index.frame_size(&matched) { + if sp.checked_add(size as usize)? > fp + 8 { + return None; + } + } + let mut foreign_base = false; + let mut records = index.matched(&matched); + while let Some(record) = records.next() { + stats.records_matched = stats.records_matched.saturating_add(1); + let mut resolve = |location: &StackMapLocation| { + let base = match location.dwarf_reg { + DWARF_REG_RBP => fp, + ARCH_DWARF_SP => sp, + _ => { + foreign_base = true; + return None; + } + }; + let address = if location.offset < 0 { + base.checked_sub(location.offset.unsigned_abs() as usize) + } else { + base.checked_add(location.offset as usize) + }; + address.map(|address| (address, base)) + }; + unsafe { + visit_record_slots(&record, pc, &mut resolve, &mut stats, visit); + } + // A base this walk cannot reconstruct (a realigned frame's + // base pointer): the unwinder resolves every register. + if foreign_base { + return None; + } + } + if trace { + eprintln!( + "[gc-stackmap-walk] fast frame {} ip={pc:#x} js", + stats.frames_visited + ); + } + if is_root { + break; + } + // SAFETY: `fp..fp+16` was bounds-checked above. + let (caller_fp, caller_pc) = + unsafe { (*(fp as *const usize), *((fp + 8) as *const usize)) }; + stats.record_steps = stats.record_steps.saturating_add(1); + if caller_pc == 0 { + break; + } + pc = caller_pc; + sp = fp + 16; + fp = caller_fp; + continue; + } + if trace { + eprintln!( + "[gc-stackmap-walk] fast frame {} ip={pc:#x} cfi", + stats.frames_visited + ); + } + if is_root { + break; + } + stats.cfi_steps = stats.cfi_steps.saturating_add(1); + match gc_cfi_step( + pc as u64, + sp as u64, + fp as u64, + low as u64, + top as u64, + &roots.runtime, + &mut scratch, + ) { + GcCfiStep::Caller { + pc: caller_pc, + rsp, + rbp, + } => { + if rsp as usize <= sp { + return None; + } + pc = caller_pc as usize; + sp = rsp as usize; + fp = rbp as usize; + #[cfg(test)] + if SKIP_ONE_CFI_STEP.with(|skip| skip.replace(false)) { + match gc_cfi_step( + pc as u64, + sp as u64, + fp as u64, + low as u64, + top as u64, + &roots.runtime, + &mut scratch, + ) { + GcCfiStep::Caller { pc: next, rsp, rbp } => { + pc = next as usize; + sp = rsp as usize; + fp = rbp as usize; + } + _ => return None, + } + } + } + GcCfiStep::Outermost => break, + GcCfiStep::Unreadable => return None, + } + } + if trace { + eprintln!( + "[gc-stackmap-walk] fast walk frames={} records={} cfi_steps={} record_steps={} origin={}", + stats.frames_visited, + stats.records_matched, + stats.cfi_steps, + stats.record_steps, + origin.is_some() + ); + } + Some(stats) +} diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs new file mode 100644 index 00000000000..0f413c52315 --- /dev/null +++ b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs @@ -0,0 +1,405 @@ +//! The x86-64 frame walk against the system unwinder, over frames whose +//! layout we chose. +//! +//! Two JS-shaped probe frames (`push rbp; mov rbp, rsp; sub rsp, 32`, the +//! frame-record prologue every generated function has) each write a sentinel +//! into the word an SP-based record names and another into the word an +//! FP-based record names, then call back into Rust. The outer probe's Rust +//! callback calls the inner probe, so a Rust frame sits between the two JS +//! frames: the walk must step it by its unwind row to reach the outer roots. +//! The walking callback runs both walkers while every probe frame is live and +//! keeps only what they read. +//! +//! What is asserted is the CONTENTS of the resolved words, not just that the +//! walkers agree: two walkers that agree on wrong words would pass a set +//! comparison. And each discriminating check has a sabotage arm that makes it +//! fail: a walk that skips one Rust frame step, and records whose base +//! register the walk cannot reconstruct. + +use std::cell::{Cell, RefCell}; + +use super::{frame_walk, unwind, ResolvedRoot, StackMapIndex}; + +const SENTINEL_SP: u64 = 0xCAFE_F00D; +const SENTINEL_FP: u64 = 0xCAFE_FEED; +const DWARF_RBP: u16 = 6; +const DWARF_RSP: u16 = 7; +/// `push rbp` plus `sub rsp, 32`: the map's frame size for both probes. +const PROBE_STACK_SIZE: u32 = 40; + +// The callback gets (body_sp, return_address, rbp) of the probe frame: its +// stack pointer at the call, the exact pc a record matches on, and its frame +// pointer. `rdi` carries the callback in, `rsi` an argument passed through. +core::arch::global_asm!( + ".p2align 4", + ".globl perry_frame_walk_probe_outer", + "perry_frame_walk_probe_outer:", + ".cfi_startproc", + "push rbp", + ".cfi_def_cfa_offset 16", + ".cfi_offset rbp, -16", + "mov rbp, rsp", + ".cfi_def_cfa_register rbp", + "sub rsp, 32", + "mov rax, 0xCAFEF00D", + "mov qword ptr [rsp + 8], rax", + "mov rax, 0xCAFEFEED", + "mov qword ptr [rbp - 8], rax", + "mov rax, rdi", + "mov rcx, rsi", + "mov rdi, rsp", + "lea rsi, [rip + 2f]", + "mov rdx, rbp", + "call rax", + "2:", + "mov rsp, rbp", + "pop rbp", + ".cfi_def_cfa rsp, 8", + "ret", + ".cfi_endproc", + ".p2align 4", + ".globl perry_frame_walk_probe_inner", + "perry_frame_walk_probe_inner:", + ".cfi_startproc", + "push rbp", + ".cfi_def_cfa_offset 16", + ".cfi_offset rbp, -16", + "mov rbp, rsp", + ".cfi_def_cfa_register rbp", + "sub rsp, 32", + "mov rax, 0xCAFEF00D", + "mov qword ptr [rsp + 8], rax", + "mov rax, 0xCAFEFEED", + "mov qword ptr [rbp - 8], rax", + "mov rax, rdi", + "mov rcx, rsi", + "mov rdi, rsp", + "lea rsi, [rip + 3f]", + "mov rdx, rbp", + "call rax", + "3:", + "mov rsp, rbp", + "pop rbp", + ".cfi_def_cfa rsp, 8", + "ret", + ".cfi_endproc", + // A frame whose CFA is based on rbx: legal CFI the system unwinder + // follows and the direct decoder declines, so it stands for every row the + // fast walk cannot read. + ".p2align 4", + ".globl perry_frame_walk_probe_rbx_cfa", + "perry_frame_walk_probe_rbx_cfa:", + ".cfi_startproc", + "push rbx", + ".cfi_def_cfa_offset 16", + ".cfi_offset rbx, -16", + "mov rbx, rsp", + ".cfi_def_cfa_register rbx", + "sub rsp, 16", + "mov rax, rdi", + "mov rcx, rsi", + "mov rdi, rsp", + "lea rsi, [rip + 4f]", + "mov rdx, rbp", + "call rax", + "4:", + "mov rsp, rbx", + "pop rbx", + ".cfi_def_cfa rsp, 8", + "ret", + ".cfi_endproc", +); + +type Callback = extern "C" fn(body_sp: usize, return_address: usize, rbp: usize); + +unsafe extern "C" { + fn perry_frame_walk_probe_outer(callback: Callback); + fn perry_frame_walk_probe_inner(callback: Callback); + fn perry_frame_walk_probe_rbx_cfa(callback: Callback); +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +struct Probe { + body_sp: usize, + return_address: usize, + rbp: usize, +} + +impl Probe { + fn roots(self) -> [usize; 2] { + [self.body_sp + 8, self.rbp - 8] + } +} + +#[derive(Clone, Copy, Debug)] +struct Sample { + address: usize, + word: u64, +} + +fn sample(address: usize) -> Sample { + Sample { + address, + word: unsafe { std::ptr::read(address as *const u64) }, + } +} + +#[derive(Clone, Copy)] +enum Scenario { + /// outer probe -> Rust -> inner probe -> walk. + TwoJsFrames, + /// outer probe -> Rust -> rbx-CFA frame -> walk. + UnreadableRow, +} + +#[derive(Default)] +struct State { + outer: Probe, + inner: Probe, + /// Base register every record names (sabotage: one the walk cannot use). + foreign_base: bool, + /// Start the fast walk at the inner probe, as a poll entry would. + from_origin: bool, + ran: bool, + fast: Option<(Vec, super::NativeStackWalkStats)>, + slow: Vec, +} + +crate::perry_thread_local! { + static STATE: RefCell = RefCell::new(State::default()); + static SCENARIO: Cell = const { Cell::new(Scenario::TwoJsFrames) }; +} + +fn record_offset(probe: Probe, function: usize) -> u32 { + u32::try_from(probe.return_address - function).expect("return address inside its probe") +} + +/// The index both walks get: a real v8 blob naming the probe frames' roots. +fn index_for(state: &State, with_inner: bool) -> StackMapIndex { + let outer_fn = perry_frame_walk_probe_outer as *const () as usize; + let inner_fn = perry_frame_walk_probe_inner as *const () as usize; + let (fp_reg, sp_reg) = if state.foreign_base { + (3u16, 3u16) // rbx + } else { + (DWARF_RBP, DWARF_RSP) + }; + let roots = vec![(sp_reg, 8), (fp_reg, -8)]; + let mut functions = vec![( + outer_fn as u64, + PROBE_STACK_SIZE, + vec![(record_offset(state.outer, outer_fn), roots.clone())], + )]; + if with_inner { + functions.push(( + inner_fn as u64, + PROBE_STACK_SIZE, + vec![(record_offset(state.inner, inner_fn), roots)], + )); + } + functions.sort_by_key(|function| function.0); + let origin = functions[0].0 as usize; + let blob = super::lazy::test_blob_multi_at(origin as u64, &functions); + let mut index = super::build_index_from_sections( + vec![Box::leak(blob.into_boxed_slice())], + &[origin], + super::IndexMode::Lazy, + ); + // The outer probe stands in for `main`: the walk ends after it, instead of + // stepping the test harness's thread start. + let (start, end) = crate::eh_walker::cfi_function_extent(outer_fn as u64) + .expect("the outer probe has unwind tables"); + index.root_frames = frame_walk::RootFrames::of([(start as usize, end as usize), (0, 0)]); + index +} + +/// The walking callback: both walkers, while every probe frame is live. +/// Nothing here may panic (an `extern "C"` frame aborts on unwind). +extern "C" fn walk_here(body_sp: usize, return_address: usize, rbp: usize) { + let scenario = SCENARIO.with(Cell::get); + let (index, origin, foreign_base) = STATE.with(|cell| { + let mut state = cell.borrow_mut(); + let with_inner = matches!(scenario, Scenario::TwoJsFrames); + if with_inner { + state.inner = Probe { + body_sp, + return_address, + rbp, + }; + } + let origin = (with_inner && state.from_origin).then_some(super::super::WalkOrigin { + pc: return_address, + sp: body_sp, + fp: rbp, + }); + (index_for(&state, with_inner), origin, state.foreign_base) + }); + let mut fast = Vec::new(); + let fast_stats = { + let _origin = origin.as_ref().map(super::super::EntryWalkOrigin::enter); + frame_walk::visit(&index, &mut |root: ResolvedRoot| { + fast.push(sample(root.address)) + }) + }; + let mut slow = Vec::new(); + // An rbx-based record names words relative to whatever rbx holds here, + // which the unwinder would resolve and the sample would read: only the + // fast walk's refusal is under test in that case. + if !foreign_base { + unwind::visit(&index, &mut |root: ResolvedRoot| { + slow.push(sample(root.address)) + }); + } + STATE.with(|cell| { + let mut state = cell.borrow_mut(); + state.ran = true; + state.fast = fast_stats.map(|stats| (fast, stats)); + state.slow = slow; + }); +} + +extern "C" fn outer_body(body_sp: usize, return_address: usize, rbp: usize) { + STATE.with(|cell| { + cell.borrow_mut().outer = Probe { + body_sp, + return_address, + rbp, + } + }); + // A Rust frame between the two probe frames: this one. + match SCENARIO.with(Cell::get) { + Scenario::TwoJsFrames => unsafe { perry_frame_walk_probe_inner(walk_here) }, + Scenario::UnreadableRow => unsafe { perry_frame_walk_probe_rbx_cfa(walk_here) }, + } + std::hint::black_box(()); +} + +fn drive(scenario: Scenario, foreign_base: bool, from_origin: bool) -> State { + SCENARIO.with(|cell| cell.set(scenario)); + STATE.with(|cell| { + *cell.borrow_mut() = State { + foreign_base, + from_origin, + ..State::default() + } + }); + unsafe { perry_frame_walk_probe_outer(outer_body) }; + let state = STATE.with(|cell| std::mem::take(&mut *cell.borrow_mut())); + assert!(state.ran, "the walking callback never ran"); + state +} + +/// The agreement the walks must reach: both land on exactly the probes' +/// root words, each holding its sentinel. An `Err` names what went wrong. +fn agreement(state: &State, expected: &[usize]) -> Result<(), String> { + let mut expected: Vec = expected.to_vec(); + expected.sort_unstable(); + let Some((fast, _)) = state.fast.as_ref() else { + return Err("the fast walk declined".into()); + }; + for (walker, samples) in [("fast", fast), ("unwinder", &state.slow)] { + let mut addresses: Vec = samples.iter().map(|s| s.address).collect(); + addresses.sort_unstable(); + addresses.dedup(); + if addresses != expected { + return Err(format!( + "{walker} walk resolved {addresses:#x?}, expected {expected:#x?}" + )); + } + for Sample { address, word } in samples { + if *word != SENTINEL_SP && *word != SENTINEL_FP { + return Err(format!( + "{walker} walk resolved {address:#x}, holding {word:#x}, not a sentinel" + )); + } + } + } + Ok(()) +} + +#[test] +fn the_fast_walk_and_the_unwinder_resolve_both_frames_across_a_rust_frame() { + let state = drive(Scenario::TwoJsFrames, false, false); + let expected = [state.inner.roots(), state.outer.roots()].concat(); + agreement(&state, &expected).unwrap(); + let (_, stats) = state.fast.as_ref().unwrap(); + // The inner probe steps by its frame record; the Rust frames (the walk's + // own, the callback, `outer_body`) by their rows. + assert!(stats.record_steps >= 1, "{stats:?}"); + assert!(stats.cfi_steps >= 3, "{stats:?}"); +} + +#[test] +fn a_walk_from_the_entry_origin_skips_the_collector_frames() { + let state = drive(Scenario::TwoJsFrames, false, true); + let expected = [state.inner.roots(), state.outer.roots()].concat(); + agreement(&state, &expected).unwrap(); + let (_, stats) = state.fast.as_ref().unwrap(); + // inner probe (record step) -> outer_body (one row) -> outer probe (end). + assert_eq!( + (stats.frames_visited, stats.record_steps, stats.cfi_steps), + (3, 1, 1), + "{stats:?}" + ); +} + +/// Sabotage: a walk that skips one Rust frame step. The agreement check must +/// fail, or it proves nothing. +#[test] +fn skipping_one_rust_frame_step_is_caught() { + frame_walk::SKIP_ONE_CFI_STEP.with(|skip| skip.set(true)); + let state = drive(Scenario::TwoJsFrames, false, true); + frame_walk::SKIP_ONE_CFI_STEP.with(|skip| skip.set(false)); + let expected = [state.inner.roots(), state.outer.roots()].concat(); + let verdict = agreement(&state, &expected); + assert!( + verdict.is_err(), + "a walk that skipped the Rust frame between two JS frames passed the agreement check" + ); +} + +/// A row the direct decoder cannot follow fails the fast walk closed, and the +/// unwinder still finds the outer frame's roots. +#[test] +fn an_unreadable_row_fails_closed_and_the_unwinder_answers() { + let state = drive(Scenario::UnreadableRow, false, false); + assert!( + state.fast.is_none(), + "the fast walk stepped an rbx-based CFA" + ); + let mut slow: Vec = state.slow.iter().map(|s| s.address).collect(); + slow.sort_unstable(); + let mut expected = state.outer.roots().to_vec(); + expected.sort_unstable(); + assert_eq!(slow, expected); + assert!(state + .slow + .iter() + .all(|s| s.word == SENTINEL_SP || s.word == SENTINEL_FP)); +} + +/// A record based on a register other than rbp/rsp fails the walk closed +/// rather than resolving (or dropping) that root. +#[test] +fn a_foreign_base_register_fails_closed() { + let state = drive(Scenario::TwoJsFrames, true, false); + assert!( + state.fast.is_none(), + "the fast walk resolved an rbx-based root" + ); +} + +/// The extent reject in `match_records` returns what the full scan returns. +#[test] +fn a_pc_past_every_record_matches_nothing_in_one_search() { + let state = drive(Scenario::TwoJsFrames, false, false); + let index = index_for(&state, true); + let inner_fn = perry_frame_walk_probe_inner as *const () as usize; + let at = state.inner.return_address; + assert!(index.match_records(at).is_some()); + assert!( + index.match_records(at + 16).is_some(), + "the window still matches" + ); + assert!(index.match_records(at + 17).is_none(), "past the window"); + assert!(index.match_records(inner_fn + 200).is_none()); +} diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_index.rs b/crates/perry-runtime/src/gc/roots/stack_maps_index.rs index ef3e34fac4d..9c3145494de 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_index.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_index.rs @@ -43,6 +43,16 @@ impl StackMapIndex { matched } + /// The map's frame size of the matched function: `None` when it is + /// dynamic (a variable-sized or realigned frame) or the entries sharing + /// the address disagree. + #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] + pub(super) fn frame_size(&self, matched: &lazy::RecordMatch) -> Option { + let mut entries = self.functions.get(matched.functions.clone())?.iter(); + let size = entries.next()?.stack_size; + (size != u32::MAX && entries.all(|entry| entry.stack_size == size)).then_some(size) + } + pub(super) fn matched(&self, matched: &lazy::RecordMatch) -> lazy::MatchedRecords<'_> { lazy::MatchedRecords::new(&self.functions, &self.records, matched) } diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs b/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs index e29718318bf..4ab2b1a0be7 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs @@ -29,7 +29,7 @@ use super::{StackMapDerived, StackMapLocation, DWARF_REG_FP_AARCH64, DWARF_REG_S /// One function's position in the map, in the form the root scan needs. /// -/// 24 bytes, one per function with records: 1.7 MB for claude-code's 72,669, +/// 32 bytes, one per function with records: 2.3 MB for claude-code's 72,669, /// against the ~117 MB of materialised records it replaces. Byte offsets are /// relative to the function's records blob rather than pointers, so the entry /// stays small and a stale entry cannot be dereferenced without going through @@ -47,6 +47,15 @@ pub(super) struct FunctionEntry { pub(super) record_count: u32, /// Index into the index's records-blob list. pub(super) blob: u32, + /// The largest instruction offset of this function's records. An `ip` + /// more than the match window beyond it matches nothing here, which + /// [`match_records`] then says in one search instead of scanning every + /// record offset. Derived from the records at build; with `stack_size` + /// it fills what was padding. + pub(super) max_offset: u32, + /// The map's frame size for this function (`u32::MAX`: dynamic), for the + /// x86-64 frame walk's coherence check. + pub(super) stack_size: u32, } /// Where one record's live set lives in the stream. @@ -425,18 +434,36 @@ pub(super) fn parse_function_table<'a>( if function.record_count == 0 { continue; } + let offsets_at = function.first_record.checked_mul(4)?; + let offsets = directory.records.get( + offsets_at + ..offsets_at.checked_add((function.record_count as usize).checked_mul(4)?)?, + )?; out.push(FunctionEntry { address: function.address, - offsets_at: u32::try_from(function.first_record.checked_mul(4)?).ok()?, + offsets_at: u32::try_from(offsets_at).ok()?, stream_at: u32::try_from(stream_base.checked_add(function.stream_offset)?).ok()?, record_count: function.record_count, blob, + max_offset: max_record_offset(offsets), + stack_size: function.stack_size, }); } Some(()) }) } +/// [`FunctionEntry::max_offset`] of a function whose record offsets are the +/// little-endian `u32`s of `offsets`: the maximum, not the last, because +/// nothing in the format orders a function's records by offset. One +/// sequential pass the compiler vectorises. +fn max_record_offset(offsets: &[u8]) -> u32 { + offsets + .chunks_exact(4) + .map(|word| u32::from_le_bytes([word[0], word[1], word[2], word[3]])) + .fold(0u32, u32::max) +} + /// One record's live set, as positions in the section rather than as a /// materialised list. Decoding happens when the walker asks, and only for the /// handful of records a stack walk actually names. @@ -537,13 +564,25 @@ pub(super) fn match_records( ip: usize, max_delta: usize, ) -> Option { - let at = functions - .partition_point(|entry| entry.address <= ip) - .checked_sub(1)?; - let address = functions[at].address; - let start = functions.partition_point(|entry| entry.address < address); - let end = functions.partition_point(|entry| entry.address <= address); + // One search. Entries sharing an address are adjacent, so the run that + // owns `ip` ends here and its start is a short step back (almost always + // zero steps). + let end = functions.partition_point(|entry| entry.address <= ip); + let address = functions[end.checked_sub(1)?].address; + let mut start = end - 1; + while start > 0 && functions[start - 1].address == address { + start -= 1; + } let target = u32::try_from(ip.checked_sub(address)?).ok()?; + // A pc past every record of the run matches nothing in it: a Rust or + // libc frame above the nearest lower JS function, which used to scan + // every one of that function's record offsets to learn the same thing. + if functions[start..end] + .iter() + .all(|entry| target as usize > entry.max_offset as usize + max_delta) + { + return None; + } let mut best: Option<(u32, u32)> = None; // (distance, offset) for entry in &functions[start..end] { diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs b/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs index 6e464a750b1..94ffd12f939 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs @@ -33,7 +33,9 @@ //! prologue words `fp_to_sp_offset` decoded to derive an SP base. That is //! enough to say which walker is wrong without a second run. -use super::{fp_chain, unwind, MutableRootSlot, NativeStackWalkStats, ResolvedRoot, StackMapIndex}; +use super::{ + fast_walk, unwind, MutableRootSlot, NativeStackWalkStats, ResolvedRoot, StackMapIndex, +}; use std::fmt::Write as _; /// Run the fast walk non-mutating, then the unwinder for the real visitation, @@ -43,13 +45,14 @@ pub(super) fn visit( visit: &mut impl FnMut(MutableRootSlot), ) -> NativeStackWalkStats { let mut fast: Vec = Vec::new(); - let fast_stats = fp_chain::visit(index, &mut |root: ResolvedRoot| fast.push(root)); + let fast_stats = fast_walk::visit(index, &mut |root: ResolvedRoot| fast.push(root)); let Some(fast_stats) = fast_stats else { panic!( "PERRY_STACKMAP_WALKER=verify: fast walk unavailable — it abandoned the \ walk on a frame it could not resolve (a base outside FP/SP/x19, an \ - undecodable prologue, or an unvalidated frame record), or this target \ - has no fp-chain walker." + undecodable prologue, an unvalidated frame record, or on x86-64 an \ + unwind row the direct decoder cannot follow), or this target has no \ + fast walker." ); }; let mut slow: Vec = Vec::new(); diff --git a/crates/perry-runtime/src/gc/roots/walk_origin.rs b/crates/perry-runtime/src/gc/roots/walk_origin.rs new file mode 100644 index 00000000000..c8e0872880c --- /dev/null +++ b/crates/perry-runtime/src/gc/roots/walk_origin.rs @@ -0,0 +1,152 @@ +//! Where a root scan's native stack walk starts. +//! +//! A runtime entry that JS calls directly and that can collect (the back-edge +//! poll `js_gc_loop_safepoint`, `gc()`, the inline allocation slow paths) is +//! entered through a shim that hands the body its caller's frame: the JS +//! frame's return address, its stack pointer at the call and its frame +//! pointer. While the body runs it offers that frame ([`EntryOrigin`]), and a +//! scan that runs meanwhile starts its walk there instead of stepping the +//! collector's own Rust frames, which hold no roots: statepoint records are +//! the only root mechanism, and those frames have none. +//! +//! What makes this sound is what an offering entry does not do: it runs no JS +//! before it returns (the poll, `gc()` and allocation run collector and +//! allocator code only). So every frame between any scan made during the +//! entry and the offered frame is the runtime's own, whichever collection, +//! nested collection or budgeted step the scan belongs to. An entry that can +//! call back into JS must not offer. +//! +//! The offer ends with the entry: [`EntryOrigin`] clears it on return (an +//! entry nested in another, a Rust caller of an offering entry, so ends the +//! outer offer too, and the outer body's later scans walk from the scan: +//! never wrong, only slower). A walk can therefore only start at a frame that +//! is live and beneath the scan. + +use std::cell::Cell; + +/// The JS caller of a collecting entry: `pc` is the return address into it, +/// `sp` its stack pointer at the call (the entry's CFA), `fp` its frame +/// pointer. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct WalkOrigin { + pub(crate) pc: usize, + pub(crate) sp: usize, + pub(crate) fp: usize, +} + +// `#[thread_local]`, not `thread_local!`: const, drop-free and all-zero (so +// in `.tbss`), it needs no accessor and no claimed slot. One +// thread-pointer-relative load or store. It holds a pointer to the origin in +// the offering entry's own frame, one word of TLS rather than four, so the +// thread's TLS block (and the loader's offset arithmetic over it) does not +// change size; null when nothing is offered. +#[thread_local] +static OFFERED: Cell<*const WalkOrigin> = Cell::new(core::ptr::null()); + +/// Offers the entry's caller frame to the collection requests of its body. +#[cfg_attr( + not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")), + allow(dead_code) +)] +pub(crate) struct EntryOrigin<'a>(core::marker::PhantomData<&'a WalkOrigin>); + +#[cfg_attr( + not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")), + allow(dead_code) +)] +impl<'a> EntryOrigin<'a> { + /// `origin` lives in the entry's frame for as long as the guard does. + #[inline] + pub(crate) fn enter(origin: &'a WalkOrigin) -> Self { + OFFERED.set(origin); + Self(core::marker::PhantomData) + } +} + +/// Clears rather than restores: an entry nested in another (a Rust caller of +/// an offering entry) ends the outer offer too, and the outer body's later +/// requests then walk from the scan. Never wrong, only slower, and it spares +/// every entry a load. +impl Drop for EntryOrigin<'_> { + #[inline] + fn drop(&mut self) { + OFFERED.set(core::ptr::null()); + } +} + +/// The frame the running offering entry passed, if any: where a scan's walk +/// starts. +#[cfg_attr( + not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")), + allow(dead_code) +)] +#[inline] +pub(crate) fn offered() -> Option { + // SAFETY: non-null only while the guard that set it is alive, and the + // guard borrows the origin it points at. + unsafe { OFFERED.get().as_ref().copied() } +} + +/// The body of an x86-64 Linux naked entry that offers its caller's frame: it +/// leaves the entry's own `N` integer arguments in place, passes the return +/// address, the stack pointer at the call and the frame pointer as arguments +/// `N`, `N + 1` and `N + 2`, and tail-jumps to the `_from` body, so the entry +/// never owns a frame. Three moves on paths that are already out of line. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +macro_rules! offering_entry_asm { + (0, $from:path) => { + core::arch::naked_asm!( + "mov rdi, qword ptr [rsp]", + "lea rsi, [rsp + 8]", + "mov rdx, rbp", + "jmp {from}", + from = sym $from, + ) + }; + (1, $from:path) => { + core::arch::naked_asm!( + "mov rsi, qword ptr [rsp]", + "lea rdx, [rsp + 8]", + "mov rcx, rbp", + "jmp {from}", + from = sym $from, + ) + }; + (3, $from:path) => { + core::arch::naked_asm!( + "mov rcx, qword ptr [rsp]", + "lea r8, [rsp + 8]", + "mov r9, rbp", + "jmp {from}", + from = sym $from, + ) + }; +} +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +pub(crate) use offering_entry_asm; + +#[cfg(test)] +mod tests { + use super::*; + + const A: WalkOrigin = WalkOrigin { + pc: 1, + sp: 2, + fp: 3, + }; + + #[test] + fn an_offer_never_outlives_its_entry() { + { + let _entry = EntryOrigin::enter(&A); + assert_eq!(offered(), Some(A)); + { + let nested = WalkOrigin { pc: 4, ..A }; + let _nested = EntryOrigin::enter(&nested); + assert_eq!(offered().map(|o| o.pc), Some(4)); + } + assert_eq!(offered(), None, "a nested entry ends the outer offer"); + } + assert_eq!(offered(), None, "the offer ends with its entry"); + } +} diff --git a/crates/perry-runtime/src/object/layout_desc.rs b/crates/perry-runtime/src/object/layout_desc.rs index 7678a0d4907..ddddb30729f 100644 --- a/crates/perry-runtime/src/object/layout_desc.rs +++ b/crates/perry-runtime/src/object/layout_desc.rs @@ -679,8 +679,38 @@ pub(crate) use registration::register_static_layouts_in; /// /// # Safety /// `desc` is a registered descriptor of this program. +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] #[no_mangle] pub unsafe extern "C" fn perry_alloc_layout(desc: *const PerryLayoutDesc) -> *mut ObjectHeader { + unsafe { perry_alloc_layout_body(desc, None) } +} + +/// x86-64 Linux: an allocation JS calls directly, entered through a shim that +/// passes its caller's frame along. Only the arena slow path, the one branch +/// that can collect, offers it to the collection (`gc::roots::walk_origin`); +/// the bump path pays the shim's three moves. Allocation runs no JS, which is +/// what lets it offer. +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[unsafe(naked)] +#[no_mangle] +pub unsafe extern "C" fn perry_alloc_layout(desc: *const PerryLayoutDesc) -> *mut ObjectHeader { + crate::gc::offering_entry_asm!(1, perry_alloc_layout_from) +} + +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +extern "C" fn perry_alloc_layout_from( + desc: *const PerryLayoutDesc, + pc: usize, + sp: usize, + fp: usize, +) -> *mut ObjectHeader { + unsafe { perry_alloc_layout_body(desc, Some(crate::gc::WalkOrigin { pc, sp, fp })) } +} + +unsafe fn perry_alloc_layout_body( + desc: *const PerryLayoutDesc, + origin: Option, +) -> *mut ObjectHeader { #[cfg(test)] crate::test_support::bootstrap_test_thread(); let d = &*desc; @@ -691,7 +721,7 @@ pub unsafe extern "C" fn perry_alloc_layout(desc: *const PerryLayoutDesc) -> *mu ); std::process::abort(); }; - alloc_layout_object(d, LayoutWord(id)) + alloc_layout_object_offering(d, LayoutWord(id), origin) } /// Allocate an instance of class family layout `desc` born on `copy`, a class @@ -712,9 +742,21 @@ pub(crate) unsafe fn alloc_family_layout_copy( /// Allocate an object of the registered layout `id` of `d` (see /// [`perry_alloc_layout`]). Can collect. unsafe fn alloc_layout_object(d: &PerryLayoutDesc, id: LayoutWord) -> *mut ObjectHeader { + alloc_layout_object_offering(d, id, None) +} + +/// [`alloc_layout_object`] for an entry that passed its caller's frame: a +/// collection the arena slow path runs walks from there. +#[inline(always)] +unsafe fn alloc_layout_object_offering( + d: &PerryLayoutDesc, + id: LayoutWord, + origin: Option, +) -> *mut ObjectHeader { // The descriptor carries the header image (with the size) and where the slots start. - let obj = crate::arena::arena_alloc_gc_object_word(&d.gc_header_word, d.slot_count) - .cast::(); + let obj = + crate::arena::arena_alloc_gc_object_word_offering(&d.gc_header_word, d.slot_count, origin) + .cast::(); finish_layout_object( obj, d, @@ -1888,6 +1930,7 @@ mod windows_layout_bounds_tests { #[cfg(all(test, target_os = "windows"))] mod windows_atom_bounds_tests { + use super::atoms::atom_bounds; use super::*; #[test] diff --git a/crates/perry-runtime/src/object/layout_desc/literal_birth.rs b/crates/perry-runtime/src/object/layout_desc/literal_birth.rs index 886f3291378..f2df32566b1 100644 --- a/crates/perry-runtime/src/object/layout_desc/literal_birth.rs +++ b/crates/perry-runtime/src/object/layout_desc/literal_birth.rs @@ -227,9 +227,38 @@ unsafe fn first_birth(desc: *const PerryLayoutDesc, index: u32) -> Option { /// /// # Safety /// desc points to this program's immutable literal descriptor image. +#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))] #[no_mangle] pub unsafe extern "C" fn perry_alloc_literal_layout( desc: *const PerryLayoutDesc, +) -> *mut ObjectHeader { + unsafe { perry_alloc_literal_layout_body(desc, None) } +} + +/// x86-64 Linux: as `perry_alloc_layout`, the caller's frame reaches only the +/// arena slow path (`gc::roots::walk_origin`). +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +#[unsafe(naked)] +#[no_mangle] +pub unsafe extern "C" fn perry_alloc_literal_layout( + desc: *const PerryLayoutDesc, +) -> *mut ObjectHeader { + crate::gc::offering_entry_asm!(1, perry_alloc_literal_layout_from) +} + +#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] +extern "C" fn perry_alloc_literal_layout_from( + desc: *const PerryLayoutDesc, + pc: usize, + sp: usize, + fp: usize, +) -> *mut ObjectHeader { + unsafe { perry_alloc_literal_layout_body(desc, Some(crate::gc::WalkOrigin { pc, sp, fp })) } +} + +unsafe fn perry_alloc_literal_layout_body( + desc: *const PerryLayoutDesc, + origin: Option, ) -> *mut ObjectHeader { #[cfg(test)] crate::test_support::bootstrap_test_thread(); @@ -239,7 +268,7 @@ pub unsafe extern "C" fn perry_alloc_literal_layout( ); std::process::abort(); }; - alloc_layout_object(&*desc, LayoutWord(id)) + alloc_layout_object_offering(&*desc, LayoutWord(id), origin) } #[cfg(feature = "keepalive-anchors")] diff --git a/crates/perry-runtime/src/test_support.rs b/crates/perry-runtime/src/test_support.rs index d1223c6734b..2b21a158b18 100644 --- a/crates/perry-runtime/src/test_support.rs +++ b/crates/perry-runtime/src/test_support.rs @@ -21,6 +21,19 @@ pub(crate) fn process_cwd_test_lock() -> std::sync::MutexGuard<'static, ()> { .unwrap_or_else(|poisoned| poisoned.into_inner()) } +/// Serializes tests that `dlopen`/`dlclose` an object against tests that read +/// every loaded object's tables or assert the eh walker's per-image cache. A +/// reader that enumerated the images while a writer had an object loaded +/// would otherwise read its tables after the `dlclose` unmapped them (a +/// SIGSEGV in `direct_rows_match_gimli_across_every_fde`), or see its cache +/// dropped by the generation change. Poison-tolerant like the cwd lock. +pub(crate) fn loaded_objects_test_lock() -> std::sync::MutexGuard<'static, ()> { + static LOADED_OBJECTS_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + LOADED_OBJECTS_TEST_LOCK + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} + /// Run a libtest case in its own process when its fixture needs exclusive /// ownership of process-global state (#9197). A lock shared by only a few /// tests cannot exclude the runtime's other side-table readers or counters. diff --git a/scripts/gc_runtime_root_holders.json b/scripts/gc_runtime_root_holders.json index e3fde8f0591..d05865d02e6 100644 --- a/scripts/gc_runtime_root_holders.json +++ b/scripts/gc_runtime_root_holders.json @@ -250,7 +250,7 @@ "file": "crates/perry-runtime/src/gc/census.rs", "name": "PASS1_MARKED", "verdict": "non_moving_snapshot", - "why": "Re-audited 2026-10-23 against the pinned-source diffs and current collection phases. Raw header-address snapshot exists only in a synchronous full run_to_completion: step_mark_propagation records it after tracing, then block persistence and weak/ephemeron processing only mark or clear existing edges. Full finalization skips the relocating minor prelude. step_sweep consumes the snapshot before census reporting and before reclamation; budgeted cycles and minors skip its creation and consumption. No JS callback, object relocation, or mutator window occurs between the boundaries. The added full-sweep honest-pointer verifier only reads heap slots and allocator metadata; it neither calls JS nor relocates. Scanner registration and optional feature installation occur in gc_init before this window. Trigger-watermark admission, promotion-policy estimates and test-only throw sabotage do not change the synchronous mark-to-sweep sequence; the production throw check aborts rather than calling JS or unwinding. Native Rust allocations do not collect. Removing the retired PIC census row changes reporting only. Re-audited b7f3d56fed: policy.rs adds old_gen_growth_since_last_full_bytes, a read-only query of existing pressure and post-full baseline counters for idle-reducer admission. It does not call JS, collect or relocate, and neither snapshot boundary nor the synchronous mark-to-sweep sequence changes. Re-audited DPA-R: js_gc_init_with_layouts binds immutable image metadata before runtime initialization; the compatibility wrapper and linker keepalive anchor do not touch collection, either snapshot boundary, or the synchronous mark-to-sweep sequence. Re-audited WRT2b: the added image-root scan precedes mark propagation and snapshot creation; image cells are immortal MARKED|PINNED and do not enter the moving arena set. The honest-pointer verifier enumerates those immutable image addresses read-only. The new module declarations change no snapshot boundary, JS callback, relocation, or synchronous mark-to-sweep window. WRT3a re-audit: these source files equal e6e7cc419 and the parent; the synchronous census_pass1_if_armed to census_take_if_armed_at_full_sweep_start window remains inside run_to_completion, before reclamation, with no mutator resumption or relocation. Full runtime tests exercise this current source. Release-readiness re-audit 2026-10-28: census.rs only narrows the proc_pid_rusage reporter to macOS. Current gc_init installs roots and intrinsic layouts before collection; js_gc_init_with_layouts binds image metadata before gc_init. The unchanged run_to_completion, mark-completion capture, and pre-reclamation step_sweep consumption still provide the synchronous non-moving window. Re-audited release CI head: burst_boundary now arms the existing precise-poll flag and records the arena baseline. It does not collect or call JavaScript; collection remains deferred until a later precise poll outside the synchronous mark-to-sweep snapshot window. Re-audited #13405: cycle.rs routes birth-color changes through set_birth_extra_flags; it stores the same flags and refreshes the existing inline allocation bound. The refresh only reads existing TLS/arena state and changes the bound, without allocation, JS callbacks, collection or relocation. The synchronous mark-to-sweep custody window is unchanged. Re-audited 2026-10-29 against #13443 (trace.rs -> mark.rs, AtomicFinalize split out to cycle/atomic_finalize.rs, GC_STATS consolidated into gc/progress.rs), the #13457/#13463/#13479 GC slices, and the #13405/#13471/#13472 pacing changes. Both boundaries and both predicates are unchanged and still name the same condition: pass 1 needs minor.is_none() && !is_budgeted(), pass 2 needs full_trace (= minor.is_none()) && !is_budgeted(). The window now runs BlockPersistence -> AtomicFinalize -> the sweep entry before note_sweep_started, and each phase was read rather than assumed: BlockPersistence only marks; the only relocating subphase of AtomicFinalize, MinorPrelude, is entered solely from the minor branch of the ephemeron clear (cycle/weak_ephemerons.rs), while a full goes WeakProcessing -> DisableBarrier -> Sweep, so a full cycle never evacuates between the boundaries; no phase inside the window invokes JavaScript. The const-generic DIAGNOSTICS split of #13443 gates only trace_phase_* recording and the read-only honest-pointer and minor-unmarked verifiers. The new leaf-escape and in-place-promotion machinery (#13463/#13479, #13439) is reached from allocation, store and copying-minor promotion sites, never from this window. census.rs changed only its diagnostic gate predicate. The rest of the interval was read too: HeapChange::begin(Sweep) only clears the weak intern cache and advances the scope counter, the sweep-entry gap drain only traces seeds the barrier shaded, and finish_full_trace invokes perry-stdlib provider hooks that release native payloads owned by the stdlib rather than GC objects. The snapshot's own Vec is a native Rust allocation, which does not collect.", + "why": "Re-audited 2026-10-23 against the pinned-source diffs and current collection phases. Raw header-address snapshot exists only in a synchronous full run_to_completion: step_mark_propagation records it after tracing, then block persistence and weak/ephemeron processing only mark or clear existing edges. Full finalization skips the relocating minor prelude. step_sweep consumes the snapshot before census reporting and before reclamation; budgeted cycles and minors skip its creation and consumption. No JS callback, object relocation, or mutator window occurs between the boundaries. The added full-sweep honest-pointer verifier only reads heap slots and allocator metadata; it neither calls JS nor relocates. Scanner registration and optional feature installation occur in gc_init before this window. Trigger-watermark admission, promotion-policy estimates and test-only throw sabotage do not change the synchronous mark-to-sweep sequence; the production throw check aborts rather than calling JS or unwinding. Native Rust allocations do not collect. Removing the retired PIC census row changes reporting only. Re-audited b7f3d56fed: policy.rs adds old_gen_growth_since_last_full_bytes, a read-only query of existing pressure and post-full baseline counters for idle-reducer admission. It does not call JS, collect or relocate, and neither snapshot boundary nor the synchronous mark-to-sweep sequence changes. Re-audited DPA-R: js_gc_init_with_layouts binds immutable image metadata before runtime initialization; the compatibility wrapper and linker keepalive anchor do not touch collection, either snapshot boundary, or the synchronous mark-to-sweep sequence. Re-audited WRT2b: the added image-root scan precedes mark propagation and snapshot creation; image cells are immortal MARKED|PINNED and do not enter the moving arena set. The honest-pointer verifier enumerates those immutable image addresses read-only. The new module declarations change no snapshot boundary, JS callback, relocation, or synchronous mark-to-sweep window. WRT3a re-audit: these source files equal e6e7cc419 and the parent; the synchronous census_pass1_if_armed to census_take_if_armed_at_full_sweep_start window remains inside run_to_completion, before reclamation, with no mutator resumption or relocation. Full runtime tests exercise this current source. Release-readiness re-audit 2026-10-28: census.rs only narrows the proc_pid_rusage reporter to macOS. Current gc_init installs roots and intrinsic layouts before collection; js_gc_init_with_layouts binds image metadata before gc_init. The unchanged run_to_completion, mark-completion capture, and pre-reclamation step_sweep consumption still provide the synchronous non-moving window. Re-audited release CI head: burst_boundary now arms the existing precise-poll flag and records the arena baseline. It does not collect or call JavaScript; collection remains deferred until a later precise poll outside the synchronous mark-to-sweep snapshot window. Re-audited #13405: cycle.rs routes birth-color changes through set_birth_extra_flags; it stores the same flags and refreshes the existing inline allocation bound. The refresh only reads existing TLS/arena state and changes the bound, without allocation, JS callbacks, collection or relocation. The synchronous mark-to-sweep custody window is unchanged. Re-audited 2026-10-29 against #13443 (trace.rs -> mark.rs, AtomicFinalize split out to cycle/atomic_finalize.rs, GC_STATS consolidated into gc/progress.rs), the #13457/#13463/#13479 GC slices, and the #13405/#13471/#13472 pacing changes. Both boundaries and both predicates are unchanged and still name the same condition: pass 1 needs minor.is_none() && !is_budgeted(), pass 2 needs full_trace (= minor.is_none()) && !is_budgeted(). The window now runs BlockPersistence -> AtomicFinalize -> the sweep entry before note_sweep_started, and each phase was read rather than assumed: BlockPersistence only marks; the only relocating subphase of AtomicFinalize, MinorPrelude, is entered solely from the minor branch of the ephemeron clear (cycle/weak_ephemerons.rs), while a full goes WeakProcessing -> DisableBarrier -> Sweep, so a full cycle never evacuates between the boundaries; no phase inside the window invokes JavaScript. The const-generic DIAGNOSTICS split of #13443 gates only trace_phase_* recording and the read-only honest-pointer and minor-unmarked verifiers. The new leaf-escape and in-place-promotion machinery (#13463/#13479, #13439) is reached from allocation, store and copying-minor promotion sites, never from this window. census.rs changed only its diagnostic gate predicate. The rest of the interval was read too: HeapChange::begin(Sweep) only clears the weak intern cache and advances the scope counter, the sweep-entry gap drain only traces seeds the barrier shaded, and finish_full_trace invokes perry-stdlib provider hooks that release native payloads owned by the stdlib rather than GC objects. The snapshot's own Vec is a native Rust allocation, which does not collect. Re-audited #13582 (x86-64 Linux GC walk): arena/inline.rs only puts a naked offering shim in front of js_inline_arena_slow_alloc, which passes its caller's pc/sp/fp to a Rust frame that stores a pointer to that origin in the thread-local walk-origin offer cell for the duration of the unchanged slow-path body and clears it on return. It is an allocation entry, not reached from inside the census window; it calls no JS, relocates nothing and changes neither snapshot boundary nor the synchronous mark-to-sweep sequence.", "window": { "start": { "file": "crates/perry-runtime/src/gc/census.rs", @@ -268,7 +268,7 @@ "crates/perry-runtime/src/gc/census.rs": "1f8f47bb2eaedf1eeb93be9f3042d74291f9d4ba22a9be7c698eaa7c8dfd434f", "crates/perry-runtime/src/gc/cycle.rs": "34f0fe91de16f0597e96a1856863e7a13b4afe39c6c5c3110158e9b2ac6a4a96", "crates/perry-runtime/src/gc/barrier/mod.rs": "01e34f1351cfb07c298572a6ec5a8500ec187db45421811b09f8db182d30dfc4", - "crates/perry-runtime/src/arena/inline.rs": "fc97cfec2c6d644194b4dcdee7a689219acedf0939d31e3da1e285a008a81c41", + "crates/perry-runtime/src/arena/inline.rs": "c4fa63b0fbd3aa00d47a3e856c34c4050ef96edc8c50444158d2cfc817d6f863", "crates/perry-runtime/src/gc/mod.rs": "9069fe9d2d052f319645bc3cdd17a9f50a636f28e9b8e7aa6db023085c729c4b", "crates/perry-runtime/src/gc/policy.rs": "dd268c2aa26e01e5f9ef1c106d14857eedf3e44c0830732c8be4374b372b4d7a", "crates/perry-runtime/src/gc/progress.rs": "d2cf96a624dff35e405ac15fa37fe6ba31e7e158668581428fb3765f2c7160c3", diff --git a/scripts/global_sink_asserted_reviews.json b/scripts/global_sink_asserted_reviews.json index d6093f62905..7e840ac56ad 100644 --- a/scripts/global_sink_asserted_reviews.json +++ b/scripts/global_sink_asserted_reviews.json @@ -6,14 +6,14 @@ "crates/perry-runtime/src/buffer/adopted_backing_tests.rs": "48df7dd79b7301517a5d04d6e06ab5718514e54960bf8c4e110e39e2365a26c1", "crates/perry-runtime/src/buffer/store_tests.rs": "0a6ed4adbb40ff906cb54a0bfd571663a110db81239392cb4e8dd324d9e052a0", "crates/perry-runtime/src/gc/tests/buffer_bytes.rs": "e7bf2457497c86a32b371be0678eb952988ea0b411b1a8ac34836a745223e051", - "crates/perry-runtime/src/test_support.rs": "fad16754c572eef842c567ec3c08d1a5ab1860dd8cc3bdfae595161cda8d742a" + "crates/perry-runtime/src/test_support.rs": "eda6b41d7531bbf515d3b34ed133285ead5fed492accd5ad8aee966bb4210cb2" } }, "crates/perry-runtime/src/loop_interrupt.rs::PERRY_LOOP_INTERRUPT_ARMED": { "claim": "Exported compiled-poll ABI counts cross-worker requests; its exact before/after witness runs in an isolated child process.", "source_sha256": { "crates/perry-runtime/src/loop_interrupt.rs": "15c91f29ecf1504a3cb110da6e360162207e87459bd5761c524a872b729bcaa3", - "crates/perry-runtime/src/test_support.rs": "fad16754c572eef842c567ec3c08d1a5ab1860dd8cc3bdfae595161cda8d742a" + "crates/perry-runtime/src/test_support.rs": "eda6b41d7531bbf515d3b34ed133285ead5fed492accd5ad8aee966bb4210cb2" } }, "crates/perry-runtime/src/object/class_family_birth.rs::LAYOUT": { @@ -40,7 +40,7 @@ "claim": "Exported monotonic ABI latch is intentionally process-wide; the fixture forcing it runs in an isolated child process. Rechecked the current interned_key_abi_tests fixture: isolated_process_guard still re-execs the assertion in a fresh process; prototype modifications only set the monotonic flag.", "source_sha256": { "crates/perry-runtime/src/object/own_override.rs": "6c112095932e32ce0d165850b73813bf530adbf494c22c17e2ad12d36b49f8b0", - "crates/perry-runtime/src/test_support.rs": "fad16754c572eef842c567ec3c08d1a5ab1860dd8cc3bdfae595161cda8d742a" + "crates/perry-runtime/src/test_support.rs": "eda6b41d7531bbf515d3b34ed133285ead5fed492accd5ad8aee966bb4210cb2" } }, "crates/perry-runtime/src/object/runtime_layouts.rs::READABLE_STATE": { From 2350775ff41216a4180bde7d171164986f6e7de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 22:21:05 +0000 Subject: [PATCH 093/144] wpfacts: order unit cycles by strong edges; parameter types and namespace reach are weak A cycle of declaration units is solved with one back edge whose importer sees the value as unknown (P1). The depth-first walk picked that edge by discovery order, so tsc broke at `ExternalProject extends Project`. Units of a strongly connected component are now ordered by their strong edges alone: a name used only as a parameter type, or reached only through a namespace import, is a weak edge; statically read namespace keys are strong edges to their declaring unit. --- changelog.d/0-wpfacts-unit-cycle-order.md | 10 ++ crates/perry-wpfacts/src/decl_units.rs | 46 +++++- crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../src/tests/unit_mode/unit_cycle_order.rs | 48 ++++++ crates/perry-wpfacts/src/units/driver.rs | 156 ++++++++++++++++-- 5 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 changelog.d/0-wpfacts-unit-cycle-order.md create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/unit_cycle_order.rs diff --git a/changelog.d/0-wpfacts-unit-cycle-order.md b/changelog.d/0-wpfacts-unit-cycle-order.md new file mode 100644 index 00000000000..ded506543fb --- /dev/null +++ b/changelog.d/0-wpfacts-unit-cycle-order.md @@ -0,0 +1,10 @@ +Per-unit facts order the units of an import cycle by the edges whose values +they use. An edge that exists only through a parameter type or through a +namespace import's reach is weak; a key read statically off a namespace +(`ns.k`, `ns.a.b`) is a strong edge to the unit declaring it. The back edge of +a cycle, which the importer sees as unknown, is now a weak edge wherever the +strong edges have no cycle of their own, instead of whichever edge the +depth-first walk met first (tsc: `ExternalProject extends Project` saw +`Project` as unknown and broke the subclass). The order stays deterministic: +components in post-order, members in discovery order. A unit test covers a +cycle through a parameter type that was broken at `extends`. diff --git a/crates/perry-wpfacts/src/decl_units.rs b/crates/perry-wpfacts/src/decl_units.rs index 89e9d445d10..35ec156052d 100644 --- a/crates/perry-wpfacts/src/decl_units.rs +++ b/crates/perry-wpfacts/src/decl_units.rs @@ -54,6 +54,14 @@ pub(crate) struct UnitView<'a> { /// (and globals). The driver orders it after the units owning them /// (decision 21). pub ext_names: FxHashSet, + /// The `ext_names` that only parameter types name (§11.4): the entry guard + /// reads their contracts, no value of theirs is used. + pub type_names: FxHashSet, + /// The units of this module only its parameter types name. + pub type_deps: Vec, + /// The member paths read off imported names (`ns.k`, `ns.a.b`): for a + /// namespace import, the keys whose declaring units it reads. + pub ns_paths: Vec<(String, Vec)>, /// Its items, as indices into the one module it is cut from: a view /// borrows the module, it copies nothing of it (phase 03m, CWN3M: the /// per-unit module clones were 16.4 GB of tscwork's 23 GB). `classes` @@ -118,6 +126,8 @@ struct Refs { declared: Vec, /// The closures inside the item. closures: Vec, + /// The member paths read off external names (`ns.a.b`: `("ns", [a, b])`). + ns_paths: Vec<(String, Vec)>, /// The named types of its parameters (an entry guard's class, §11.4). param_types: Vec, /// The locals it writes (also in `locals`). @@ -287,6 +297,19 @@ fn expr_refs(e: &Expr, r: &mut Refs) { } direct_ref(o, r) } + Expr::PropertyGet { .. } => { + let mut path: Vec = Vec::new(); + let mut o: &Expr = e; + while let Expr::PropertyGet { object, property, .. } = o { + path.push(property.clone()); + o = object; + } + if let Expr::ExternFuncRef { name, .. } = o { + path.reverse(); + r.ns_paths.push((name.clone(), path)); + } + class_name_refs(e, r) + } _ => class_name_refs(e, r), } } @@ -1761,6 +1784,22 @@ pub(crate) fn views<'a>( .filter(|nm| ref_owner(nm.as_str()).is_none()) .cloned() .collect(); + view.type_names = r + .param_types + .iter() + .filter(|nm| ref_owner(nm.as_str()).is_none()) + .filter(|nm| !r.names.contains(nm) && !r.join_names.contains(nm)) + .cloned() + .collect(); + let mut ns_paths: Vec<(String, Vec)> = r + .ns_paths + .iter() + .filter(|(nm, _)| ref_owner(nm.as_str()).is_none()) + .cloned() + .collect(); + ns_paths.sort(); + ns_paths.dedup(); + view.ns_paths = ns_paths; let mut deps: FxHashSet = FxHashSet::default(); let imp = &mut imports_of[u]; // The local name is the export name, unless this unit already @@ -1789,9 +1828,10 @@ pub(crate) fn views<'a>( // against that class's contract (§11.4), so the unit imports the // class and depends on its unit. A type reference reads no state: // no join, no SCC edge. + let mut type_deps: FxHashSet = FxHashSet::default(); for nm in &r.param_types { if let Some(&w) = owner_class.get(nm.as_str()).filter(|&&w| w != u) { - want(nm, w, &mut deps); + want(nm, w, &mut type_deps); } } for id in r.locals.iter().chain(&r.join_locals) { @@ -1835,6 +1875,10 @@ pub(crate) fn views<'a>( .filter(|(_, id)| own_fn(id)) .cloned() .collect(); + let mut weak: Vec = type_deps.difference(&deps).copied().collect(); + weak.sort_unstable(); + view.type_deps = weak; + deps.extend(type_deps); let mut deps: Vec = deps.into_iter().collect(); deps.sort_unstable(); out.push((view, deps)); diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index 2b835b493c8..bc344eb4d6c 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -609,3 +609,5 @@ mod method_on_read; mod fn_transfer; #[path = "unit_mode/protocol_touch.rs"] mod protocol_touch; +#[path = "unit_mode/unit_cycle_order.rs"] +mod unit_cycle_order; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/unit_cycle_order.rs b/crates/perry-wpfacts/src/tests/unit_mode/unit_cycle_order.rs new file mode 100644 index 00000000000..6b2c17909cf --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/unit_cycle_order.rs @@ -0,0 +1,48 @@ +//! The order of a unit cycle (`units::driver::dependency_order`): a cycle +//! whose only way back is a parameter type is broken there, never at a +//! subclass's `extends` edge (which made the subclass's unit see its parent +//! class as ⊤ and break it: tsc's Project -> ProjectService -> +//! ExternalProject -> Project). + +use super::*; + +// `b` is named first by `m`, so the order's DFS enters the cycle at `b`: +// b -> p (extends) -> s (the parameter type) -> b (`new Ext`). +const P: &str = r#"import { Service } from "./s"; +export class Base { + n: number; + constructor(readonly svc: Service) { this.n = 1; } + size() { return this.n + 1; } +}"#; + +const S: &str = r#"import { Ext } from "./b"; +export class Service { + count = 0; + create() { const e = new Ext(null); return e.size() + e.k; } +}"#; + +const B: &str = r#"import { Base } from "./p"; +export class Ext extends Base { + k: number; + constructor(svc: any) { super(svc); this.k = 2; } +}"#; + +const M: &str = r#"import { Ext } from "./b"; +import { Service } from "./s"; +const s = new Service(); +console.log(s.create(), new Ext(s).size());"#; + +#[test] +fn a_cycle_through_a_parameter_type_is_broken_at_the_type_not_at_extends() { + let mods = [("p", P), ("s", S), ("b", B), ("m", M)]; + let (u, w) = (units_of(&mods), whole_of(&mods)); + let (pu, pw) = (proven_set(&u), proven_set(&w)); + let lost: Vec<_> = pw.difference(&pu).collect(); + let broken: Vec<_> = u + .broken + .iter() + .filter(|d| !d.starts_with("override ")) + .collect(); + assert!(!pw.is_empty(), "{}", w.dump_text()); + assert!(lost.is_empty() && broken.is_empty(), "lost {lost:?} broken {broken:?}\n{}", u.dump_text()); +} diff --git a/crates/perry-wpfacts/src/units/driver.rs b/crates/perry-wpfacts/src/units/driver.rs index 275d18c5463..1d822d213b9 100644 --- a/crates/perry-wpfacts/src/units/driver.rs +++ b/crates/perry-wpfacts/src/units/driver.rs @@ -19,6 +19,10 @@ pub(super) struct Solve<'a> { /// the contracts must exist in; ordering by whole modules made cycles /// the names do not have, and a back edge's importer sees ⊤. after: Vec, + /// The `after` units only parameter types name (§11.4, the entry + /// guard's contracts): where a cycle must have a back edge, it is one + /// of these (`dependency_order`). + weak: Vec, } /// The unit of module range `t` exporting `name`; a re-export goes with @@ -74,6 +78,7 @@ pub(super) fn solves<'a>( .enumerate() { let deps: Vec = deps.into_iter().map(|d| base + d).collect(); + let weak: Vec = view.type_deps.iter().map(|&d| base + d).collect(); out.push(Solve { mi, key: crate::decl_units::unit_path(path, k), @@ -83,6 +88,7 @@ pub(super) fn solves<'a>( ..view }, after: deps.clone(), + weak, deps, }); } @@ -93,6 +99,8 @@ pub(super) fn solves<'a>( let (path, module) = sorted[out[si].mi]; let mut after: Vec = Vec::new(); let mut deps: Vec = Vec::new(); + let mut strong: FxHashSet = FxHashSet::default(); + let mut weak: Vec = Vec::new(); for imp in &module.imports { let Some(&t) = imp .resolved_path @@ -114,6 +122,7 @@ pub(super) fn solves<'a>( }); if reexports { after.extend(range[t].clone()); + strong.extend(range[t].clone()); } for sp in &imp.specifiers { let (local, name) = match sp { @@ -139,18 +148,58 @@ pub(super) fn solves<'a>( } // A namespace's keys are read where they are declared: its // unit is ordered after those modules too. + // Its order: after the units declaring the keys it reads + // (`ns.k`, `ns.a.b` through a nested namespace); every other + // unit the namespace reaches is a weak edge (a back edge of a + // cycle sees ⊤ there, as any back edge does). if name.is_none() { for &r in ns_reach.entry(d).or_insert_with(|| ex.reach(d)).iter() { if r != out[si].mi { deps.extend(range[r].clone()); after.extend(range[r].clone()); + weak.extend(range[r].clone()); + } + } + for (_, keys) in out[si].view.ns_paths.iter().filter(|(l, _)| l == local) { + let mut m = d; + for (i, k) in keys.iter().enumerate() { + match ex.resolve(sorted[m].0, k.as_str()) { + Some(Resolved::Binding(p, n)) => { + let p = index[p]; + match owner_of(&out, &range[p], n) { + Some(w) => { + after.push(w); + strong.insert(w); + } + None => { + after.extend(range[p].clone()); + strong.extend(range[p].clone()); + } + } + } + Some(Resolved::Namespace(p)) if i + 1 < keys.len() => { + m = index[p]; + continue; + } + Some(Resolved::Namespace(_)) => {} + _ => { + after.extend(range[m].clone()); + strong.extend(range[m].clone()); + } + } + break; } } } - match name.and_then(|n| owner_of(&out, &range[d], n)) { - Some(w) => after.push(w), - None => after.extend(range[d].clone()), + let ws: Vec = match name.and_then(|n| owner_of(&out, &range[d], n)) { + Some(w) => vec![w], + None => range[d].clone().collect(), + }; + match name.is_none() || out[si].view.type_names.contains(local) { + true => weak.extend(&ws), + false => strong.extend(&ws), } + after.extend(ws); } } // A re-export whose source the module does not import (resolved @@ -177,11 +226,20 @@ pub(super) fn solves<'a>( }; out[si].deps.extend(range[t].clone()); after.extend(range[t].clone()); + strong.extend(range[t].clone()); } } // Each once, in first-named order (the order's DFS follows it). let mut once: FxHashSet = FxHashSet::default(); after.retain(|&w| once.insert(w)); + weak.retain(|w| !strong.contains(w)); + weak.sort_unstable(); + weak.dedup(); + out[si].weak.retain(|w| !strong.contains(w)); + let own = &mut out[si].weak; + own.extend(weak); + own.sort_unstable(); + own.dedup(); out[si].after.extend(after); deps.sort_unstable(); deps.dedup(); @@ -192,35 +250,103 @@ pub(super) fn solves<'a>( /// Solves in dependency order (a solve after the ones it depends on), so a /// contract is computed before a unit consumes it. In an import cycle the -/// back edge has no contract yet: that import is ⊤, as in P1. +/// back edge has no contract yet: that import is ⊤, as in P1. Which edge of +/// a cycle is the back edge decides what is ⊤: a strong edge (a name whose +/// value the unit uses: `extends C`, `new C`, a call) makes the importer +/// see ⊤ for that value, while a weak edge (a name only a parameter type +/// names, `Solve::weak`) only leaves the entry guard without a contract. +/// So the units of a cycle (a strongly connected component of `after`) are +/// ordered by their strong edges alone: a back edge is weak wherever the +/// strong edges have no cycle of their own. The components stay in the +/// order a depth-first post-order of `after` gives (each after the ones it +/// depends on); a component without weak edges keeps that order exactly. pub(super) fn dependency_order(solves: &[Solve]) -> Vec { - let mut state = vec![0u8; solves.len()]; - let mut out = Vec::with_capacity(solves.len()); - for root in 0..solves.len() { - if state[root] != 0 { + let n = solves.len(); + // Depth-first post-order of `after`, with Tarjan's low links: a + // component is complete when its root finishes. + let mut index = vec![usize::MAX; n]; + let mut low = vec![0usize; n]; + let mut on_stack = vec![false; n]; + let mut comp_stack: Vec = Vec::new(); + let mut next_index = 0usize; + let mut out = Vec::with_capacity(n); + for root in 0..n { + if index[root] != usize::MAX { continue; } - // Iterative DFS, post-order. let mut stack: Vec<(usize, usize)> = vec![(root, 0)]; - state[root] = 1; + index[root] = next_index; + low[root] = next_index; + next_index += 1; + comp_stack.push(root); + on_stack[root] = true; while let Some(&mut (v, ref mut next)) = stack.last_mut() { if *next < solves[v].after.len() { let w = solves[v].after[*next]; *next += 1; - if state[w] == 0 { - state[w] = 1; + if index[w] == usize::MAX { + index[w] = next_index; + low[w] = next_index; + next_index += 1; + comp_stack.push(w); + on_stack[w] = true; stack.push((w, 0)); + } else if on_stack[w] { + low[v] = low[v].min(index[w]); } - } else { - state[v] = 2; + continue; + } + stack.pop(); + if let Some(&(u, _)) = stack.last() { + low[u] = low[u].min(low[v]); + } + if low[v] != index[v] { + continue; + } + // `v` is the root of a component: its members, the stack above it. + let at = comp_stack.iter().rposition(|&x| x == v).expect("root on stack"); + let mut members: Vec = comp_stack.split_off(at); + for &m in &members { + on_stack[m] = false; + } + if members.len() == 1 { out.push(v); - stack.pop(); + continue; } + // Discovery order: the root first, as the post-order found them. + members.sort_by_key(|&m| index[m]); + strong_post_order(solves, &members, &mut out); } } out } +/// The post-order of `members` (one component, in discovery order) by their +/// strong edges within it (`after` less `weak`), appended to `out`. +fn strong_post_order(solves: &[Solve], members: &[usize], out: &mut Vec) { + let inside: FxHashSet = members.iter().copied().collect(); + let mut done: FxHashSet = FxHashSet::default(); + for &root in members { + if !done.insert(root) { + continue; + } + let mut stack: Vec<(usize, usize)> = vec![(root, 0)]; + while let Some(&mut (v, ref mut next)) = stack.last_mut() { + let after = &solves[v].after; + if *next < after.len() { + let w = after[*next]; + *next += 1; + if inside.contains(&w) && !solves[v].weak.contains(&w) && done.insert(w) { + stack.push((w, 0)); + } + continue; + } + out.push(v); + stack.pop(); + } + } +} + /// The per-unit driver (decision 151, design §7, §11 and §12): one solve /// per declaration unit in dependency order, each publishing contracts to /// the units after it; then the census. A class some unit used beyond its From e19ea3bac1adcf399cea97b259af17a477b80c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 23:43:40 +0100 Subject: [PATCH 094/144] changelog: number the PR fragment --- ...acts-unit-cycle-order.md => 13634-wpfacts-unit-cycle-order.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-unit-cycle-order.md => 13634-wpfacts-unit-cycle-order.md} (100%) diff --git a/changelog.d/0-wpfacts-unit-cycle-order.md b/changelog.d/13634-wpfacts-unit-cycle-order.md similarity index 100% rename from changelog.d/0-wpfacts-unit-cycle-order.md rename to changelog.d/13634-wpfacts-unit-cycle-order.md From 93ad8126f821bdc7b86e96c5233f9c1f941e7472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 23:06:06 +0000 Subject: [PATCH 095/144] compile: install the GC diagnostics when their knobs are set while compiling Since the GC log, PERRY_GC_DIAG and the evacuation verifier moved behind js_runtime_install_diagnostics, a program whose installs are selected from its own features (every WASI link, every auto-optimized native link) got them only for a heap-snapshot or process.report call. A knob set only at run time printed nothing and skipped the verifier silently, so the WASI image selftest saw no [gc-log] kind=full lines and stayed red. One compile-time rule, as for the instrument knobs: PERRY_GC_DIAG, PERRY_GC_LOG, PERRY_GC_VERIFY_EVACUATION (or PERRY_GC_TRACE) on while compiling selects perry-runtime/diagnostics, and the build cache fingerprints it. Without a knob the binary is unchanged. The image selftest, the wasip1 host discard_reuse case and the guest moving-GC component check pass their GC knobs at compile time. The selftest witnesses moving minors by the verifier's evacuation_ok lines: copying_minors counts only in instrumented archives. --- changelog.d/0-gc-diagnostics-compile-time.md | 15 ++++++ .../perry/src/commands/compile/build_cache.rs | 29 ++++++++---- .../src/commands/compile/optimized_libs.rs | 4 +- .../compile/optimized_libs/features.rs | 35 +++++++++++++- .../commands/compile/optimized_libs/tests.rs | 46 +++++++++++++++++++ scripts/component_guest_acceptance.py | 12 +++-- scripts/wasi_image_selftest.py | 17 +++++-- test-wasi/p1-host/suite.py | 14 ++++-- 8 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 changelog.d/0-gc-diagnostics-compile-time.md diff --git a/changelog.d/0-gc-diagnostics-compile-time.md b/changelog.d/0-gc-diagnostics-compile-time.md new file mode 100644 index 00000000000..ee9af4ddf99 --- /dev/null +++ b/changelog.d/0-gc-diagnostics-compile-time.md @@ -0,0 +1,15 @@ +Install the GC diagnostics (`PERRY_GC_LOG`, `PERRY_GC_DIAG`, +`PERRY_GC_VERIFY_EVACUATION`, and the `PERRY_GC_TRACE` JSON) when one of +their knobs is set while compiling, the same pay-for-use rule the instrument +knobs follow. Since these diagnostics moved behind +`js_runtime_install_diagnostics`, a program whose installs are chosen from its +own features (every WASI link and every auto-optimized native link) installed +them only for a heap-snapshot or `process.report` call. Setting the knobs only +at run time therefore printed nothing and skipped the evacuation verifier +without any warning. The WASI image selftest's moving/full-collection gate was +red on main for this reason. A compile with none of the knobs set produces a +byte-identical binary. The image selftest, the wasip1 reference-host +`discard_reuse` case and the guest moving-GC component check now pass their GC +knobs at compile time too. The selftest checks moving collections with the +verifier's `evacuation_ok` lines: the `copying_minors` counter only counts in +instrumented archives. diff --git a/crates/perry/src/commands/compile/build_cache.rs b/crates/perry/src/commands/compile/build_cache.rs index 9df30de71e3..ef30bb06297 100644 --- a/crates/perry/src/commands/compile/build_cache.rs +++ b/crates/perry/src/commands/compile/build_cache.rs @@ -9,7 +9,9 @@ use std::path::{Path, PathBuf}; use super::compile_env::{compile_env_value, COMPILE_ENV_VARS}; use super::{ - optimized_libs::{gc_instruments_requested, gc_trace_requested, hot_diag_requested}, + optimized_libs::{ + gc_diagnostics_requested, gc_instruments_requested, gc_trace_requested, hot_diag_requested, + }, BuildCacheStats, CompilationContext, CompileArgs, CompileResult, LinkCacheStats, }; @@ -47,27 +49,35 @@ mod tests { let cases = [ ( &[][..], - "gc-instruments=false,hot-diag=false,gc-trace=false", + "gc-instruments=false,hot-diag=false,gc-trace=false,gc-diagnostics=false", ), ( &[("PERRY_GC_INSTRUMENTS", "1")][..], - "gc-instruments=true,hot-diag=true,gc-trace=false", + "gc-instruments=true,hot-diag=true,gc-trace=false,gc-diagnostics=false", ), ( &[("PERRY_GC_SCHEDULE_SEED", "1")][..], - "gc-instruments=true,hot-diag=false,gc-trace=false", + "gc-instruments=true,hot-diag=false,gc-trace=false,gc-diagnostics=false", ), ( &[("PERRY_GC_SCHEDULE_SEED", "7")][..], - "gc-instruments=true,hot-diag=false,gc-trace=false", + "gc-instruments=true,hot-diag=false,gc-trace=false,gc-diagnostics=false", ), ( &[("PERRY_IC_DIAG", "1")][..], - "gc-instruments=false,hot-diag=true,gc-trace=false", + "gc-instruments=false,hot-diag=true,gc-trace=false,gc-diagnostics=false", ), ( &[("PERRY_GC_TRACE", "1")][..], - "gc-instruments=false,hot-diag=false,gc-trace=true", + "gc-instruments=false,hot-diag=false,gc-trace=true,gc-diagnostics=true", + ), + ( + &[("PERRY_GC_LOG", "1")][..], + "gc-instruments=false,hot-diag=false,gc-trace=false,gc-diagnostics=true", + ), + ( + &[("PERRY_GC_DIAG", "0")][..], + "gc-instruments=false,hot-diag=false,gc-trace=false,gc-diagnostics=false", ), ]; @@ -870,6 +880,7 @@ fn current_env() -> Vec { gc_instruments_requested(), hot_diag_requested(), gc_trace_requested(), + gc_diagnostics_requested(), )); env } @@ -878,11 +889,13 @@ fn runtime_feature_fingerprint( gc_instruments: bool, hot_diag: bool, gc_trace: bool, -) -> [EnvFingerprint; 3] { + gc_diagnostics: bool, +) -> [EnvFingerprint; 4] { [ ("runtime-feature:gc-instruments", gc_instruments), ("runtime-feature:hot-diag", hot_diag), ("runtime-feature:gc-trace", gc_trace), + ("runtime-feature:gc-diagnostics", gc_diagnostics), ] .map(|(name, enabled)| EnvFingerprint { name: name.to_string(), diff --git a/crates/perry/src/commands/compile/optimized_libs.rs b/crates/perry/src/commands/compile/optimized_libs.rs index dad0516a826..7155e8193f6 100644 --- a/crates/perry/src/commands/compile/optimized_libs.rs +++ b/crates/perry/src/commands/compile/optimized_libs.rs @@ -16,8 +16,8 @@ mod wrapper_libs; pub(crate) use driver::build_optimized_libs; pub(crate) use features::{ - auto_optimized_cross_features, gc_instruments_requested, gc_trace_requested, - hot_diag_requested, instrumented_variant_requested, variant_archive_name, + auto_optimized_cross_features, gc_diagnostics_requested, gc_instruments_requested, + gc_trace_requested, hot_diag_requested, instrumented_variant_requested, variant_archive_name, }; pub(crate) use paths::android_global_dynamic_tls_rustflag; diff --git a/crates/perry/src/commands/compile/optimized_libs/features.rs b/crates/perry/src/commands/compile/optimized_libs/features.rs index 93cb9a51588..c6dcb2208e1 100644 --- a/crates/perry/src/commands/compile/optimized_libs/features.rs +++ b/crates/perry/src/commands/compile/optimized_libs/features.rs @@ -77,6 +77,36 @@ pub(crate) fn hot_diag_requested() -> bool { }) } +/// Run-time knobs served only by the GC diagnostics that +/// `js_runtime_install_diagnostics` installs (perry-runtime +/// `gc::diagnostic_hooks::install`): the per-collection `[gc-log]` line, the +/// `PERRY_GC_DIAG` reports, and the evacuation verifier, which runs only in the +/// diagnostic collector specialization. +pub(crate) const GC_DIAGNOSTIC_KNOBS: &[&str] = &[ + "PERRY_GC_DIAG", + "PERRY_GC_LOG", + "PERRY_GC_VERIFY_EVACUATION", +]; + +/// Install the GC diagnostics in this build: one of [`GC_DIAGNOSTIC_KNOBS`] +/// on while compiling (by the runtime's value rules: `1`/`true`/`on`/`yes`), +/// or `PERRY_GC_TRACE`, whose JSON is built by the same install. A program +/// whose installs are selected from its own features (auto-optimized native +/// links, every WASI link) otherwise installs them only for a diagnostics API, +/// so `PERRY_GC_LOG=1 perry compile … && PERRY_GC_LOG=1 ./a.out` logs, as the +/// instrument knobs do. +pub(crate) fn gc_diagnostics_requested() -> bool { + gc_trace_requested() + || GC_DIAGNOSTIC_KNOBS.iter().any(|knob| { + std::env::var(knob).is_ok_and(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "on" | "yes" + ) + }) + }) +} + /// Whether this compile asks for `PERRY_GC_TRACE` output (the telemetry and /// typed-feedback JSON, which only the instrumented archives carry). /// Shared with the build-level no-op cache so the cache uses the same decision @@ -474,10 +504,13 @@ pub(crate) fn auto_optimized_cross_features( } // Heap snapshots and `process.report` (~95 KB of JSON serializers plus the // `serde_json` pulled only by them): user APIs, always compiled into the - // archive and installed only for a program that references one. The GC and + // archive and installed only for a program that references one. The GC + // diagnostics ride this install and are installed when one of their knobs + // is set while compiling (`gc_diagnostics_requested`). The GC and // typed-feedback instruments are not installs at all: they exist only in // the instrumented archives (`instrumented_variant_requested`). if ctx.uses_diagnostics + || gc_diagnostics_requested() || ctx.uses_get_builtin_module || needs_dyn_eval || !ctx.native_addons.is_empty() diff --git a/crates/perry/src/commands/compile/optimized_libs/tests.rs b/crates/perry/src/commands/compile/optimized_libs/tests.rs index 9ee14d93e53..5521d4f8fed 100644 --- a/crates/perry/src/commands/compile/optimized_libs/tests.rs +++ b/crates/perry/src/commands/compile/optimized_libs/tests.rs @@ -902,3 +902,49 @@ fn deserializers_install_regex_without_a_regex_literal() { .iter() .any(|f| f == "perry-runtime/regex-engine")); } + +/// The GC diagnostics (`PERRY_GC_LOG`'s per-collection line, `PERRY_GC_DIAG`, +/// the evacuation verifier) run only in a program whose installer calls +/// `js_runtime_install_diagnostics`. A program selected from its own features +/// (every auto-optimized native link and every WASI link) installs it only when +/// asked: by a diagnostics API in the source, or by one of these knobs set +/// while compiling, as for the instrument knobs. Without the compile-time arm a +/// WASI program run with `PERRY_GC_LOG=1` printed nothing, and the image +/// selftest's moving/full-collection gate could not see its collections. +#[test] +fn gc_diagnostic_knobs_set_while_compiling_install_the_gc_diagnostics() { + let _guard = env_lock(); + let knobs = [ + "PERRY_GC_DIAG", + "PERRY_GC_LOG", + "PERRY_GC_VERIFY_EVACUATION", + ]; + let saved: Vec<_> = knobs + .iter() + .chain(&["PERRY_GC_TRACE"]) + .map(|k| (*k, std::env::var(k).ok())) + .collect(); + for (knob, _) in &saved { + set_env_var(knob, None); + } + let dir = tempfile::tempdir().expect("tempdir"); + let ctx = CompilationContext::new(dir.path().to_path_buf()); + let installs = || { + auto_optimized_cross_features(&ctx, &BTreeSet::new(), &[]) + .contains(&"perry-runtime/diagnostics".to_string()) + }; + assert!(!installs(), "no knob, no diagnostics API: nothing installs"); + for knob in knobs { + set_env_var(knob, Some("1")); + assert!( + installs(), + "{knob}=1 while compiling must install the GC diagnostics" + ); + set_env_var(knob, Some("0")); + assert!(!installs(), "{knob}=0 is off, as the runtime parses it"); + set_env_var(knob, None); + } + for (knob, value) in saved { + set_env_var(knob, value.as_deref()); + } +} diff --git a/scripts/component_guest_acceptance.py b/scripts/component_guest_acceptance.py index 901ec410da5..29ad159b0db 100644 --- a/scripts/component_guest_acceptance.py +++ b/scripts/component_guest_acceptance.py @@ -105,14 +105,14 @@ def run(name, command, expected=None, timeout=600, environment=None, moving=Fals if status: raise SystemExit(status if status > 0 else 1) - def build_guest(directory, prefix=""): + def build_guest(directory, prefix="", environment=None): generated = directory / "generated" run(prefix + "guest-bindings", [args.wit.resolve(), "--guest-c", directory / "world.wit", generated, "plugin"]) library = directory / "libtransform.a" run(prefix + "compile-guest", [args.perry.resolve(), "compile", directory / "guest.ts", "--target", "wasi", "--output-type", "staticlib", - "--no-auto-optimize", "--no-cache", "-o", library]) + "--no-auto-optimize", "--no-cache", "-o", library], environment=environment) component = directory / "transform.component.wasm" # wasi-sdk's P2 driver componentizes the linked reactor, combining the # application WIT metadata with its libc/WASI imports. Include all TS @@ -155,7 +155,13 @@ def build_guest(directory, prefix=""): expected="PASS canonical values: 14 import/export calls") resources = output / "resources" shutil.copytree(fixture.parent / "guest-resources", resources, dirs_exist_ok=True) - resource_component = build_guest(resources, "resources-") + # The moving run's guest knobs (perry-component-host `--resources-moving`) + # include PERRY_GC_DIAG, whose `[gc-copy-minor]` witness the GC diagnostics + # print; a program installs them only when the knob is set while compiling. + guest_env = env.copy() + if args.guest_moving_gc: + guest_env.update(PERRY_GC_FORCE_EVACUATE="1", PERRY_GC_VERIFY_EVACUATION="1", PERRY_GC_DIAG="1") + resource_component = build_guest(resources, "resources-", guest_env) run("run-resources", [host, resource_component, "--resources"], timeout=120, expected="PASS Perry guest resources: imported ownership, rollback, nested transfers, exported payload GC, expired borrows, 51 explicit destructions, deferred cleanup, trap") if args.guest_moving_gc: diff --git a/scripts/wasi_image_selftest.py b/scripts/wasi_image_selftest.py index 975fa9dc131..cdeb1d7db64 100644 --- a/scripts/wasi_image_selftest.py +++ b/scripts/wasi_image_selftest.py @@ -33,6 +33,11 @@ "p1": ("wasm32-wasip1", "wasip1", ["-W", "exceptions=y"]), } MINIMUM_CHECKS = 4 +# Moving collections with the evacuation verifier, and their log. These are GC +# diagnostics: a program installs them only when they are set while compiling, +# so the same knobs go to the compile and to the run. +GC_KNOBS = {"PERRY_GC_FORCE_EVACUATE": "1", "PERRY_GC_VERIFY_EVACUATION": "1", + "PERRY_GC_DIAG": "1", "PERRY_GC_LOG": "1"} def main() -> int: @@ -76,7 +81,7 @@ def main() -> int: wasm = work / "selftest.wasm" (work / "ir").mkdir() compile_env = dict(os.environ, PERRY_NO_AUTO_OPTIMIZE="1", PERRY_NO_CACHE="1", - PERRY_RUNTIME_DIR=str(archive_dir), PERRY_SAVE_LL=str(work / "ir")) + PERRY_RUNTIME_DIR=str(archive_dir), PERRY_SAVE_LL=str(work / "ir"), **GC_KNOBS) compiled = subprocess.run([str(perry), "compile", str(source), "--target", perry_target, "-o", str(wasm)], cwd=work, env=compile_env, capture_output=True, text=True) if compiled.returncode or not wasm.is_file(): @@ -90,8 +95,7 @@ def main() -> int: sys.exit(f"coalescing gate needs two defining LLVM modules, found {definitions}") executed = subprocess.run( [args.wasmtime, "run", *wasmtime_flags, - "--env", "PERRY_GC_FORCE_EVACUATE=1", "--env", "PERRY_GC_VERIFY_EVACUATION=1", - "--env", "PERRY_GC_DIAG=1", "--env", "PERRY_GC_LOG=1", str(wasm)], + *(arg for knob, value in GC_KNOBS.items() for arg in ("--env", f"{knob}={value}")), str(wasm)], cwd=work, capture_output=True, text=True) sys.stdout.write(executed.stdout) sys.stderr.write(executed.stderr) @@ -104,8 +108,11 @@ def main() -> int: passed, section_bytes = int(summary.group(1)), int(summary.group(4)) if passed < MINIMUM_CHECKS or section_bytes == 0: sys.exit(f"selftest too weak: {passed} checks, {section_bytes}-byte perry_strings section") - copying = re.search(r"copying_minors=(\d+)", executed.stderr) - if not copying or int(copying.group(1)) == 0 or executed.stderr.count("[gc-log] kind=full") < 4: + # A verified evacuating minor (the `copying_minors` counter is an + # instrument, absent from this uninstrumented archive) and one full + # collection per collect(). + verified = len(re.findall(r"^\[gc-verify\] minor=\d+ evacuation_ok ", executed.stderr, re.M)) + if verified == 0 or executed.stderr.count("[gc-log] kind=full") < 4: sys.exit("the moving/full-collection subject was not live") seeds = re.search(r"wasm static seeds: (\d+) seeds, (\d+) image, (\d+) by-names mints", executed.stderr) if not seeds or int(seeds.group(1)) < 2 or seeds.group(1) != seeds.group(2) or int(seeds.group(3)) != 0: diff --git a/test-wasi/p1-host/suite.py b/test-wasi/p1-host/suite.py index 00a47549663..5a2c48eec42 100644 --- a/test-wasi/p1-host/suite.py +++ b/test-wasi/p1-host/suite.py @@ -419,9 +419,16 @@ def zeroed_reuse(log: str) -> None: log=zeroed_reuse) +def run_env(host_args: list[str]) -> dict[str, str]: + """The `--env NAME=VALUE` pairs a case hands its guest.""" + return dict(value.split("=", 1) for flag, value in zip(host_args, host_args[1:]) if flag == "--env") + + def compile_case(perry: Path, source: Path, wasm: Path, log: Path, net: bool, - discard: bool = False) -> str | None: - env = dict(os.environ, PERRY_NO_AUTO_OPTIMIZE="1", PERRY_NO_CACHE="1") + discard: bool = False, guest_env: dict[str, str] | None = None) -> str | None: + # The guest's run-time knobs are set while compiling too: the GC + # diagnostics (PERRY_GC_LOG) are installed only when asked for at compile time. + env = dict(os.environ, PERRY_NO_AUTO_OPTIMIZE="1", PERRY_NO_CACHE="1", **(guest_env or {})) env.pop("PERRY_WASM_NET", None) env.pop("PERRY_WASM_DISCARD", None) if net: @@ -511,7 +518,8 @@ def main() -> int: wasm = args.output / f"{c.name}.wasm" try: problem = compile_case(args.perry, HERE / "cases" / f"{c.source or c.name}.ts", wasm, - args.output / f"{c.name}.compile.log", c.net, c.discard) + args.output / f"{c.name}.compile.log", c.net, c.discard, + run_env(c.host_args)) if problem is None: problem = run_case(c, args.host, wasm, args.output) except Exception: # a harness error is a failure, never a skip From e0cdf25f73c2d3c1a71fa18bd8541441b3ef17f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 00:09:44 +0100 Subject: [PATCH 096/144] changelog: number the PR fragment --- ...stics-compile-time.md => 13635-gc-diagnostics-compile-time.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-gc-diagnostics-compile-time.md => 13635-gc-diagnostics-compile-time.md} (100%) diff --git a/changelog.d/0-gc-diagnostics-compile-time.md b/changelog.d/13635-gc-diagnostics-compile-time.md similarity index 100% rename from changelog.d/0-gc-diagnostics-compile-time.md rename to changelog.d/13635-gc-diagnostics-compile-time.md From 53fb3afac5462cdc5047a08e29e00bd6194a2626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 00:22:00 +0000 Subject: [PATCH 097/144] wpfacts: own definite data key shadows the prototype chain (DEC 234) --- changelog.d/0-wpfacts-own-key-shadow.md | 1 + crates/perry-wpfacts/src/gen.rs | 4 +- crates/perry-wpfacts/src/model.rs | 48 +++- crates/perry-wpfacts/src/model/operations.rs | 1 + crates/perry-wpfacts/src/solve.rs | 23 +- crates/perry-wpfacts/src/solve/apply.rs | 45 +++- .../src/solve/call_and_construct.rs | 28 +++ crates/perry-wpfacts/src/solve/lookup.rs | 223 +++++++++++++++++- crates/perry-wpfacts/src/solve/shared.rs | 10 + crates/perry-wpfacts/src/summary.rs | 8 +- crates/perry-wpfacts/src/tests.rs | 1 + .../perry-wpfacts/src/tests/own_key_shadow.rs | 146 ++++++++++++ crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../src/tests/unit_mode/contracts.rs | 6 +- .../src/tests/unit_mode/markers.rs | 6 +- .../src/tests/unit_mode/own_key_shadow.rs | 83 +++++++ .../src/tests/unit_mode/shared_values.rs | 6 +- crates/perry-wpfacts/src/units/driver.rs | 5 +- 18 files changed, 620 insertions(+), 26 deletions(-) create mode 100644 changelog.d/0-wpfacts-own-key-shadow.md create mode 100644 crates/perry-wpfacts/src/tests/own_key_shadow.rs create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/own_key_shadow.rs diff --git a/changelog.d/0-wpfacts-own-key-shadow.md b/changelog.d/0-wpfacts-own-key-shadow.md new file mode 100644 index 00000000000..7cd207dbaca --- /dev/null +++ b/changelog.d/0-wpfacts-own-key-shadow.md @@ -0,0 +1 @@ +Per-unit wpfacts treat an own definite data key (constructor store, field initializer, literal key) as shadowing the prototype chain on read, so a store through an unknown value under a computed key no longer defeats reads of keys an object always holds itself. delete, defineProperty and escape of the object still revoke the key. diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index 4cd55e8a78a..e459404fa8a 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -881,7 +881,9 @@ impl<'a> Gen<'a> { let proto = m.tok_index.get(&TokKind::Proto(c)).copied(); let mut keys: Vec = tp.proto_members.clone(); keys.extend(tp.birth_keys.iter().copied()); - let mut open = tp.has_computed_members; + // DECISIONS 234: a key of its instances may disappear or be + // redefined (delete, defineProperty): no key of theirs is sure. + let mut open = tp.has_computed_members || m.keys_unsure.contains(&c); if let Some(pt) = proto { keys.extend(held(pt)); open |= open_node(m.field_any.get(&pt)); diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index c5ae8fd21c9..2b667ed1997 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -239,6 +239,9 @@ impl SharedReads { SharedWrite::Unmodeled(s) => self.objects.contains_key(s) || !self.keys.is_empty(), // Every chain reaches Object.prototype. SharedWrite::Hard(Shared::ObjectProto, _) => true, + // DECISIONS 234: a key may be gone from any escaped object. + SharedWrite::Hard(Shared::Top, Hard::E5DefineProperty) => true, + SharedWrite::Removed(_) => true, SharedWrite::Hard(s, _) => self.objects.contains_key(s), } } @@ -265,6 +268,10 @@ pub enum SharedWrite { /// `setPrototypeOf` ...) on the object: on `Object.prototype` every /// chain through it is HARD, as the whole program derives it. Hard(Shared, Hard), + /// DECISIONS 234: a delete, defineProperty or `with` under a key + /// (`None`: computed) on an object ⊤ denotes or on a parameter: it may + /// remove or redefine that key of any escaped object (`top_reshape`). + Removed(Option), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -790,7 +797,14 @@ pub enum Cons { CreateProto { reason: u32 }, /// The node is `ProtoNode(of)`: dst ⊇ the values of `key` on each /// prototype (and further up), shared by every receiver of `of`. - ChainUnion { key: Key, dst: NodeId, of: Tok }, + /// `builtin` false: the builtin prototypes on the chain add nothing (an + /// own key shadows them, DECISIONS 234). + ChainUnion { + key: Key, + dst: NodeId, + of: Tok, + builtin: bool, + }, /// The node holds a value given as the prototype of an object (its /// `ProtoNode` is `protos`): a builtin prototype there may be any of /// them (`ObjectProto` collapses them), whose members the chain cannot @@ -1323,15 +1337,43 @@ pub struct Model { /// Fact units whose keys can disappear (delete, defineProperty, with): /// no key of theirs is sure. pub keys_unsure: crate::pts::FxHashSet, + /// DECISIONS 234: keys a `delete`, a `defineProperty` or an accessor + /// through ⊤ or on a builtin prototype may have removed or redefined + /// (`reshaped_any`: every key). A builtin prototype redefined under a + /// key may intercept the store that makes an object's own key (a + /// setter, a read-only value), so the own key no longer shadows it. + pub reshaped_keys: crate::pts::FxHashSet, + pub reshaped_any: bool, + /// DECISIONS 234: reads of an own definite data key that skipped the + /// builtin members of the chain (`Model::shadows`), by receiver: (key, + /// destination, whether the receiver's own builtin members were + /// skipped too). Once the key no longer shadows, the read takes them. + pub shadowed: FxHashMap>, + /// DECISIONS 234: keys a delete or defineProperty through ⊤ may have + /// removed or redefined (`top_reshaped_any`: every key), in any unit + /// (`SharedWrite::Hard(Shared::Top, ..)`). ⊤ denotes escaped objects + /// only (DECISIONS 150): it revokes no other object's key. + pub top_reshaped_keys: crate::pts::FxHashSet, + pub top_reshaped_any: bool, + /// Templates and classes some instance of which escaped (`inst_escaped`) + /// or met a delete, defineProperty or `with` (`inst_lose`): for a + /// class, an instance of it or of a subclass, or a receiver of it. + pub inst_escaped: crate::pts::FxHashSet, + pub inst_lose: crate::pts::FxHashSet, + /// A named define on a parameter is marking it HARD: its removal is + /// already shared under its key. + pub marker_define: bool, /// Calls the runtime makes for a protocol (ToPrimitive, iteration …). pub protocol_calls: crate::pts::FxHashSet, /// Shared prototype-chain value nodes: (token, key) → node. - pub cv_nodes: FxHashMap<(Tok, Key), NodeId>, + /// `true`: with the builtin prototypes' members; `false`: the program + /// prototypes only (an own key shadows the builtin ones, DECISIONS 234). + pub cv_nodes: FxHashMap<(Tok, Key, bool), NodeId>, /// Prototype closure nodes: token → node. pub cp_nodes: FxHashMap, /// Shared chain unions that stopped at a declared member: resumed when /// that prototype is mutated. - pub stopped_shared: FxHashMap>, + pub stopped_shared: FxHashMap>, /// Keys some accessor may be defined under (known at generation): only /// lookups of these keys walk the chain per receiver. pub accessor_atoms: crate::pts::FxHashSet, diff --git a/crates/perry-wpfacts/src/model/operations.rs b/crates/perry-wpfacts/src/model/operations.rs index 70e3013986a..04c56ab513b 100644 --- a/crates/perry-wpfacts/src/model/operations.rs +++ b/crates/perry-wpfacts/src/model/operations.rs @@ -582,6 +582,7 @@ impl Model { d.esc_why.entry(t).or_insert((w, n)); } self.virt_revoke(t, None); + self.note_inst(t, false); } if new & MARK_ESCAPE != 0 && !described { self.set_marker_top(t); diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index fde6194d2a8..bfc9ae734c6 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1048,6 +1048,9 @@ impl Model { for &c in &fresh { self.virt_by_class.entry(c).or_default().push(t); } + if !fresh.is_empty() { + self.flush_shadowed(Some(t)); + } if let Some(&pn) = self.proto_node.get(&t) { for p in fresh { let pt = self.tok(TokKind::Proto(p)); @@ -1070,9 +1073,15 @@ impl Model { let n = self.node_l("virt-load"); self.virt_nodes.insert((t, a), n); self.virt_tok_keys.entry(t).or_default().push(a); - let cv = self.cv(t, Key::Named(a)); + // DECISIONS 234: an own definite data key of every instance shadows + // the builtin prototypes; its value is the own field. + let shadow = self.shadows(t, a); + let cv = self.cv_of(t, Key::Named(a), !shadow); self.edge(cv, n); - let own = self.virt_own(&classes, a); + if shadow { + self.note_shadowed(t, a, n, false); + } + let own = shadow || self.virt_own(&classes, a); if own { let f = self.field(t, a); self.edge(f, n); @@ -1235,6 +1244,7 @@ impl Model { } None => { self.virt_open.insert(p); + self.flush_shadowed(None); None } }; @@ -1818,8 +1828,13 @@ impl Model { format!("implicit {n} {reason} {}", proto.name()) } Cons::CreateProto { reason } => format!("createproto {n} {reason}"), - Cons::ChainUnion { key, dst, of } => { - format!("chainunion {n} {} {dst} {of}", self.key_name(*key)) + Cons::ChainUnion { + key, + dst, + of, + builtin, + } => { + format!("chainunion {n} {} {dst} {of} {builtin}", self.key_name(*key)) } Cons::ProtoGiven { protos } => format!("protogiven {n} {protos}"), Cons::ParentGiven { of } => format!("parentgiven {n} {of}"), diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index 389e87b4eaf..c1e16b4c1f0 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -331,6 +331,16 @@ impl Model { let k = Some(Key::Named(key)); self.note_fnproto_write(TOP, k, Hard::E7Delete.code()); builtins::note_native_write(self, TOP, k, "E7 delete"); + self.top_reshape(Some(key)); + let k = self.atoms[key as usize].clone(); + self.note_shared(SharedWrite::Removed(Some(k))); + } else if self.is_marker(t) { + // DECISIONS 234: key `key` of what the callers pass. + let k = self.atoms[key as usize].clone(); + self.note_shared(SharedWrite::Removed(Some(k))); + self.marker_define = true; + self.mark_hard(t, Hard::E7Delete, reason); + self.marker_define = false; } else { self.mark_hard(t, Hard::E7Delete, reason); } @@ -351,6 +361,14 @@ impl Model { } Cons::DefineKey { key, data, reason } => { for &t in toks { + // DECISIONS 234: on a parameter it defines key `key` of + // what the callers pass (shared under its key). + self.marker_define = self.is_marker(t); + if self.marker_define { + self.note_inst(t, true); + let k = self.atoms[key as usize].clone(); + self.note_shared(SharedWrite::Removed(Some(k))); + } // A key the layout already holds as data may move into an // accessor pair in its slot, and a class instance's layout // is the class's: E5 HARD, as any defineProperty was. (On @@ -395,10 +413,19 @@ impl Model { let k = Some(Key::Named(key)); self.note_fnproto_write(TOP, k, Hard::E5DefineProperty.code()); builtins::note_native_write(self, TOP, k, "E5 Object.defineProperty"); + self.reshape(Some(key)); + // DECISIONS 234: in every unit, as an accessor + // through ⊤ under the key (a superset: what a + // descriptor installs, a getter included). + self.top_reshape(Some(key)); + let k = self.atoms[key as usize].clone(); + self.note_shared(SharedWrite::Accessor(Shared::Top, k.clone())); + self.note_shared(SharedWrite::Removed(Some(k))); } _ => self.mark_hard(t, Hard::E5DefineProperty, reason), } } + self.marker_define = false; } Cons::DefineAll { o, @@ -465,8 +492,16 @@ impl Model { } } } - Cons::ChainUnion { key, dst, of } => { + Cons::ChainUnion { + key, + dst, + of, + builtin, + } => { for &q in toks { + if !builtin && self.is_builtin_proto(q) { + continue; + } if q == TOP { self.add_tok(dst, TOP); if self.is_constructor_key(key) { @@ -486,9 +521,10 @@ impl Model { self.edge(f, dst); self.with_b(|m| builtins::chain_load(m, of, q, key, dst)); if self.stops_at(q, key) { - self.stopped_shared.entry(q).or_default().push((key, dst)); + let v = self.stopped_shared.entry(q).or_default(); + v.push((key, dst, builtin)); } else { - let up = self.cv(q, key); + let up = self.cv_of(q, key, builtin); self.edge(up, dst); } } @@ -675,6 +711,9 @@ impl Model { } self.note_fnproto_write(t, Some(Key::Named(key)), "accessor"); builtins::note_native_write(self, t, Some(Key::Named(key)), "accessor"); + if t == TOP || self.is_chain_proto(t) { + self.reshape(Some(key)); + } if t == TOP { continue; } diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index e8158c18405..a32c03318b1 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -1658,6 +1658,7 @@ impl Model { None => format!("escape: {}", m.reasons[reason as usize]), }); self.virt_revoke(t, None); + self.note_inst(t, false); if let Some(is) = self.contract_recv.get(&t).cloned() { self.recv_log .entry(t) @@ -1802,6 +1803,33 @@ impl Model { pub fn mark_hard(&mut self, t: Tok, kind: Hard, reason: u32) { self.virt_revoke(t, None); + // DECISIONS 234: under a computed key, a defineProperty on a + // prototype of the runtime, or through ⊤ (which may be one), may + // redefine any key there (`reshape`); a delete or `with` through ⊤ + // may remove any key of an escaped object (`top_reshape`). Every + // unit sees it: the other units' objects escape into ⊤ too. On an + // object it may remove its own keys; on a receiver marker, those of + // the other units' instances of its class, which are ⊤ here. + if matches!( + kind, + Hard::E5DefineProperty | Hard::E7Delete | Hard::E15With + ) { + if t == TOP { + if kind == Hard::E5DefineProperty { + self.reshape(None); + self.note_shared(SharedWrite::Hard(Shared::Top, kind)); + } + self.top_reshape(None); + self.note_shared(SharedWrite::Removed(None)); + } else if self.is_chain_proto(t) { + self.reshape(None); + } else { + self.note_inst(t, true); + if self.is_marker(t) && !self.marker_define { + self.note_shared(SharedWrite::Removed(None)); + } + } + } if matches!( kind, Hard::E5DefineProperty | Hard::E7Delete | Hard::E15With diff --git a/crates/perry-wpfacts/src/solve/lookup.rs b/crates/perry-wpfacts/src/solve/lookup.rs index 00cbcf03c9e..d5e7b2d9b9a 100644 --- a/crates/perry-wpfacts/src/solve/lookup.rs +++ b/crates/perry-wpfacts/src/solve/lookup.rs @@ -57,6 +57,168 @@ impl Model { } } + /// DECISIONS 234: does own key `a` shadow the builtin prototypes on + /// every object `t` denotes? It does when each of them holds `a` as an + /// own data property from birth (a definite birth key of a fact unit, + /// no key of which can disappear; for a receiver token or marker, of + /// each class it stands for, `class_holds`), and nothing redefined `a` + /// on a builtin prototype or through ⊤ (`reshaped_keys`), where a setter + /// or a read-only value would intercept the store that makes the key. + pub(crate) fn shadows(&self, t: Tok, a: Atom) -> bool { + if t == TOP || self.reshaped_any || self.reshaped_keys.contains(&a) || self.objproto_accessor { + return false; + } + let top = self.top_reshaped_any || self.top_reshaped_keys.contains(&a); + if let Some(classes) = self.virt_protos.get(&t) { + // Instances of these classes made in another unit may have + // escaped there, unseen here: a delete or defineProperty + // through ⊤ revokes the key on them. + return !top + && !classes.is_empty() + && classes.iter().all(|&p| self.class_holds(p, a)); + } + match self.tmpl_of(t) { + Some(tm) => { + self.tmpls[tm as usize].kind.is_fact_unit() + && !self.keys_unsure.contains(&tm) + && !self.inst_lose.contains(&tm) + && !(top && self.inst_escaped.contains(&tm)) + && (self.tmpls[tm as usize].definite.contains(&a) || self.spread_sure(tm, a)) + } + None => false, + } + } + + /// Every instance of class `p` and of each subclass holds `a` as an own + /// data property: a definite birth key of `p` (its constructor runs in + /// every subclass's `super()`), no instance or receiver of `p` met a + /// delete, defineProperty or `with` (`inst_lose`; the keys an instance + /// gains, `virt_open`, take none away), no accessor on the chain + /// intercepts the store, + /// and no subclass of another unit defines `a` or deletes or redefines + /// a key (census `override a of p`, `override * of p`). + fn class_holds(&self, p: TmplId, a: Atom) -> bool { + let tp = &self.tmpls[p as usize]; + let star = self.atom_index.get(crate::STAR_KEY).copied(); + tp.definite.contains(&a) + && !self.keys_unsure.contains(&p) + && !self.inst_lose.contains(&p) + && !self.chain_accessor_key(p, a) + && !self + .override_keys + .get(&p) + .is_some_and(|v| v.contains(&a) || star.is_some_and(|s| v.contains(&s))) + } + + /// A read that skipped the builtin members (`shadows`): resumed by + /// `flush_shadowed` once the key no longer shadows them. + pub(crate) fn note_shadowed(&mut self, t: Tok, a: Atom, dst: NodeId, own_members: bool) { + let v = self.shadowed.entry(t).or_default(); + if !v.contains(&(a, dst, own_members)) { + v.push((a, dst, own_members)); + } + } + + /// The shadowed reads (of `t`; `None`: of every receiver) whose key no + /// longer shadows the builtin prototypes take their members now. + pub(crate) fn flush_shadowed(&mut self, of: Option) { + let toks: Vec = match of { + Some(t) if self.shadowed.contains_key(&t) => vec![t], + Some(_) => return, + None => { + let mut v: Vec = self.shadowed.keys().copied().collect(); + v.sort_unstable(); + v + } + }; + for t in toks { + let reads = self.shadowed.remove(&t).unwrap_or_default(); + let mut kept = Vec::new(); + for (a, dst, own_members) in reads { + if self.shadows(t, a) { + kept.push((a, dst, own_members)); + continue; + } + let key = Key::Named(a); + let cv = self.cv(t, key); + self.edge(cv, dst); + if own_members { + self.with_b(|m| builtins::tok_load(m, t, key, dst)); + } + } + if !kept.is_empty() { + self.shadowed.insert(t, kept); + } + } + } + + /// DECISIONS 234: key `key` (`None`: any key) of a builtin prototype may + /// have been deleted or redefined (through ⊤, or on the prototype). + pub(crate) fn reshape(&mut self, key: Option) { + let star = self.atom_index.get(crate::STAR_KEY).copied(); + let fresh = match key { + Some(a) if Some(a) != star => self.reshaped_keys.insert(a), + _ => !std::mem::replace(&mut self.reshaped_any, true), + }; + if fresh { + self.flush_shadowed(None); + } + } + + /// DECISIONS 234: key `key` (`None`: any key) of an object ⊤ denotes, + /// an escaped one, may have been deleted or redefined. + pub(crate) fn top_reshape(&mut self, key: Option) { + let star = self.atom_index.get(crate::STAR_KEY).copied(); + let fresh = match key { + Some(a) if Some(a) != star => { + !self.top_reshaped_any && self.top_reshaped_keys.insert(a) + } + _ => !std::mem::replace(&mut self.top_reshaped_any, true), + }; + if fresh { + self.flush_shadowed(None); + } + } + + /// The template of object `t` and its classes up the chain (for a + /// receiver token or marker: the classes it stands for). + fn inst_classes(&self, t: Tok) -> Vec { + if let Some(ps) = self.virt_protos.get(&t) { + return ps.clone(); + } + let Some(tm) = self.tmpl_of(t) else { + return Vec::new(); + }; + let mut seen: Vec = Vec::new(); + let mut work = vec![tm]; + while let Some(c) = work.pop() { + if seen.contains(&c) { + continue; + } + seen.push(c); + let tp = &self.tmpls[c as usize]; + work.extend(tp.parent); + work.extend(tp.parent_links.iter().copied()); + } + seen + } + + /// DECISIONS 234: `t` escaped (`lose` false) or met a delete, + /// defineProperty or `with` (`lose`): an own key of it may be gone. + pub(crate) fn note_inst(&mut self, t: Tok, lose: bool) { + let mut fresh = false; + for c in self.inst_classes(t) { + fresh |= if lose { + self.inst_lose.insert(c) + } else { + self.inst_escaped.insert(c) + }; + } + if fresh && (lose || self.top_reshaped_any || !self.top_reshaped_keys.is_empty()) { + self.flush_shadowed(None); + } + } + /// The reads of `tm` that found a key no longer sure read `undefined`. pub(crate) fn flush_present_reads(&mut self, tm: TmplId) { let reads = self.present_reads.remove(&tm).unwrap_or_default(); @@ -73,6 +235,7 @@ impl Model { if !kept.is_empty() { self.present_reads.insert(tm, kept); } + self.flush_shadowed(None); } /// dst ⊇ value of `key` read on object `t` (receiver `origin`). @@ -191,9 +354,20 @@ impl Model { // `undefined`, is never reached. Its values stay (a superset). let defined = present && !self.is_proto_key(key); let chain = if defined { self.defined_into(dst) } else { dst }; - if !(self.is_marker(t) && self.beside_values(t)) { + // DECISIONS 234: a key every object `t` denotes holds as an own data + // property never reaches the builtin prototypes, nor the builtin + // members of `t`'s own kind. + let shadow = match key { + Key::Named(a) if !self.is_proto_key(key) => self.shadows(t, a), + _ => false, + }; + let own_members = !(self.is_marker(t) && self.beside_values(t)); + if own_members && !shadow { self.with_b(|m| builtins::tok_load(m, t, key, chain)); } + if let (true, Key::Named(a)) = (shadow, key) { + self.note_shadowed(t, a, dst, own_members); + } if matches!(self.toks[t as usize], TokKind::ObjectProto) && !self.is_proto_key(key) { self.with_b(|m| builtins::intrinsic_proto_own(m, key, dst)); } @@ -203,7 +377,7 @@ impl Model { return; } // The prototype chain's values, shared by every receiver of `t`. - let cv = self.cv(t, key); + let cv = self.cv_of(t, key, !shadow); if defined { // A rule on the chain node, not an edge into a relay: an // observation node feeds only observation nodes (`edge`), and @@ -447,16 +621,51 @@ impl Model { /// The values `key` takes on `t`'s prototypes (transitively). pub fn cv(&mut self, t: Tok, key: Key) -> NodeId { - if let Some(&n) = self.cv_nodes.get(&(t, key)) { + self.cv_of(t, key, true) + } + + /// `cv`, with the builtin prototypes' members or (`builtin` false) + /// with the program prototypes' values only. + pub fn cv_of(&mut self, t: Tok, key: Key, builtin: bool) -> NodeId { + if let Some(&n) = self.cv_nodes.get(&(t, key, builtin)) { return n; } let n = self.node_l("chainval"); - self.cv_nodes.insert((t, key), n); + self.cv_nodes.insert((t, key, builtin), n); let pn = self.proto_node(t); - self.cons(pn, Cons::ChainUnion { key, dst: n, of: t }); + self.cons( + pn, + Cons::ChainUnion { + key, + dst: n, + of: t, + builtin, + }, + ); n } + /// A prototype of the runtime an instance's chain can reach + /// (`ObjectProto` stands for each of them): redefining a key there may + /// intercept the store that makes an own key (a setter, a read-only + /// value). A namespace object or a builtin function is no instance's + /// prototype. + pub(crate) fn is_chain_proto(&self, q: Tok) -> bool { + matches!( + self.toks[q as usize], + TokKind::ObjectProto | TokKind::NativeProto(_) + ) + } + + /// A builtin prototype on a chain (`ObjectProto` stands for each of + /// them): its members are the runtime's, and what stores into ⊤ wrote. + pub(crate) fn is_builtin_proto(&self, q: Tok) -> bool { + matches!( + self.toks[q as usize], + TokKind::ObjectProto | TokKind::NativeProto(_) | TokKind::Builtin(_) | TokKind::BuiltinFn + ) + } + /// The node whose values reach `dst` without `undefined` (`Cons::Defined`): /// `tok_load` writes into it (no edge, so an observation `dst` is fine). fn defined_into(&mut self, dst: NodeId) -> NodeId { @@ -632,8 +841,8 @@ impl Model { } } if let Some(v) = self.stopped_shared.remove(&p) { - for (key, dst) in v { - let up = self.cv(p, key); + for (key, dst, builtin) in v { + let up = self.cv_of(p, key, builtin); self.edge(up, dst); } } diff --git a/crates/perry-wpfacts/src/solve/shared.rs b/crates/perry-wpfacts/src/solve/shared.rs index 26157481c1a..55af8c7026a 100644 --- a/crates/perry-wpfacts/src/solve/shared.rs +++ b/crates/perry-wpfacts/src/solve/shared.rs @@ -77,6 +77,7 @@ impl Model { Some(Key::Named(key)), "another unit's accessor", ); + self.reshape(Some(key)); match s { Shared::Top => {} // E23: every lookup that reaches it may run the accessor. @@ -119,6 +120,15 @@ impl Model { // Its effect is the stores and accessors the driver derives // (`units::shared_closure`). SharedWrite::Handed(_) => {} + // DECISIONS 234: another unit's defineProperty through ⊤ under a + // computed key (⊤ may be a builtin prototype); its delete, + // defineProperty or `with` through ⊤ or on a parameter: a key of + // an escaped object may be gone. + SharedWrite::Hard(Shared::Top, Hard::E5DefineProperty) => self.reshape(None), + SharedWrite::Removed(k) => { + let a = k.as_ref().map(|k| self.atom(k)); + self.top_reshape(a) + } SharedWrite::Hard(s, kind) => { let t = self.shared_tok(s); let r = self.reason(&format!("{} by another unit", kind.code())); diff --git a/crates/perry-wpfacts/src/summary.rs b/crates/perry-wpfacts/src/summary.rs index 12b959f12e6..a9fcbb19928 100644 --- a/crates/perry-wpfacts/src/summary.rs +++ b/crates/perry-wpfacts/src/summary.rs @@ -334,10 +334,16 @@ pub fn map_cons( }, Implicit { reason, proto } => Implicit { reason, proto }, CreateProto { reason } => CreateProto { reason }, - ChainUnion { key, dst, of } => ChainUnion { + ChainUnion { + key, + dst, + of, + builtin, + } => ChainUnion { key, dst: n(dst), of, + builtin, }, ProtoGiven { protos } => ProtoGiven { protos: n(protos) }, ParentGiven { of } => ParentGiven { of }, diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index 67907cd9273..d2b21a34890 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -63,6 +63,7 @@ mod unit_mode; mod unit_namespace; mod unit_shared; mod unit_this; +mod own_key_shadow; use crate::{compute, FactsConfig, ProgramFacts, Verdict}; diff --git a/crates/perry-wpfacts/src/tests/own_key_shadow.rs b/crates/perry-wpfacts/src/tests/own_key_shadow.rs new file mode 100644 index 00000000000..13608d0fa62 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/own_key_shadow.rs @@ -0,0 +1,146 @@ +//! DECISIONS 234: an own definite data key shadows the prototype chain on +//! read. A store into ⊤ under a computed key may write every key of +//! `Object.prototype`; a key every object of the receiver holds as an own +//! data property from birth never reaches it, so the read is the own +//! field's value (plus the chain's program values, a superset), not the +//! builtin prototype's unknown member. Anything that may remove or redefine +//! the own key revokes it: `delete` (named, computed), `defineProperty` +//! (through ⊤ too, which may be a builtin prototype whose setter intercepts +//! the store that makes the key), and a `delete` through ⊤ on an object +//! that escaped (⊤ denotes escaped objects only, DECISIONS 150). + +use super::*; + +/// `h` is a value from unmodeled code; `h[name] = …` may write any key of +/// `Object.prototype` (node: `hostFn` returns `Object.prototype`). +fn size_verdict(body: &str) -> Verdict { + let src = format!( + r#" + declare const name: string; + const h: any = (globalThis as any).hostFn(); + h[name] = 1; + class Svc {{ size = 3; }} + {body} + "# + ); + site(&facts(&src), "size").verdict.clone() +} + +fn proven(v: &Verdict) -> bool { + !matches!(v, Verdict::No(_)) +} + +const P: &str = r#" + class P { + svc: Svc; + constructor(s: Svc) { this.svc = s; } + total() { return this.svc.size; } + } +"#; + +#[test] +fn a_constructor_store_shadows_a_top_written_prototype() { + let v = size_verdict(&format!("{P} console.log(new P(new Svc()).total());")); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn a_field_initializer_shadows_a_top_written_prototype() { + let v = size_verdict( + "class F { svc = new Svc(); total() { return this.svc.size; } } \ + console.log(new F().total());", + ); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn a_subclass_instance_holds_its_parents_definite_key() { + let v = size_verdict(&format!( + "{P} class Q extends P {{ k = 1; }} console.log(new Q(new Svc()).total());" + )); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn a_literal_key_shadows_a_top_written_prototype() { + let v = size_verdict("const o = { svc: new Svc() }; console.log(o.svc.size);"); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn a_deleted_key_reads_the_prototype() { + let v = size_verdict(&format!( + "{P} const p: any = new P(new Svc()); delete p.svc; console.log(p.total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn a_computed_delete_reads_the_prototype() { + let v = size_verdict(&format!( + "{P} const p: any = new P(new Svc()); delete p[name]; console.log(p.total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn a_delete_through_top_keeps_the_key_of_an_object_that_never_escaped() { + let v = size_verdict(&format!( + "{P} const p = new P(new Svc()); delete h.svc; console.log(p.total());" + )); + assert!(proven(&v), "{v:?}"); +} + +#[test] +fn a_computed_delete_through_top_keeps_the_key_of_an_object_that_never_escaped() { + let v = size_verdict(&format!( + "{P} const p = new P(new Svc()); delete h[name]; console.log(p.total());" + )); + assert!(proven(&v), "{v:?}"); +} + +/// node: `hostFn(p)` hands `p` out and `hostGet()` returns it; the delete +/// through ⊤ removes `p.svc`, so `this.svc` reads `Object.prototype.svc`. +#[test] +fn a_computed_delete_through_top_revokes_the_key_of_an_escaped_object() { + let v = size_verdict(&format!( + "{P} const p = new P(new Svc()); (globalThis as any).hostFn(p); \ + delete (globalThis as any).hostGet()[name]; console.log(p.total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn a_define_property_through_top_revokes_the_key() { + let v = size_verdict(&format!( + "{P} Object.defineProperty(h, name, {{ get() {{ return 1; }} }}); \ + console.log(new P(new Svc()).total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn a_define_property_on_the_receiver_revokes_the_key() { + let v = size_verdict(&format!( + "{P} const p: any = new P(new Svc()); \ + Object.defineProperty(p, \"svc\", {{ get() {{ return 1; }} }}); console.log(p.total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn an_escaped_receiver_reads_the_prototype() { + let v = size_verdict(&format!( + "{P} const p = new P(new Svc()); (globalThis as any).hostFn(p); console.log(p.total());" + )); + assert!(!proven(&v), "{v:?}"); +} + +#[test] +fn a_receiver_without_the_key_reads_the_prototype() { + let v = size_verdict( + "declare const c: boolean; \ + const o: any = c ? { svc: new Svc() } : { k: 1 }; console.log(o.svc.size);", + ); + assert!(!proven(&v), "{v:?}"); +} diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index bc344eb4d6c..df4e49c2926 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -611,3 +611,5 @@ mod fn_transfer; mod protocol_touch; #[path = "unit_mode/unit_cycle_order.rs"] mod unit_cycle_order; +#[path = "unit_mode/own_key_shadow.rs"] +mod own_key_shadow; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs index 08226ebfabd..a8cc97ffdb3 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs @@ -318,8 +318,10 @@ pub(super) fn a_broken_namespace_field_function_resolves_only_the_units_that_see // below. DCE: a read of a key the object surely holds takes its chain // through a relay that drops the chain end's `undefined` // (`Cons::Defined`): a relay node for the read's own builtin members - // and a rule on the chain node, [758, 1094, 952, 42] -> the pin below. - assert_eq!(pf.round_work, [762, 1104, 960, 42], "{:?}", pf.units); + // and a rule on the chain node, [758, 1094, 952, 42] -> [762, 1104, + // 960, 42]. DECISIONS 234: a read of an own definite key takes the + // chain's program values only (a chain node of its own) -> the pin below. + assert_eq!(pf.round_work, [763, 1103, 960, 42], "{:?}", pf.units); } #[test] diff --git a/crates/perry-wpfacts/src/tests/unit_mode/markers.rs b/crates/perry-wpfacts/src/tests/unit_mode/markers.rs index d4c84119ee3..2c432690629 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/markers.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/markers.rs @@ -1368,8 +1368,10 @@ pub(super) fn a_store_effect_ascends_so_the_units_reach_a_fixpoint() { .expect("D16: the per-unit fixpoint did not end in 60 s"); // ABM3 (static rule, CWN 10-27): `b`'s store into ⊤ (`y.k = 1`) under // `k` writes `k` of every builtin prototype; `a`'s lookups of `k` that - // end at `Object.prototype` read it, so `a` solves once more: three. - assert_eq!(pf.rounds, 3, "rounds {:?}", pf.round_work); + // end at `Object.prototype` read it, so `a` solved once more: three. + // DECISIONS 234: `k` is an own definite key of `o`, which shadows the + // builtin prototypes: `a`'s read is the field's, two rounds again. + assert_eq!(pf.rounds, 2, "rounds {:?}", pf.round_work); // `a` keeps performing the store: `k` is written after construction. let o = pf .templates diff --git a/crates/perry-wpfacts/src/tests/unit_mode/own_key_shadow.rs b/crates/perry-wpfacts/src/tests/unit_mode/own_key_shadow.rs new file mode 100644 index 00000000000..4a0e0333354 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/own_key_shadow.rs @@ -0,0 +1,83 @@ +//! DECISIONS 234 per unit: on a published class's receiver marker (another +//! unit calls the method), a definite own key of the class shadows the +//! builtin prototypes a store into ⊤ may have written, for instances of +//! the class and of every subclass. (A subclass of another unit reports +//! the keys its instances hold, inherited ones included, as `override k +//! of`: the marker's own field is then another unit's, and does not +//! shadow: DECISIONS 231(d) is not built.) A subclass of another unit that may +//! delete or redefine a key (census `override * of`), another unit's +//! delete on an instance (census report) and an accessor through ⊤ in any +//! unit revoke it. + +use super::*; + +/// `h.svc = 1` may write `Object.prototype.svc` (node: `hostFn` returns +/// `Object.prototype`); a computed key would write `total` there too. +const P: &str = r#"const h: any = (globalThis as any).hostFn(); +h.svc = 1; +export class Svc { size = 3; } +export class P { + svc: Svc; + constructor(s: Svc) { this.svc = s; } + total() { return this.svc.size; } +}"#; + +/// Is p.ts's `this.svc.size` proven, per unit and in the whole program? +fn size_proven(mods: &[(&str, &str)]) -> (bool, bool) { + let at = |pf: &ProgramFacts| { + proven_set(pf) + .iter() + .any(|s| s.starts_with("p.ts@") && s.ends_with(".size")) + }; + (at(&units_of(mods)), at(&whole_of(mods))) +} + +#[test] +fn a_definite_key_shadows_on_the_receiver_marker() { + let m = r#"import { P, Svc } from "./p"; +console.log(new P(new Svc()).total());"#; + assert_eq!(size_proven(&[("p", P), ("m", m)]), (true, true)); +} + +#[test] +fn a_subclass_that_deletes_the_key_revokes_it() { + let b = r#"import { P } from "./p"; +export class Q extends P { constructor(s: any) { super(s); delete (this as any).svc; } }"#; + let m = r#"import { Q } from "./b"; +import { Svc } from "./p"; +console.log(new Q(new Svc()).total());"#; + assert_eq!(size_proven(&[("p", P), ("b", b), ("m", m)]), (false, false)); +} + +#[test] +fn another_units_delete_revokes_the_key() { + let m = r#"import { P, Svc } from "./p"; +const p: any = new P(new Svc()); +delete p.svc; +console.log(p.total());"#; + assert_eq!(size_proven(&[("p", P), ("m", m)]), (false, false)); +} + +/// `kill` deletes a key of its parameter: per unit, another unit's +/// instance of P that reached it is ⊤ in p.ts, so the delete revokes the key +/// on P's receiver marker there (`SharedWrite::Hard(⊤, E7)`). +#[test] +fn another_units_delete_through_a_parameter_revokes_the_key() { + let k = r#"export function kill(o: any, key: string) { delete o[key]; }"#; + let m = r#"import { P, Svc } from "./p"; +import { kill } from "./k"; +declare const name: string; +const p = new P(new Svc()); +kill(p, name); +console.log(p.total());"#; + assert_eq!(size_proven(&[("p", P), ("k", k), ("m", m)]), (false, false)); +} + +#[test] +fn another_units_accessor_through_top_revokes_the_key() { + let m = r#"import { P, Svc } from "./p"; +const g: any = (globalThis as any).hostFn(); +Object.defineProperty(g, "svc", { get() { return 1; }, set(v: any) {} }); +console.log(new P(new Svc()).total());"#; + assert_eq!(size_proven(&[("p", P), ("m", m)]), (false, false)); +} diff --git a/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs b/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs index 604f8699dab..6f8b935b8c8 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/shared_values.rs @@ -11,8 +11,10 @@ pub(super) fn a_member_function_returning_its_own_layout_is_built_once() { .expect("D16: the per-unit fixpoint did not end in 60 s"); // ABM3 (static rule, CWN 10-27): `b`'s store into ⊤ (`y.k = 1`) under // `k` writes `k` of every builtin prototype; `a`'s lookups of `k` that - // end at `Object.prototype` read it, so `a` solves once more: three. - assert_eq!(pf.rounds, 3, "rounds {:?}", pf.round_work); + // end at `Object.prototype` read it, so `a` solved once more: three. + // DECISIONS 234: `k` is an own definite key of `o`, which shadows the + // builtin prototypes: `a`'s read is the field's, two rounds. + assert_eq!(pf.rounds, 2, "rounds {:?}", pf.round_work); } /// DECISIONS 113: every unit storing under a computed global key writes ⊤ diff --git a/crates/perry-wpfacts/src/units/driver.rs b/crates/perry-wpfacts/src/units/driver.rs index 1d822d213b9..159c7414817 100644 --- a/crates/perry-wpfacts/src/units/driver.rs +++ b/crates/perry-wpfacts/src/units/driver.rs @@ -1206,7 +1206,10 @@ pub(super) fn replay_is_own(w: &crate::model::SharedWrite) -> bool { use crate::model::{Shared, SharedWrite}; match w { SharedWrite::Store(s, _) | SharedWrite::Accessor(s, _) => *s == Shared::Top, - SharedWrite::Unmodeled(_) | SharedWrite::Handed(_) | SharedWrite::Hard(..) => true, + SharedWrite::Unmodeled(_) + | SharedWrite::Handed(_) + | SharedWrite::Hard(..) + | SharedWrite::Removed(_) => true, } } From 047ec699e2198306ed8ba48b9a5147b861610627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 02:01:10 +0100 Subject: [PATCH 098/144] changelog: number the PR fragment --- ...-wpfacts-own-key-shadow.md => 13637-wpfacts-own-key-shadow.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-own-key-shadow.md => 13637-wpfacts-own-key-shadow.md} (100%) diff --git a/changelog.d/0-wpfacts-own-key-shadow.md b/changelog.d/13637-wpfacts-own-key-shadow.md similarity index 100% rename from changelog.d/0-wpfacts-own-key-shadow.md rename to changelog.d/13637-wpfacts-own-key-shadow.md From b28eca78519e33c04dd2f2f7d8c9d5c004d6e988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 01:13:19 +0000 Subject: [PATCH 099/144] wpfacts: drop the marker Removed shares; the Hard share covers them (tssrc 20,139 unchanged) --- crates/perry-wpfacts/src/model.rs | 8 +++----- crates/perry-wpfacts/src/solve/apply.rs | 15 +-------------- .../perry-wpfacts/src/solve/call_and_construct.rs | 3 --- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index 2b667ed1997..6d03b9d1435 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -269,8 +269,9 @@ pub enum SharedWrite { /// chain through it is HARD, as the whole program derives it. Hard(Shared, Hard), /// DECISIONS 234: a delete, defineProperty or `with` under a key - /// (`None`: computed) on an object ⊤ denotes or on a parameter: it may - /// remove or redefine that key of any escaped object (`top_reshape`). + /// (`None`: computed) on an object ⊤ denotes: it may remove or redefine + /// that key of any escaped object (`top_reshape`). A delete or define on + /// a parameter marker travels as `Hard` (the callers pass the objects). Removed(Option), } @@ -1360,9 +1361,6 @@ pub struct Model { /// class, an instance of it or of a subclass, or a receiver of it. pub inst_escaped: crate::pts::FxHashSet, pub inst_lose: crate::pts::FxHashSet, - /// A named define on a parameter is marking it HARD: its removal is - /// already shared under its key. - pub marker_define: bool, /// Calls the runtime makes for a protocol (ToPrimitive, iteration …). pub protocol_calls: crate::pts::FxHashSet, /// Shared prototype-chain value nodes: (token, key) → node. diff --git a/crates/perry-wpfacts/src/solve/apply.rs b/crates/perry-wpfacts/src/solve/apply.rs index c1e16b4c1f0..b8ee48fe950 100644 --- a/crates/perry-wpfacts/src/solve/apply.rs +++ b/crates/perry-wpfacts/src/solve/apply.rs @@ -334,13 +334,6 @@ impl Model { self.top_reshape(Some(key)); let k = self.atoms[key as usize].clone(); self.note_shared(SharedWrite::Removed(Some(k))); - } else if self.is_marker(t) { - // DECISIONS 234: key `key` of what the callers pass. - let k = self.atoms[key as usize].clone(); - self.note_shared(SharedWrite::Removed(Some(k))); - self.marker_define = true; - self.mark_hard(t, Hard::E7Delete, reason); - self.marker_define = false; } else { self.mark_hard(t, Hard::E7Delete, reason); } @@ -361,13 +354,8 @@ impl Model { } Cons::DefineKey { key, data, reason } => { for &t in toks { - // DECISIONS 234: on a parameter it defines key `key` of - // what the callers pass (shared under its key). - self.marker_define = self.is_marker(t); - if self.marker_define { + if self.is_marker(t) { self.note_inst(t, true); - let k = self.atoms[key as usize].clone(); - self.note_shared(SharedWrite::Removed(Some(k))); } // A key the layout already holds as data may move into an // accessor pair in its slot, and a class instance's layout @@ -425,7 +413,6 @@ impl Model { _ => self.mark_hard(t, Hard::E5DefineProperty, reason), } } - self.marker_define = false; } Cons::DefineAll { o, diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index a32c03318b1..985c47f22f5 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -1825,9 +1825,6 @@ impl Model { self.reshape(None); } else { self.note_inst(t, true); - if self.is_marker(t) && !self.marker_define { - self.note_shared(SharedWrite::Removed(None)); - } } } if matches!( From 87f8f08bfa805ffedb163d5e1793a980d06f851d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 02:32:46 +0000 Subject: [PATCH 100/144] component-host: run live owners' destructors when disposing a driver component All non-HTTP components run on the persistent driver since e41021334d, and dispose dropped that driver, which retires its Store without guest code. A live owned guest resource (an abandoned constructor result) was never destroyed, so the native resources acceptance saw [42,7] instead of [42,7,9]. Disposal now closes the driver client: queued commands are refused, in-flight ones cancelled, and the Store returns to Host::dispose, which destroys live owners as on a legacy handle, also while a guest call is suspended. --- .../0-component-driver-dispose-destructors.md | 14 ++++ crates/perry-component-host/src/driver.rs | 11 +++ .../src/driver/requests.rs | 14 ++++ crates/perry-component-host/src/ffi.rs | 3 +- crates/perry-component-host/src/ffi/driver.rs | 15 +++- .../src/ffi/driver/tests/disposal_tests.rs | 84 +++++++++++++++++++ 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 changelog.d/0-component-driver-dispose-destructors.md diff --git a/changelog.d/0-component-driver-dispose-destructors.md b/changelog.d/0-component-driver-dispose-destructors.md new file mode 100644 index 00000000000..6888f2c7e37 --- /dev/null +++ b/changelog.d/0-component-driver-dispose-destructors.md @@ -0,0 +1,14 @@ +Disposing a component now runs the destructor of every owned guest resource +that is still live, exactly once, in the disposal frame. Since all non-HTTP +components moved to the persistent driver, disposal dropped the driver and +discarded its Store, so a live owner (for example an abandoned constructor +result) was never destroyed. Disposal now closes the driver's submission +channel: queued commands are refused without running guest code, in-flight +commands are cancelled, and the Store returns to `Host::dispose`, which drains +live owners in the same order as a legacy handle. This holds while a guest call +is still suspended. A driver that has already retired its Store (after a +cancelled started call or a failing destructor) has no owners left. + +Driver tests cover a quiescent component and one with a suspended export and a +queued call; both were red on main. The `resources-run` native component +acceptance step is green again. diff --git a/crates/perry-component-host/src/driver.rs b/crates/perry-component-host/src/driver.rs index d7a133b5d0d..fb317b6f631 100644 --- a/crates/perry-component-host/src/driver.rs +++ b/crates/perry-component-host/src/driver.rs @@ -310,6 +310,11 @@ async fn run( }) .await; match event { + Event::Request(Some(request)) if state.disposing.get() => { + request + .reply + .send(Err(anyhow::anyhow!("component is disposed"))); + } Event::Request(Some(request)) => { if request.cancelled.get() { request.reply.send(Err(anyhow::anyhow!( @@ -350,6 +355,12 @@ async fn run( } } } + Event::Request(None) if state.disposing.get() => { + // Cancel in-flight commands; their guest tasks stay + // suspended in the Store that Host::dispose drains. + drop(std::mem::take(&mut pending)); + return Ok::<_, anyhow::Error>(()); + } Event::Request(None) => { ensure!( pending.is_empty() diff --git a/crates/perry-component-host/src/driver/requests.rs b/crates/perry-component-host/src/driver/requests.rs index 98cacc6af21..ed17a71a612 100644 --- a/crates/perry-component-host/src/driver/requests.rs +++ b/crates/perry-component-host/src/driver/requests.rs @@ -25,6 +25,9 @@ pub(super) struct State { pub owner: std::thread::ThreadId, pub alive: Cell, pub abort: Cell, + /// Set by DriverClient::dispose: refuse queued commands, cancel those in + /// flight and return the Store, whose live owners Host::dispose destroys. + pub disposing: Cell, pub active: Cell, pub outstanding: Cell, pub collected_writer_retirement: Cell>, @@ -44,6 +47,7 @@ impl Default for State { owner: std::thread::current().id(), alive: Cell::new(true), abort: Cell::new(false), + disposing: Cell::new(false), active: Cell::new(false), outstanding: Cell::new(0), collected_writer_retirement: Cell::new(None), @@ -570,6 +574,16 @@ impl DriverClient { }) } } +impl DriverClient { + /// Close submission for disposal. The next driver poll refuses queued + /// commands, cancels in-flight ones and returns the Host even while guest + /// tasks are suspended; it never retires a Store with live owners. + pub fn dispose(&self) { + self.state.disposing.set(true); + self.send.close_channel(); + self.state.waker.wake(); + } +} impl Drop for DriverClient { fn drop(&mut self) { self.state.waker.wake(); diff --git a/crates/perry-component-host/src/ffi.rs b/crates/perry-component-host/src/ffi.rs index 99593a2f71e..cd5687bde85 100644 --- a/crates/perry-component-host/src/ffi.rs +++ b/crates/perry-component-host/src/ffi.rs @@ -431,8 +431,9 @@ pub unsafe extern "C" fn perry_component_host_dispose( drop(unsafe { (&mut *bridge.pending.get()).take() }); if let Some(driver) = &bridge.driver { let before = bridge.async_replies.failure_serial(); - driver.dispose(); + let result = driver.dispose(); disposed = true; + result?; ensure!( before == bridge.async_replies.failure_serial(), "component driver disposal callback failed" diff --git a/crates/perry-component-host/src/ffi/driver.rs b/crates/perry-component-host/src/ffi/driver.rs index ca9781d63e6..1868d64b3cf 100644 --- a/crates/perry-component-host/src/ffi/driver.rs +++ b/crates/perry-component-host/src/ffi/driver.rs @@ -122,11 +122,20 @@ impl Actor { ); Ok(id) } - pub fn dispose(&self) { - // Driver Drop first cancels tasks and retires host-owned resources. - self.driver.borrow_mut().take(); + pub fn dispose(&self) -> Result<()> { + // Disposal returns the Store before any observer is dropped, since a + // dropped started call would request hard retirement. Host::dispose + // then destroys every live owner as on a legacy handle. A driver that + // already retired its Store has no owners left to destroy. + self.client.dispose(); + let driver = self.driver.borrow_mut().take(); + let result = match driver.map(futures_lite::future::block_on) { + Some(Ok(mut host)) => host.dispose(), + Some(Err(_)) | None => Ok(()), + }; self.calls.borrow_mut().clear(); self.failure.borrow_mut().take(); + result } fn poll(&self, wake: WakeCallback, agent: u64) -> Result { let waker = std::task::Waker::from(Arc::new(async_calls::Notify(wake, agent))); diff --git a/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs b/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs index 9bdd730b755..966f9d76eab 100644 --- a/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs +++ b/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs @@ -70,6 +70,90 @@ fn ffi_driver_resource_disposal_preserves_nominal_identity_and_ignores_transferr } } #[test] +fn ffi_driver_disposal_runs_each_live_owned_guest_destructor_once_in_the_dispose_frame() { + unsafe { + let mut state = State::default(); + new( + &mut state, + crate::resource_tests::WORLD, + crate::resource_tests::COMPONENT, + ); + let released = guest(&mut state, 31); + completed(&mut state, json!({"op":"resource-drop","value":released})); + guest(&mut state, 32); + assert_eq!(state.guest_drops.len(), 1); + // A quiescent component returns its Store before disposal, so the + // live owner is destroyed as on a legacy handle, not discarded. + for marker in [700, 800] { + let frame = Frame { + state: &mut state, + marker, + }; + let mut out = Buffer::default(); + let status = perry_component_host_dispose( + state.host, + &frame as *const Frame as usize as u64, + &mut out, + ); + reply(status, out).unwrap(); + } + assert_eq!(state.guest_drops.len(), 2, "{:?}", state.guest_drops); + assert_eq!((state.guest_drops[1].0, state.guest_drops[1].2), (32, 700)); + perry_component_host_drop(state.host); + assert_eq!(state.guest_drops.len(), 2); + } +} +unsafe fn dispose(state: &mut State, marker: u64) -> i32 { + let frame = Frame { state, marker }; + let mut out = Buffer::default(); + let status = perry_component_host_dispose( + (*frame.state).host, + &frame as *const Frame as usize as u64, + &mut out, + ); + perry_component_host_buffer_drop(out); + status +} +#[test] +fn ffi_driver_disposal_destroys_live_owners_while_a_guest_call_is_suspended() { + unsafe { + let (source, text) = fixture_text(false); + let source = source.replace("world plugin {", "interface observe { dropped: func(value: u32); } interface counters { resource counter { constructor(value: u32); read: func() -> u32; } consume: func(value: own); consume-two: func(first: own, second: own); } world plugin { import observe; export counters;"); + let resources = crate::resource_tests::COMPONENT; + let start = resources.find(" (component $implementation").unwrap(); + let end = resources.find(" (instance $exports").unwrap(); + let text = text.replacen( + "(component", + &format!( + "(component\n (import \"test:driver/observe\" (instance $observe (export \"dropped\" (func (param \"value\" u32)))))\n (alias export $observe \"dropped\" (func $dropped))\n{} (instance $exports (instantiate $implementation (with \"dropped\" (func $dropped))))\n (export \"test:driver/counters\" (instance $exports))\n", + &resources[start..end] + ), + 1, + ); + let mut state = State::default(); + new(&mut state, &source, &text); + completed( + &mut state, + json!({"op":"call","interface":"test:driver/counters","function":"[constructor]counter","args":[41]}), + ); + let running = submit(state.host, call(7)).unwrap(); + assert_eq!(poll(&mut state, 10)["completed"], json!([])); + assert!(state.requests.contains_key(&7), "the export is suspended"); + let queued = submit(state.host, call(8)).unwrap(); + assert_ne!(running, queued); + assert_eq!(dispose(&mut state, 900), OK); + assert_eq!(state.guest_drops.len(), 1, "{:?}", state.guest_drops); + assert_eq!((state.guest_drops[0].0, state.guest_drops[0].2), (41, 900)); + assert!( + !state.requests.contains_key(&8), + "a queued command never starts during disposal" + ); + assert_eq!(dispose(&mut state, 950), OK); + perry_component_host_drop(state.host); + assert_eq!(state.guest_drops.len(), 1); + } +} +#[test] fn ffi_driver_foreign_disposal_preflight_and_discarded_cleanup_observer_keep_owners_distinct() { unsafe { let mut left = State::default(); From 2dcc006447aab9451c2f4dd82608b2346c2cb474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 03:34:40 +0100 Subject: [PATCH 101/144] changelog: number the PR fragment --- ...structors.md => 13638-component-driver-dispose-destructors.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-component-driver-dispose-destructors.md => 13638-component-driver-dispose-destructors.md} (100%) diff --git a/changelog.d/0-component-driver-dispose-destructors.md b/changelog.d/13638-component-driver-dispose-destructors.md similarity index 100% rename from changelog.d/0-component-driver-dispose-destructors.md rename to changelog.d/13638-component-driver-dispose-destructors.md From 121c6adc3c8b1f74ebc286fa8529a1be59f74de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 04:34:00 +0000 Subject: [PATCH 102/144] component-host: release host-type handles without the synchronous drop API host_resources::drop_owned and dispose removed a host-defined handle with ResourceAny::resource_drop. The persistent driver configures its Store for async, where wasmtime rejects that call ("store configuration requires that *_async functions are used instead"), so an embedding that disposed an owned argument inside its callback retired the component (host-resources-run). Remove the table entry with try_into_resource_dynamic: host types have no canonical destructor, so no guest code runs, and the host payload is then destroyed in the same order as before, valid in sync and async Stores alike. --- .../src/ffi/driver/tests.rs | 16 +++++++ .../src/ffi/driver/tests/disposal_tests.rs | 43 +++++++++++++++++++ .../src/host_resources.rs | 16 +++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/perry-component-host/src/ffi/driver/tests.rs b/crates/perry-component-host/src/ffi/driver/tests.rs index af9b01d342d..52058668257 100644 --- a/crates/perry-component-host/src/ffi/driver/tests.rs +++ b/crates/perry-component-host/src/ffi/driver/tests.rs @@ -22,6 +22,8 @@ struct State { pings: usize, watched_transfer: Option, early_commit: bool, + dispose_consumed: bool, + drain: Vec, } struct Frame { state: *mut State, @@ -144,6 +146,14 @@ unsafe extern "C" fn callback( output(out, b"null".to_vec()); return OK; } + if bytes(function, function_len).unwrap() == b"consume" && state.dispose_consumed { + // The embedding disposes the owned argument inside its callback. + let args: Value = serde_json::from_slice(bytes(args, args_len).unwrap()).unwrap(); + let value = &args[0]; + state.drain.push(json!({"token":value["$hostResource"],"type":value["type"],"generation":value["generation"]})); + output(out, b"null".to_vec()); + return OK; + } if bytes(function, function_len).unwrap() == b"consume" { output(out, b"primary synchronous import error".to_vec()); return ERROR; @@ -472,6 +482,12 @@ unsafe extern "C" fn resource_callback( ); return ERROR; } + if event["kind"] == "drain" && !state.drain.is_empty() { + let pending = std::mem::take(&mut state.drain); + state.resources.push((event, origin, frame.marker)); + output(out, serde_json::to_vec(&pending).unwrap()); + return OK; + } let reject = (event["kind"] == "drop" && state.reject_drop) || (event["kind"] == "lower" && state.reject_lower); state.resources.push((event, origin, frame.marker)); diff --git a/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs b/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs index 966f9d76eab..238c5ea4ac8 100644 --- a/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs +++ b/crates/perry-component-host/src/ffi/driver/tests/disposal_tests.rs @@ -154,6 +154,49 @@ fn ffi_driver_disposal_destroys_live_owners_while_a_guest_call_is_suspended() { } } #[test] +fn ffi_driver_releases_host_payloads_disposed_in_a_callback_and_held_at_disposal() { + unsafe { + let mut state = State::default(); + new( + &mut state, + crate::host_resource_tests::WORLD, + crate::host_resource_tests::COMPONENT, + ); + let world = perry_wit::parse(crate::host_resource_tests::WORLD, None).unwrap(); + let key = &world.imports[0].resources["token"]; + let owned = |name: &str| json!({"$hostResource":name,"type":key,"generation":1,"owned":true,"new":true}); + let drops = |state: &State| -> Vec { + state + .resources + .iter() + .filter(|(e, _, _)| e["kind"] == "drop") + .map(|(e, _, _)| e["resource"]["token"].clone()) + .collect() + }; + state.dispose_consumed = true; + let id = submit( + state.host, + json!({"op":"call","interface":"","function":"consume-imported","args":[owned("passed")]}), + ) + .unwrap(); + let done = poll(&mut state, 10); + assert_eq!( + done["completed"], + json!([{"id":id,"ok":true,"returned":false}]), + "{done}" + ); + assert_eq!(done["retired"], false); + assert_eq!(drops(&state), vec![json!("passed")]); + completed( + &mut state, + json!({"op":"call","interface":"","function":"keep","args":[owned("held")]}), + ); + assert_eq!(dispose(&mut state, 900), OK); + assert_eq!(drops(&state), vec![json!("passed"), json!("held")]); + perry_component_host_drop(state.host); + } +} +#[test] fn ffi_driver_foreign_disposal_preflight_and_discarded_cleanup_observer_keep_owners_distinct() { unsafe { let mut left = State::default(); diff --git a/crates/perry-component-host/src/host_resources.rs b/crates/perry-component-host/src/host_resources.rs index 79b1e6aee28..237c4600c6f 100644 --- a/crates/perry-component-host/src/host_resources.rs +++ b/crates/perry-component-host/src/host_resources.rs @@ -143,11 +143,21 @@ pub(crate) fn drop_owned( .handle .context("host resource is owned by the guest")?; let (rep, ty) = (entry.rep, entry.ty); - handle.resource_drop(store.as_context_mut())?; + release(handle, store.as_context_mut())?; *released = true; destroy(store, rep, ty) } +/// Remove a host-defined handle's table entry. Its type has no canonical +/// destructor, so no guest code runs; unlike resource_drop this is valid in an +/// async-configured Store such as the persistent driver's. +fn release(handle: ResourceAny, store: StoreContextMut<'_, StoreData>) -> Result<()> { + handle + .try_into_resource_dynamic(store) + .map(drop) + .map_err(anyhow::Error::from) +} + pub(crate) fn drain(mut store: StoreContextMut<'_, StoreData>) -> Result<()> { let hook = store.data().host_resources.hook.clone(); let pending = hook(ResourceEvent::Drain)?; @@ -183,8 +193,8 @@ pub(crate) fn dispose(mut store: StoreContextMut<'_, StoreData>) -> Result<()> { .collect(); let mut first = None; for handle in handles { - if let Err(error) = handle.resource_drop(store.as_context_mut()) { - first.get_or_insert(anyhow::Error::from(error)); + if let Err(error) = release(handle, store.as_context_mut()) { + first.get_or_insert(error); } } let state = &mut store.data_mut().host_resources; From a7a12f4eec6de462ddd9092bfc3d3452177c2136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 04:23:38 +0000 Subject: [PATCH 103/144] wpfacts: a call through a receiver field runs where the receiver is held (DECISIONS 231, 236) DECISIONS 236: a named-key method call through the keyed reach of a receiver marker (`this.f.m(args)`) is published like DECISIONS 144's marker call, with its path; every unit holding an object the receiver may be runs it on what that object holds under `f`, arguments mapped. DECISIONS 231(a): a constructor gets no receiver token in `this`; 231(b) narrowed, 231(c) on the receiver token only. A constructor's field initializers run on the receiver marker too, so another unit's `super()` sees their stores through the contract (fixture (a): per unit was missing the initializer's number). --- crates/perry-wpfacts/src/gen/export.rs | 2 +- crates/perry-wpfacts/src/gen/import_values.rs | 28 +- crates/perry-wpfacts/src/lib/contracts.rs | 4 +- crates/perry-wpfacts/src/model.rs | 25 +- crates/perry-wpfacts/src/model/operations.rs | 47 +++- crates/perry-wpfacts/src/solve.rs | 172 +++++++++++-- .../src/solve/call_and_construct.rs | 54 +++- crates/perry-wpfacts/src/solve/lookup.rs | 3 + crates/perry-wpfacts/src/solve/shared.rs | 19 ++ crates/perry-wpfacts/src/solve/store.rs | 21 +- crates/perry-wpfacts/src/summary.rs | 21 +- crates/perry-wpfacts/src/tests/unit_mode.rs | 2 + .../src/tests/unit_mode/contracts.rs | 9 +- .../tests/unit_mode/ctor_receiver_calls.rs | 242 ++++++++++++++++++ crates/perry-wpfacts/src/units.rs | 9 +- 15 files changed, 600 insertions(+), 58 deletions(-) create mode 100644 crates/perry-wpfacts/src/tests/unit_mode/ctor_receiver_calls.rs diff --git a/crates/perry-wpfacts/src/gen/export.rs b/crates/perry-wpfacts/src/gen/export.rs index 95a55e583a3..779b568de29 100644 --- a/crates/perry-wpfacts/src/gen/export.rs +++ b/crates/perry-wpfacts/src/gen/export.rs @@ -873,7 +873,7 @@ impl<'a> Gen<'a> { // the marker escapes (the caller's function is then unknown code), // they escape as before. let calls = self.m.marker_calls.clone(); - for (f, i, _, call) in calls { + for (f, i, _, _, call) in calls { let top = self .m .marker_of diff --git a/crates/perry-wpfacts/src/gen/import_values.rs b/crates/perry-wpfacts/src/gen/import_values.rs index 9955ec50fcd..0b97ba76002 100644 --- a/crates/perry-wpfacts/src/gen/import_values.rs +++ b/crates/perry-wpfacts/src/gen/import_values.rs @@ -990,12 +990,30 @@ impl<'a> Gen<'a> { // unknown: it leaves. for pc in &c.param_calls { let i = pc.param as usize; - let Some(&p) = params.get(i) else { - continue; + // DECISIONS 236: a call through the receiver's field `f` runs on + // what this unit's receivers hold under it (their chain, their + // stores; ⊤ where one escaped). + let p = match (&pc.path, pc.param) { + (Some(f), u32::MAX) => { + if c.lexical_this || c.this_use & MARK_ESCAPE != 0 { + continue; + } + let key = Key::Named(self.m.atom(f)); + let l = self.load(this, key); + self.m.nodes[l as usize].frame = run; + l + } + (Some(_), _) => continue, + (None, _) => { + let Some(&p) = params.get(i) else { + continue; + }; + if c.params[i] & MARK_ESCAPE != 0 { + continue; + } + p + } }; - if c.params[i] & MARK_ESCAPE != 0 { - continue; - } let mut args = Vec::with_capacity(pc.args.len()); for (j, d) in pc.args.iter().enumerate() { let a = self diff --git a/crates/perry-wpfacts/src/lib/contracts.rs b/crates/perry-wpfacts/src/lib/contracts.rs index d2631db8009..f81da18e6c7 100644 --- a/crates/perry-wpfacts/src/lib/contracts.rs +++ b/crates/perry-wpfacts/src/lib/contracts.rs @@ -506,7 +506,7 @@ pub(super) fn fn_contract_of( // DECISIONS 140: the calls the body makes through a parameter marker, // each argument described as the result is. let mut param_calls: Vec = Vec::new(); - for &(f2, i, key, call) in &g.m.marker_calls { + for &(f2, i, path, key, call) in &g.m.marker_calls { if f2 != f { continue; } @@ -519,10 +519,12 @@ pub(super) fn fn_contract_of( }) .collect(); let key = key.map(|a| g.m.atoms[a as usize].clone()); + let path = path.map(|a| g.m.atoms[a as usize].clone()); units::join_param_calls( &mut param_calls, &[units::ParamCall { param: i, + path, key, args, }], diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index 6d03b9d1435..dfdc8c512c4 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -1622,6 +1622,24 @@ pub struct Model { /// computed store, a hard operation, an escape). pub virt_revoked: crate::pts::FxHashSet<(TmplId, Atom)>, pub virt_open: crate::pts::FxHashSet, + /// DECISIONS 231(c): keys the code of this unit stores, with known + /// values, into an instance or receiver of each published class or a + /// subclass (`virt_store`), and the node holding those values: the own + /// field a receiver of the class reads. + pub virt_stored: FxHashMap<(TmplId, Atom), NodeId>, + /// Published classes an instance or receiver of which escaped: a store + /// into ⊤ under a key (any unit's, `top_stored`) may be a store into it. + pub virt_escaped: crate::pts::FxHashSet, + /// The keys stored into ⊤ (this unit's stores and the replayed ones); + /// `None`: under a computed key. + pub top_stored: crate::pts::FxHashSet>, + /// DECISIONS 231(b): argument markers that stood for receiver markers + /// only and now stand for another value too: every key may be an own + /// key of what they denote, as for an opened class (`virt_own`). + pub virt_tok_open: crate::pts::FxHashSet, + /// The argument markers (no reach) each marker is a member of, for the + /// receiver classes an argument marker stands for (`virt_member`). + pub virt_member_of: FxHashMap>, /// Census: keys a subclass of another unit defines, per published /// class (`override of `; `*`: any key). pub override_keys: FxHashMap>, @@ -1832,8 +1850,11 @@ pub struct Model { /// The calls through a contract function's parameter marker /// (function, parameter, method key, call): `p(..)` (no key, DECISIONS /// 140) or `p.k(..)` (DECISIONS 144). Their arguments go to the - /// caller's unit, described as the result is. - pub marker_calls: Vec<(FnIdx, u32, Option, CallId)>, + /// caller's unit, described as the result is. DECISIONS 236: with a + /// path (parameter `u32::MAX`, path `Some(f)`), `this.f.k(..)` through + /// the receiver marker's keyed reach: each unit holding a receiver runs + /// it on what its object holds under `f`. + pub marker_calls: Vec<(FnIdx, u32, Option, Option, CallId)>, /// A contract function's parameter markers: one object token per /// parameter standing for everything the caller passes (anything read /// from it is the marker again), and what the callee did with it diff --git a/crates/perry-wpfacts/src/model/operations.rs b/crates/perry-wpfacts/src/model/operations.rs index 04c56ab513b..2fbce5723e2 100644 --- a/crates/perry-wpfacts/src/model/operations.rs +++ b/crates/perry-wpfacts/src/model/operations.rs @@ -581,7 +581,7 @@ impl Model { let n = d.cause_node; d.esc_why.entry(t).or_insert((w, n)); } - self.virt_revoke(t, None); + self.virt_escape(t); self.note_inst(t, false); } if new & MARK_ESCAPE != 0 && !described { @@ -1228,6 +1228,50 @@ impl Model { } } + /// DECISIONS 231(b): an argument marker standing only for receiver + /// markers (DECISIONS 59: instances of their classes) is a receiver of + /// those classes itself: `this.m()` and `this.k` in a body that runs on + /// it resolve through the classes' chains (`virt_load`). Member `m` + /// joins `r`: a receiver adds its classes, any other value opens `r` + /// (every key may be an own key, `virt_own`), so the loads and calls + /// already made on `r` take its own fields too. The first member decides + /// whether `r` is a receiver at all; a receiver marker registered after + /// it joined adds its classes (`register_virt`). + pub(crate) fn virt_member(&mut self, m: Tok, r: Tok) { + if self.reach_marker.contains(&r) || self.keyed_parent.contains_key(&r) { + return; + } + self.virt_member_of.entry(m).or_default().push(r); + let first = self.arg_members.get(&r).is_some_and(|v| v.len() == 1); + match self.virt_protos.get(&m).cloned() { + Some(cs) if !self.virt_tok_open.contains(&m) => { + if first || self.virt_protos.contains_key(&r) { + self.register_virt(r, &cs); + } + } + _ => self.open_virt_tok(r), + } + } + + /// `r` stands for a value that is not a receiver of its classes + /// (`virt_member`): so do the argument markers above it. + pub(crate) fn open_virt_tok(&mut self, r: Tok) { + let mut st = vec![r]; + while let Some(x) = st.pop() { + if !self.virt_protos.contains_key(&x) || !self.virt_tok_open.insert(x) { + continue; + } + self.flush_shadowed(Some(x)); + for a in self.virt_tok_keys.get(&x).cloned().unwrap_or_default() { + if let Some(&n) = self.virt_nodes.get(&(x, a)) { + let f = self.field(x, a); + self.edge(f, n); + } + } + st.extend(self.virt_member_of.get(&x).cloned().unwrap_or_default()); + } + } + /// Argument marker `r` stands for marker `m` too: `m` takes every use /// of `r`, and what `m` reaches takes every use of what `r` reaches. pub(crate) fn link_marker(&mut self, m: Tok, r: Tok) { @@ -1235,6 +1279,7 @@ impl Model { return; } self.arg_members.get_mut(&r).unwrap().push(m); + self.virt_member(m, r); // A new link changes the sets settled for `r` and the markers above // it (`marker_members`), no others (DECISIONS 200). if !self.members_settled.get_mut().is_empty() { diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index bfc9ae734c6..a3663807f48 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1052,11 +1052,30 @@ impl Model { self.flush_shadowed(Some(t)); } if let Some(&pn) = self.proto_node.get(&t) { - for p in fresh { + for &p in &fresh { let pt = self.tok(TokKind::Proto(p)); self.add_tok(pn, pt); } } + // DECISIONS 231(b): an argument marker `t` joined before it was a + // receiver stands for its classes too, if it stands for receivers + // only (`virt_member`). + if fresh.is_empty() || self.virt_tok_open.contains(&t) { + return; + } + for r in self.virt_member_of.get(&t).cloned().unwrap_or_default() { + if self.virt_protos.contains_key(&r) { + self.register_virt(r, classes); + } else if self.arg_members.get(&r).is_some_and(|ms| { + ms.iter().all(|m| self.virt_protos.contains_key(m) && !self.virt_tok_open.contains(m)) + }) { + let mut cs: Vec = Vec::new(); + for m in self.arg_members[&r].clone() { + cs.extend(self.virt_protos[&m].iter().copied()); + } + self.register_virt(r, &cs); + } + } } /// The load node of key `a` on receiver token / marker `t` (`None`: `t` @@ -1081,8 +1100,31 @@ impl Model { if shadow { self.note_shadowed(t, a, n, false); } - let own = shadow || self.virt_own(&classes, a); - if own { + // DECISIONS 231(c): the receiver token's own field is what this + // unit's code stores under `a` into instances and receivers of the + // classes and their subclasses (other units' store effects and ⊤ + // stores into escaped ones arrive as such stores); its field (⊤) + // only where the key may hold a value no such store makes + // (`virt_own`, a subclass of another unit defining it, a shadowed + // key no store of this unit makes). A marker's own field stays its + // keyed reach: the caller's own value, which receiver flows carry + // relative to each caller (DECISIONS 179); the stored values beside + // it would be this unit's tokens on every caller's object. + let marker = self.is_marker(t); + let mut stored = false; + for &p in &classes { + if let Some(&s) = self.virt_stored.get(&(p, a)) { + if !marker { + self.edge(s, n); + } + stored = true; + } + } + if self.virt_own(t, &classes, a) + || !self.virt_overriders(t, a).is_empty() + || (shadow && !stored) + || (stored && marker) + { let f = self.field(t, a); self.edge(f, n); } @@ -1093,8 +1135,8 @@ impl Model { /// stands for in this unit's view: the class code stores it, uses a /// hard operation on one or lets one escape, or an accessor on the /// chain may answer it. - fn virt_own(&self, classes: &[TmplId], a: Atom) -> bool { - classes.iter().any(|&p| { + fn virt_own(&self, t: Tok, classes: &[TmplId], a: Atom) -> bool { + self.virt_tok_open.contains(&t) || classes.iter().any(|&p| { self.virt_open.contains(&p) || self.virt_revoked.contains(&(p, a)) || self.chain_accessor_key(p, a) @@ -1126,7 +1168,7 @@ impl Model { /// (`override_node`); else nothing (`virt_load` holds the own field). pub(crate) fn virt_call(&mut self, t: Tok, a: Atom) -> Vec { let classes = self.virt_protos.get(&t).cloned().unwrap_or_default(); - if self.virt_own(&classes, a) { + if self.virt_own(t, &classes, a) { return Vec::new(); } let ps = self.virt_overriders(t, a); @@ -1209,36 +1251,112 @@ impl Model { if self.virt_by_class.is_empty() { return; } - let classes: Vec = match self.virt_protos.get(&t) { + for p in self.virt_classes(t) { + self.virt_open_class(p, key); + } + } + + /// The published classes whose receivers may denote what `t` is or + /// stands for: its classes (an instance's template, a receiver's + /// classes) and their ancestors (a receiver of a class stands for its + /// subclasses' instances too). + fn virt_classes(&self, t: Tok) -> Vec { + let mut work: Vec = match self.virt_protos.get(&t) { Some(ps) => ps.clone(), - None => { - let Some(tm) = self.tmpl_of(t) else { return }; - let mut out = Vec::new(); - let mut work = vec![tm]; - let mut seen: Vec = Vec::new(); - while let Some(c) = work.pop() { - if seen.contains(&c) { - continue; - } - seen.push(c); - if self.virt_by_class.contains_key(&c) { - out.push(c); + None => match self.tmpl_of(t) { + Some(tm) => vec![tm], + None => return Vec::new(), + }, + }; + let mut out = Vec::new(); + let mut seen: Vec = Vec::new(); + while let Some(c) = work.pop() { + if seen.contains(&c) { + continue; + } + seen.push(c); + if self.virt_by_class.contains_key(&c) { + out.push(c); + } + let tp = &self.tmpls[c as usize]; + work.extend(tp.parent); + work.extend(tp.parent_links.iter().copied()); + } + out + } + + /// DECISIONS 231(c): this unit's code stores `src` under `a` into `t`: + /// the receivers of the classes `t` may be an instance of read it as + /// their own field (`virt_load`). + pub(crate) fn virt_store(&mut self, t: Tok, a: Atom, src: NodeId) { + if self.virt_by_class.is_empty() { + return; + } + for p in self.virt_classes(t) { + let s = match self.virt_stored.get(&(p, a)) { + Some(&s) => s, + None => { + let s = self.node_l("virt-stored"); + self.virt_stored.insert((p, a), s); + for vt in self.virt_by_class.get(&p).cloned().unwrap_or_default() { + if let Some(&n) = self.virt_nodes.get(&(vt, a)) { + if self.is_marker(vt) { + let f = self.field(vt, a); + self.edge(f, n); + } else { + self.edge(s, n); + } + } } - let tp = &self.tmpls[c as usize]; - work.extend(tp.parent); - work.extend(tp.parent_links.iter().copied()); + s } - out + }; + self.edge(src, s); + } + } + + /// An instance or receiver `t` escaped: unknown code holds it, which per + /// unit is other units' code too, and its writes through ⊤ reach it + /// (`top_store`): each key stored into ⊤ may be any value on it. + pub(crate) fn virt_escape(&mut self, t: Tok) { + if self.virt_by_class.is_empty() { + return; + } + for p in self.virt_classes(t) { + if !self.virt_escaped.insert(p) { + continue; } - }; - for p in classes { + let ks: Vec> = self.top_stored.iter().copied().collect(); + for k in ks { + self.virt_open_class(p, k); + } + } + } + + /// A write through ⊤ under key `k` (`None`: computed, or a write of any + /// key: a hard operation, an accessor, unmodeled code): on each escaped + /// instance the key may hold any value. + pub(crate) fn top_store(&mut self, k: Option) { + if !self.top_stored.insert(k) { + return; + } + let ps: Vec = self.virt_escaped.iter().copied().collect(); + for p in ps { + self.virt_open_class(p, k); + } + } + + /// Key `key` (`None`: every key) of class `p`'s instances may hold a + /// value no store of this unit makes: its receivers read their own field. + fn virt_open_class(&mut self, p: TmplId, key: Option) { + { if self.virt_open.contains(&p) { - continue; + return; } let opened: Option = match key { Some(a) => { if !self.virt_revoked.insert((p, a)) { - continue; + return; } Some(a) } diff --git a/crates/perry-wpfacts/src/solve/call_and_construct.rs b/crates/perry-wpfacts/src/solve/call_and_construct.rs index 985c47f22f5..49819f2d0dc 100644 --- a/crates/perry-wpfacts/src/solve/call_and_construct.rs +++ b/crates/perry-wpfacts/src/solve/call_and_construct.rs @@ -24,6 +24,7 @@ impl Model { if let Some(own) = plain.then(|| self.own_param_members(t)).flatten() { if self.bind_done.insert((call, t, u32::MAX - 3)) { self.mark_use(t, crate::model::MARK_READ); + let own = own.into_iter().map(|(f, i)| (f, i, None)).collect(); self.marker_call(own, None, call); } return; @@ -130,13 +131,50 @@ impl Model { .filter(|v| !v.is_empty()) } + /// DECISIONS 236: the members a method call on `t` is recorded for: + /// `own_param_members`, and a receiver marker's keyed reach (`this.f`, + /// a path marker under a named key of an unescaped receiver marker) as + /// (function, `u32::MAX`, `Some(f)`): each unit holding a receiver runs + /// the call on what its object holds under `f` (its own chain, its own + /// stores, ⊤ where its object escaped or `f` was written through ⊤). + /// `None`: some member is neither, or none is left. + pub(crate) fn mcall_members(&self, t: Tok) -> Option)>> { + self.marker_members(t) + .into_iter() + .map(|m| match self.param_marker.get(&m) { + _ if self.beside_values(m) && !self.unrun_param(m) => Some(None), + Some(&(f, i)) + if i != u32::MAX + && self.marker_of.get(&(f, i)) == Some(&m) + && !self.marker_is_top(m) => + { + Some(Some((f, i, None))) + } + Some(&(f, u32::MAX)) => match self.path_parent.get(&m) { + Some(&(rm, Some(k))) + if self.marker_of.get(&(f, u32::MAX)) == Some(&rm) + && !self.marker_is_top(rm) + && !self.marker_is_top(m) => + { + Some(Some((f, u32::MAX, Some(k)))) + } + _ => None, + }, + _ => None, + }) + .collect::>>() + .map(|v| v.into_iter().flatten().collect::>()) + .filter(|v| !v.is_empty()) + } + /// Records a call through the parameters `own` (`key`: a method of - /// theirs) for the caller's unit to run (DECISIONS 140/144); its - /// result is that unknown code's. - fn marker_call(&mut self, own: Vec<(FnIdx, u32)>, key: Option, call: CallId) { - for (f, i) in own { - if !self.marker_calls.contains(&(f, i, key, call)) { - self.marker_calls.push((f, i, key, call)); + /// theirs; a path: of the receiver's field, DECISIONS 236) for the + /// caller's unit to run (DECISIONS 140/144); its result is that + /// unknown code's. + fn marker_call(&mut self, own: Vec<(FnIdx, u32, Option)>, key: Option, call: CallId) { + for (f, i, path) in own { + if !self.marker_calls.contains(&(f, i, path, key, call)) { + self.marker_calls.push((f, i, path, key, call)); } } let ret = self.calls[call as usize].ret; @@ -752,7 +790,7 @@ impl Model { // arguments go to the caller's unit described as the result is. if let (Key::Named(a), None, false) = (key, virt, self.protocol_calls.contains(&call)) { let plain = self.calls[call as usize].spread.is_empty(); - if let Some(own) = plain.then(|| self.own_param_members(t)).flatten() { + if let Some(own) = plain.then(|| self.mcall_members(t)).flatten() { if self.bind_done.insert((call, t, u32::MAX - 4)) { self.mark_use(t, crate::model::MARK_READ); let r = self.keyed_reach_of(t, a); @@ -1657,7 +1695,7 @@ impl Model { Some(o) => format!("{o} / escape: {}", m.reasons[reason as usize]), None => format!("escape: {}", m.reasons[reason as usize]), }); - self.virt_revoke(t, None); + self.virt_escape(t); self.note_inst(t, false); if let Some(is) = self.contract_recv.get(&t).cloned() { self.recv_log diff --git a/crates/perry-wpfacts/src/solve/lookup.rs b/crates/perry-wpfacts/src/solve/lookup.rs index d5e7b2d9b9a..60bc12e5159 100644 --- a/crates/perry-wpfacts/src/solve/lookup.rs +++ b/crates/perry-wpfacts/src/solve/lookup.rs @@ -70,6 +70,9 @@ impl Model { } let top = self.top_reshaped_any || self.top_reshaped_keys.contains(&a); if let Some(classes) = self.virt_protos.get(&t) { + if self.virt_tok_open.contains(&t) { + return false; + } // Instances of these classes made in another unit may have // escaped there, unseen here: a delete or defineProperty // through ⊤ revokes the key on them. diff --git a/crates/perry-wpfacts/src/solve/shared.rs b/crates/perry-wpfacts/src/solve/shared.rs index 55af8c7026a..f08cbce5b9a 100644 --- a/crates/perry-wpfacts/src/solve/shared.rs +++ b/crates/perry-wpfacts/src/solve/shared.rs @@ -39,11 +39,29 @@ impl Model { } pub(crate) fn note_shared(&mut self, w: SharedWrite) { + self.top_write(&w); if !self.shared_replay { self.shared_out.insert(w); } } + /// DECISIONS 231(c): a write through ⊤ other than a store (which + /// `store_into` notes) may give a key of an escaped instance any value + /// (`top_store`): an accessor under its key, a hard operation or + /// unmodeled code under every key. + fn top_write(&mut self, w: &SharedWrite) { + match w { + SharedWrite::Accessor(Shared::Top, k) if k != crate::STAR_KEY => { + let a = self.atom(k); + self.top_store(Some(a)); + } + SharedWrite::Accessor(Shared::Top, _) + | SharedWrite::Unmodeled(Shared::Top) + | SharedWrite::Hard(Shared::Top, _) => self.top_store(None), + _ => {} + } + } + /// Another unit's write of a shared object, as if this unit's code did /// it with a value it does not know: a store of ⊤, an accessor of a /// function this unit cannot see (on `Object.prototype` E23, as for any @@ -51,6 +69,7 @@ impl Model { /// unit's: only the write's target and key are this unit's to see. pub(crate) fn replay_shared(&mut self, w: &SharedWrite) { self.shared_replay = true; + self.top_write(w); match w { SharedWrite::Store(s, k) => { let t = self.shared_tok(s); diff --git a/crates/perry-wpfacts/src/solve/store.rs b/crates/perry-wpfacts/src/solve/store.rs index 26e31c6af5b..8c8ad84c2c5 100644 --- a/crates/perry-wpfacts/src/solve/store.rs +++ b/crates/perry-wpfacts/src/solve/store.rs @@ -280,14 +280,19 @@ impl Model { // is a store under a computed key. let elem = self.is_elem_key(key); let key = if elem { Key::Any } else { key }; - { - self.virt_revoke( - t, - match key { - Key::Named(a) => Some(a), - Key::Any => None, - }, - ); + // DECISIONS 231(c): a named store is the value of that key on the + // receivers of the classes `t` is or stands for (`virt_store`); a + // computed one opens them. A store into ⊤ may be a store into an + // escaped instance (`top_store`). + match key { + Key::Named(a) => self.virt_store(t, a, src), + Key::Any => self.virt_revoke(t, None), + } + if t == TOP { + self.top_store(match key { + Key::Named(a) => Some(a), + Key::Any => None, + }); } // DECISIONS 210: a store into another unit's function (a stand-in) // under `prototype` (or a key that may be it) replaces the chain its diff --git a/crates/perry-wpfacts/src/summary.rs b/crates/perry-wpfacts/src/summary.rs index a9fcbb19928..f9e24f84765 100644 --- a/crates/perry-wpfacts/src/summary.rs +++ b/crates/perry-wpfacts/src/summary.rs @@ -1103,7 +1103,20 @@ impl Model { } let f = self.tok_fn(ft); let why = |m: &Self| format!("this of escaped function: {}", m.reasons[reason as usize]); - let dsts = self.this_nodes(ft); + let mut dsts = self.this_nodes(ft); + // A constructor's field initializers run on every object it runs on + // (before its body, as `run_ctor_chain` runs them for a `new`): + // another unit's `super()` reaches them through the constructor's + // contract, so their stores into the receiver are the contract's + // (`this_stores`, DECISIONS 179), not only the body's. + if let Some(fi) = self.funcs[f as usize] + .ctor_of + .filter(|&tm| self.tmpls[tm as usize].ctor == Some(f)) + .and_then(|tm| self.tmpls[tm as usize].field_init) + { + let fit = self.funcs[fi as usize].token; + dsts.extend(self.this_nodes(fit)); + } let insts = self.published_this.get(&f).cloned().unwrap_or_default(); // DECISIONS 116: a method of sealed classes only, published through // its contract, runs on the instances this unit gave out (and the @@ -1169,6 +1182,12 @@ impl Model { self.add_tok(d, t); } } + // DECISIONS 231(a): a constructor runs on its receiver + // marker (another unit's `super()`) and on the instances + // `new` makes (the contract's construction, DECISIONS 125), + // never on the receiver token: no unit calls a constructor + // as a method of an instance it holds. + _ if self.funcs[f as usize].ctor_of.is_some() => {} _ => { for &i in &insts { self.add_tok(d, i); diff --git a/crates/perry-wpfacts/src/tests/unit_mode.rs b/crates/perry-wpfacts/src/tests/unit_mode.rs index df4e49c2926..3458681f5d5 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode.rs @@ -613,3 +613,5 @@ mod protocol_touch; mod unit_cycle_order; #[path = "unit_mode/own_key_shadow.rs"] mod own_key_shadow; + +mod ctor_receiver_calls; diff --git a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs index a8cc97ffdb3..ef0d7c84ef0 100644 --- a/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs +++ b/crates/perry-wpfacts/src/tests/unit_mode/contracts.rs @@ -320,8 +320,13 @@ pub(super) fn a_broken_namespace_field_function_resolves_only_the_units_that_see // (`Cons::Defined`): a relay node for the read's own builtin members // and a rule on the chain node, [758, 1094, 952, 42] -> [762, 1104, // 960, 42]. DECISIONS 234: a read of an own definite key takes the - // chain's program values only (a chain node of its own) -> the pin below. - assert_eq!(pf.round_work, [763, 1103, 960, 42], "{:?}", pf.units); + // chain's program values only (a chain node of its own) -> [763, 1103, + // 960, 42]. DECISIONS 231(c): a store of `a`'s code into an instance of + // a class a receiver marker stands for goes to the class's `virt-stored` + // node for that key (`virt_store`), which the marker's reads take as its + // own field: two such nodes (two keys) in each of the three rounds that + // solve `a` -> the pin below. + assert_eq!(pf.round_work, [765, 1105, 962, 42], "{:?}", pf.units); } #[test] diff --git a/crates/perry-wpfacts/src/tests/unit_mode/ctor_receiver_calls.rs b/crates/perry-wpfacts/src/tests/unit_mode/ctor_receiver_calls.rs new file mode 100644 index 00000000000..79c8dce6458 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/unit_mode/ctor_receiver_calls.rs @@ -0,0 +1,242 @@ +//! DECISIONS 231(b)(c): a published class's receivers (the receiver token, +//! the receiver markers of its constructor and methods, and the argument +//! markers that stand only for them) read a key's own field as what this +//! unit's code stores under it into the class's instances and receivers, +//! with other units' store effects and, once an instance escaped, the +//! writes through ⊤; another unit's value (the keyed reach) only where the +//! key may hold one no such store makes. Node checks: qb9 +//! /root/RCV/r7s/crc.node.mjs. +use super::*; + +/// Per unit proves nothing the whole program does not, and every field of +/// `class` holds at least the kinds the whole program finds. +fn never_stronger(mods: &[(&str, &str)], class: &str) -> (ProgramFacts, ProgramFacts) { + let (u, w) = (units_of(mods), whole_of(mods)); + let more: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(more.is_empty(), "per unit proves more: {more:?}\n{}", u.dump_text()); + let (tu, tw) = (tmpl(&u, class), tmpl(&w, class)); + for fw in &tw.fields { + if let Some(fu) = tu.fields.iter().find(|f| f.key == fw.key) { + assert!(kinds_cover(fu, fw), "field {} {:?} vs whole {:?}\n{}", fw.key, fu.kind, fw.kind, u.dump_text()); + } + } + (u, w) +} + +const SVC: &str = r#"export class Svc { size = 3; seen = 0; onCreate(p: P) { this.seen = p.n; } } +export class P { + svc: Svc; + n = 0; + constructor(s: Svc) { BODY } + init() { this.n = this.svc.size; } + total() { return this.svc.size + this.n; } +}"#; +const SUB: &str = r#"import { P, Svc } from "./p"; +export class Q extends P { constructor(s: Svc) { super(s); } }"#; +const MAIN: &str = r#"import { Q } from "./b"; +import { P, Svc } from "./p"; +console.log(new Q(new Svc()).total(), new P(new Svc()).total());"#; + +fn svc(body: &str, on_create: &str) -> String { + SVC.replace("BODY", body).replace("this.seen = p.n;", on_create) +} + +/// The constructor's `this.init()` and `s.onCreate(this)` with `s` another +/// unit's value: the instance escapes, yet only a key some unit writes +/// through ⊤ is opened on it, so the class's own calls stay its methods. +#[test] +fn an_escaped_receiver_opens_only_the_keys_written_through_top() { + let p = svc("this.svc = s; this.init(); s.onCreate(this);", "this.seen = p.n;"); + let mods = [("p", p.as_str()), ("b", SUB), ("m", MAIN)]; + let (u, _) = never_stronger(&mods, "P"); + assert!(proven_set(&u).iter().any(|s| s.ends_with(".init")), "{}", u.dump_text()); +} + +/// Soundness: the unit that receives the instance writes its keys (`n`, +/// `init`, `svc`) through a parameter its unknown caller fills. +#[test] +fn writes_into_an_escaped_receiver_reach_the_class() { + for on_create in [ + "p.n = \"x\" as any;", + "(p as any).init = () => { p.n = \"y\" as any; };", + "p.svc = { size: \"z\" } as any;", + ] { + let p = svc("this.svc = s; s.onCreate(this); this.init();", on_create); + let mods = [("p", p.as_str()), ("b", SUB), ("m", MAIN)]; + never_stronger(&mods, "P"); + } +} + +/// The constructor stores its parameter and calls through the stored +/// field (tsc's Project: `this.projectService.onProjectCreation(this)`): +/// the units that construct an instance run the call on what they passed +/// (DECISIONS 236), so the receiver does not escape and the class's reads +/// stay proven. The callee itself stays the callers' value here: the +/// receiver marker keeps its keyed reach (DECISIONS 179). +#[test] +fn a_constructor_calls_through_the_field_it_stored() { + let p = svc("this.svc = s; this.svc.onCreate(this);", "this.seen = p.n;"); + let mods = [("p", p.as_str()), ("b", SUB), ("m", MAIN)]; + let (u, _) = never_stronger(&mods, "P"); + assert!(!u.broken.iter().any(|b| b == "p.ts:P"), "{}", u.dump_text()); + assert!(proven_set(&u).iter().any(|s| s.ends_with(".svc")), "{}", u.dump_text()); +} + +const K: &str = r#"function plain(this: any) { this.n = 1; } +export class P { + n = 0; + k: any = plain; + act() { this.n = 2; } + go() { this.k(); this.act(); return this.n + 1; } +}"#; + +/// Soundness: another unit stores a function under `k` of an instance (a +/// store effect): the class's `this.k()` runs it. +#[test] +fn another_units_store_into_an_instance_is_the_receivers_field() { + let m = r#"import { P } from "./p"; +const p = new P(); +p.k = function (this: any) { this.n = "s"; }; +console.log(p.go());"#; + never_stronger(&[("p", K), ("m", m)], "P"); +} + +/// Soundness: a subclass constructor of another unit reassigns `k`. +#[test] +fn a_subclass_constructor_reassigning_a_key_is_seen() { + let b = r#"import { P } from "./p"; +export class Q extends P { constructor() { super(); this.k = function (this: any) { this.n = "q"; }; } }"#; + let m = r#"import { Q } from "./b"; +console.log(new Q().go());"#; + never_stronger(&[("p", K), ("b", b), ("m", m)], "P"); +} + +/// Soundness (DECISIONS 231(d)): a subclass of another unit overrides +/// `act`: the class's `this.act()` may run it. +#[test] +fn a_subclass_override_of_a_method_is_seen() { + let b = r#"import { P } from "./p"; +export class Q extends P { act() { this.n = "o" as any; } }"#; + let m = r#"import { Q } from "./b"; +import { P } from "./p"; +console.log(new Q().go(), new P().go());"#; + never_stronger(&[("p", K), ("b", b), ("m", m)], "P"); +} + +/// Soundness (DECISIONS 231(b)): a helper's parameter first holds only the +/// receiver, then another unit's object joins it: the helper's `x.k()` and +/// `x.n` take that object's values. +#[test] +fn a_late_non_receiver_join_opens_the_argument() { + let p = r#"function help(x: any) { x.k(); return x.n + 1; } +function plain(this: any) { this.n = 1; } +export class P { + n = 0; + k: any = plain; + go() { return help(this); } +} +export function use(o: any) { return help(o); }"#; + let m = r#"import { P, use } from "./p"; +console.log(new P().go(), use({ n: 0, k() { this.n = "j"; } }));"#; + let (u, w) = (units_of(&[("p", p), ("m", m)]), whole_of(&[("p", p), ("m", m)])); + let more: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(more.is_empty(), "per unit proves more: {more:?}\n{}", u.dump_text()); +} + + +/// DECISIONS 236: per unit in a thread with a deadline (an oscillating +/// census never ends), with the round count, never stronger than whole for +/// every class in `classes`, each class's fields covering the whole +/// program's kinds. +fn bounded(mods: &[(&'static str, &'static str)], classes: &[&str]) -> ProgramFacts { + let owned: Vec<(&'static str, &'static str)> = mods.to_vec(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let _ = tx.send(units_of(&owned)); + }); + let u = rx + .recv_timeout(std::time::Duration::from_secs(120)) + .expect("the per-unit rounds converge"); + let w = whole_of(mods); + // The receivers and arguments the units publish only grow (fixture + // (a): 9 rounds); an oscillation never ends (RCV7: 1,250+). + assert!(u.rounds <= 12, "rounds {}\n{}", u.rounds, u.dump_text()); + let more: Vec<_> = proven_set(&u).difference(&proven_set(&w)).cloned().collect(); + assert!(more.is_empty(), "per unit proves more: {more:?}\n{}", u.dump_text()); + for class in classes { + let (tu, tw) = (tmpl(&u, class), tmpl(&w, class)); + for fw in &tw.fields { + if let Some(fu) = tu.fields.iter().find(|f| f.key == fw.key) { + assert!(kinds_cover(fu, fw), "{class}.{} {:?} vs whole {:?}\n{}", fw.key, fu.kind, fw.kind, u.dump_text()); + } + } + } + u +} + +const RA: &str = r#"export class P { + svc: any; + n: any = 0; + constructor(s: any) { this.svc = s; this.svc.onCreate(this); } + total() { return this.n + 1; } +}"#; + +/// DECISIONS 236 (a): the constructor's `this.svc.onCreate(this)` runs in +/// the unit that holds the instance (b's `super(r)`), on what it stored +/// under `svc` (b's `Reg`), with `this` as b's instance: Reg's store into +/// its argument (`p.n = "r"`) is seen, and the receiver does not escape. +#[test] +fn a_call_through_a_receiver_field_runs_where_the_receiver_is_held() { + let b = r#"import { P } from "./p"; +export class Reg { last: any = null; onCreate(p: any) { this.last = p; p.n = "r"; } } +export class Q extends P { constructor(r: Reg) { super(r); } } +export function lastN(r: Reg) { return r.last.n; }"#; + let m = r#"import { Q, Reg, lastN } from "./b"; +const r = new Reg(); +const q = new Q(r); +console.log(q.total(), lastN(r));"#; + let u = bounded(&[("p", RA), ("b", b), ("m", m)], &["P", "Q", "Reg"]); + assert!(!u.broken.iter().any(|b| b == "p.ts:P"), "{}", u.dump_text()); +} + +/// DECISIONS 236 (b): the receiver escaped before the call (unknown code +/// may have replaced `svc`): the call is unknown code's, its arguments +/// escape as before. +#[test] +fn a_call_through_an_escaped_receivers_field_escapes_its_arguments() { + let p = r#"export class Box { v: any = 1; } +export const box = new Box(); +export class P { + svc: any; + constructor(s: any) { this.svc = s; (globalThis as any).hook(this); this.svc.onCreate(this, box); } +} +export function rd() { return box.v + 1; }"#; + let m = r#"import { P, rd } from "./p"; +(globalThis as any).hook = (p: any) => { p.svc = { onCreate(_: any, o: any) { o.v = "x"; } }; }; +class S { onCreate(_: any, _o: any) {} } +new P(new S()); +console.log(rd());"#; + bounded(&[("p", p), ("m", m)], &["Box"]); +} + +/// DECISIONS 236 (c): a subclass constructor of another unit reassigns +/// `svc`: the class's calls through `this.svc` run both callees (Reg1's +/// numeric store and Reg2's string store into the receiver). +#[test] +fn a_subclass_reassigning_the_receiver_field_runs_both_callees() { + let p = r#"export class Reg1 { onCreate(p: any) { p.n = 1; } } +export class P { + svc: any; + n: any = 0; + constructor(s: any) { this.svc = s; this.svc.onCreate(this); } + ping() { this.svc.onCreate(this); return this.n; } +}"#; + let b = r#"import { P, Reg1 } from "./p"; +class Reg2 { onCreate(p: any) { p.n = "two"; } } +export class Q extends P { constructor() { super(new Reg1()); this.svc = new Reg2(); } }"#; + let m = r#"import { Q } from "./b"; +const q = new Q(); +console.log(q.n, q.ping(), q.n);"#; + let u = bounded(&[("p", p), ("b", b), ("m", m)], &["P", "Q"]); + assert!(!u.broken.iter().any(|b| b == "p.ts:P"), "{}", u.dump_text()); +} diff --git a/crates/perry-wpfacts/src/units.rs b/crates/perry-wpfacts/src/units.rs index 8d0951ca530..bafcb40fe24 100644 --- a/crates/perry-wpfacts/src/units.rs +++ b/crates/perry-wpfacts/src/units.rs @@ -189,9 +189,12 @@ impl Described { /// the function it passed, runs that call; one entry per (parameter, key, /// number of arguments), its arguments joined over the calls. With `key` /// the body calls that method of the argument (`p.k(a, b)`, DECISIONS 144). +/// DECISIONS 236: `param` `u32::MAX` with `path` `Some(f)`: the method of +/// what the receiver holds under `f` (`this.f.k(a, b)`). #[derive(Clone, Debug, PartialEq, Hash)] pub(crate) struct ParamCall { pub param: u32, + pub path: Option, pub key: Option, pub args: Vec, } @@ -209,10 +212,12 @@ pub(crate) fn join_param_calls(a: &mut Vec, b: &[ParamCall]) { join_keyed( a, b, - |x| (x.param, x.key.clone(), x.args.len()), + |x| (x.param, x.path.clone(), x.key.clone(), x.args.len()), |x, y| x.join(y), ); - a.sort_by(|x, y| (x.param, &x.key, x.args.len()).cmp(&(y.param, &y.key, y.args.len()))); + a.sort_by(|x, y| { + (x.param, &x.path, &x.key, x.args.len()).cmp(&(y.param, &y.path, &y.key, y.args.len())) + }); } /// DECISIONS 152: the body hands parameter `param`'s argument to argument From 058220ea74da8b5e74bfee07a5b17248dbe619b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 04:45:00 +0000 Subject: [PATCH 104/144] changelog: wpfacts receiver field calls --- changelog.d/0-wpfacts-receiver-field-calls.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/0-wpfacts-receiver-field-calls.md diff --git a/changelog.d/0-wpfacts-receiver-field-calls.md b/changelog.d/0-wpfacts-receiver-field-calls.md new file mode 100644 index 00000000000..fe96563fa62 --- /dev/null +++ b/changelog.d/0-wpfacts-receiver-field-calls.md @@ -0,0 +1 @@ +Per-unit wpfacts run a method call made through a field of a published class's receiver (`this.svc.onCreate(this)`) in every unit that holds such an instance, on what that instance holds in the field, so the receiver no longer escapes just by being handed to that call. A constructor's field initializers are part of its contract, so a subclass in another unit now sees their stores through `super()`. A constructor no longer runs on the class's receiver token. From f6b94077ae7850197440373c15505c7660d21053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 05:47:02 +0100 Subject: [PATCH 105/144] changelog: number the PR fragment --- ...eiver-field-calls.md => 13639-wpfacts-receiver-field-calls.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-receiver-field-calls.md => 13639-wpfacts-receiver-field-calls.md} (100%) diff --git a/changelog.d/0-wpfacts-receiver-field-calls.md b/changelog.d/13639-wpfacts-receiver-field-calls.md similarity index 100% rename from changelog.d/0-wpfacts-receiver-field-calls.md rename to changelog.d/13639-wpfacts-receiver-field-calls.md From b3f1990f94ce8858c7b1fb869ba59e3ad5032833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 05:30:30 +0000 Subject: [PATCH 106/144] wpfacts: generator next/return/throw return an iterator result, not the raw step value --- .../0-wpfacts-generator-result-object.md | 6 ++ crates/perry-wpfacts/src/builtins.rs | 4 + .../perry-wpfacts/src/builtins/collections.rs | 37 +++++++- crates/perry-wpfacts/src/gen.rs | 4 + crates/perry-wpfacts/src/gen/class_values.rs | 2 + crates/perry-wpfacts/src/gen/for_in.rs | 2 + crates/perry-wpfacts/src/gen/import_values.rs | 2 + crates/perry-wpfacts/src/gen_call.rs | 26 ++++++ crates/perry-wpfacts/src/gen_rest.rs | 2 + crates/perry-wpfacts/src/model.rs | 5 ++ crates/perry-wpfacts/src/solve.rs | 2 + crates/perry-wpfacts/src/tests.rs | 9 ++ .../src/tests/generator_results.rs | 88 +++++++++++++++++++ 13 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 changelog.d/0-wpfacts-generator-result-object.md create mode 100644 crates/perry-wpfacts/src/tests/generator_results.rs diff --git a/changelog.d/0-wpfacts-generator-result-object.md b/changelog.d/0-wpfacts-generator-result-object.md new file mode 100644 index 00000000000..ef168fbf9ba --- /dev/null +++ b/changelog.d/0-wpfacts-generator-result-object.md @@ -0,0 +1,6 @@ +wpfacts no longer reads a sync generator's public next()/return()/throw() as +returning the step closure's raw value: the result is an iterator result +{ value, done } whose value joins every yielded value, so a receiver that a +generator yields alongside another shape is no longer proven as the first +shape alone. Output was already correct (the runtime is guarded); only the +fact was wrong. diff --git a/crates/perry-wpfacts/src/builtins.rs b/crates/perry-wpfacts/src/builtins.rs index 6c60531f4d5..622e85ae12c 100644 --- a/crates/perry-wpfacts/src/builtins.rs +++ b/crates/perry-wpfacts/src/builtins.rs @@ -247,6 +247,8 @@ pub fn resolver(m: &mut Model, dst: NodeId) -> Tok { lexical_this: true, no_summary: false, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, @@ -291,6 +293,8 @@ pub fn other_unit_accessor(m: &mut Model) -> Tok { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, diff --git a/crates/perry-wpfacts/src/builtins/collections.rs b/crates/perry-wpfacts/src/builtins/collections.rs index d222eb6207b..88cd538bfcc 100644 --- a/crates/perry-wpfacts/src/builtins/collections.rs +++ b/crates/perry-wpfacts/src/builtins/collections.rs @@ -120,10 +120,43 @@ pub(super) fn function_method(m: &mut Model, t: Tok, name: Option<&str>, call: C unknown_method(m, call, "computed method of a function value"); } } - // iteration: the function runs with this = itself. - Some("next" | "return" | "throw") | None => { + // An async generator's step: the function runs with this = itself. + Some("next" | "return" | "throw") | None + if matches!(m.toks[t as usize], TokKind::Func(f, _) if m.funcs[f as usize].gen_async) => + { m.call_tok(call, t, Some(t)); } + // A sync generator's public `next`/`return`/`throw`: the step runs + // with this = itself and hands back its raw value (step kind 0 + // yield, 1 completion), which the runtime boundary wraps in a fresh + // `{ value, done }`; a `yield*` re-yield (kind 2) hands back its + // delegate's inner result object, returned as-is. + Some("next" | "return" | "throw") | None => { + let raw = m.node_l("gen-step-raw"); + m.nodes[raw as usize].frame = m.call_run(call); + let call2 = m.solver_call( + call, + CallSite { + this: c.this.clone(), + args: c.args.clone(), + spread: c.spread.clone(), + ret: raw, + site: c.site, + }, + ); + m.call_tok(call2, t, Some(t)); + let r = site_tmpl(m, call, "IterResult"); + let va = m.atom("value"); + let v = m.field(r, va); + m.edge(raw, v); + let da = m.atom("done"); + let d = m.field(r, da); + m.add_prim(d, prim::BOOL); + ret_tok(m, call, r); + if matches!(m.toks[t as usize], TokKind::Func(f, _) if m.funcs[f as usize].gen_reyields) { + m.edge(raw, c.ret); + } + } Some("toString" | "toLocaleString") => ret_prim(m, call, prim::STR), Some("hasOwnProperty") => ret_prim(m, call, prim::BOOL), _ => {} diff --git a/crates/perry-wpfacts/src/gen.rs b/crates/perry-wpfacts/src/gen.rs index e459404fa8a..f90a286dde7 100644 --- a/crates/perry-wpfacts/src/gen.rs +++ b/crates/perry-wpfacts/src/gen.rs @@ -1102,6 +1102,8 @@ impl<'a> Gen<'a> { lexical_this, no_summary: false, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: if is_closure { outer_fn } else { NO_FN }, @@ -1337,6 +1339,8 @@ impl<'a> Gen<'a> { lexical_this: false, no_summary: false, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, diff --git a/crates/perry-wpfacts/src/gen/class_values.rs b/crates/perry-wpfacts/src/gen/class_values.rs index 6db297c1311..8499f682754 100644 --- a/crates/perry-wpfacts/src/gen/class_values.rs +++ b/crates/perry-wpfacts/src/gen/class_values.rs @@ -1164,6 +1164,8 @@ impl<'a> Gen<'a> { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, diff --git a/crates/perry-wpfacts/src/gen/for_in.rs b/crates/perry-wpfacts/src/gen/for_in.rs index 3eaee252210..319d42f86bf 100644 --- a/crates/perry-wpfacts/src/gen/for_in.rs +++ b/crates/perry-wpfacts/src/gen/for_in.rs @@ -252,6 +252,8 @@ impl<'a> Gen<'a> { lexical_this: true, no_summary: false, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: fx.f, diff --git a/crates/perry-wpfacts/src/gen/import_values.rs b/crates/perry-wpfacts/src/gen/import_values.rs index 0b97ba76002..4ea71892708 100644 --- a/crates/perry-wpfacts/src/gen/import_values.rs +++ b/crates/perry-wpfacts/src/gen/import_values.rs @@ -667,6 +667,8 @@ impl<'a> Gen<'a> { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, diff --git a/crates/perry-wpfacts/src/gen_call.rs b/crates/perry-wpfacts/src/gen_call.rs index 09335e31963..e8d0a0f1727 100644 --- a/crates/perry-wpfacts/src/gen_call.rs +++ b/crates/perry-wpfacts/src/gen_call.rs @@ -748,6 +748,32 @@ impl<'a> Gen<'a> { } self.prim_node(prim::UNDEF) } + // `yield*`'s pull (`perry-runtime` `iterator_close.rs`): Call(next, + // iterator, « value »), or for a sync generator delegate stepped + // through its intrinsic `next`, the value that result would carry + // (`value`), read from its record. + "js_iterator_delegate_step" => { + let (Some(it), Some(_next), Some(v)) = (arg(0), arg(1), arg(2)) else { + return self.extern_fail(name, &args, &spread); + }; + // `next` was read from `it` once (the captured method): the + // pull is that method call on `it`, which also runs a + // generator's step closure (a generator object is its step). + let nk = Key::Named(self.m.atom("next")); + let res = self.mcall(it, nk, vec![v], vec![]); + let va = self.m.atom("value"); + let raw = self.load(res, Key::Named(va)); + let out = self.tmp("delegate-step"); + self.m.edge(res, out); + self.m.edge(raw, out); + out + } + // A `yield*` re-yield returns its pull's kind: this step closure + // may hand back an inner result object (kind 2). + "js_iterator_delegate_kind" => { + self.m.funcs[fx.f as usize].gen_reyields = true; + self.prim_node(prim::NUM) + } // The runtime calls the delegate's own `throw` / `return` with // the value (`perry-runtime` `iterator_close.rs`); a delegate // without `throw` is closed through `return` first. diff --git a/crates/perry-wpfacts/src/gen_rest.rs b/crates/perry-wpfacts/src/gen_rest.rs index 42b086556b2..169dbe25a29 100644 --- a/crates/perry-wpfacts/src/gen_rest.rs +++ b/crates/perry-wpfacts/src/gen_rest.rs @@ -964,6 +964,7 @@ impl<'a> Gen<'a> { } Expr::LinkGeneratorPrototype { obj, + is_async, i32_controls, i1_controls, .. @@ -973,6 +974,7 @@ impl<'a> Gen<'a> { for t in self.m.nodes[o as usize].pts.to_sorted() { if let TokKind::Func(f, _) = self.m.toks[t as usize] { self.m.funcs[f as usize].gen_step = true; + self.m.funcs[f as usize].gen_async = *is_async; } } for id in i32_controls.iter().chain(i1_controls.iter()) { diff --git a/crates/perry-wpfacts/src/model.rs b/crates/perry-wpfacts/src/model.rs index dfdc8c512c4..5cac28abdf1 100644 --- a/crates/perry-wpfacts/src/model.rs +++ b/crates/perry-wpfacts/src/model.rs @@ -395,6 +395,11 @@ pub struct FuncInfo { /// (`LinkGeneratorPrototype`); `next`/`return`/`throw` and the /// iteration protocol on it run the closure. pub gen_step: bool, + /// An async generator's step closure (its `next` settles a Promise). + pub gen_async: bool, + /// A sync step closure with a `yield*`: a step may hand back its + /// delegate's inner result object as-is (step kind 2). + pub gen_reyields: bool, /// A class constructor of the shape `constructor(...a) { super(...a) }` /// (the spec default a derived class gets): the `new` call's own /// arguments construct the parent, position by position, not through diff --git a/crates/perry-wpfacts/src/solve.rs b/crates/perry-wpfacts/src/solve.rs index a3663807f48..0b33d1a02e9 100644 --- a/crates/perry-wpfacts/src/solve.rs +++ b/crates/perry-wpfacts/src/solve.rs @@ -1208,6 +1208,8 @@ impl Model { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, diff --git a/crates/perry-wpfacts/src/tests.rs b/crates/perry-wpfacts/src/tests.rs index d2b21a34890..dc276f68b82 100644 --- a/crates/perry-wpfacts/src/tests.rs +++ b/crates/perry-wpfacts/src/tests.rs @@ -1342,6 +1342,8 @@ fn a_parameter_node_holds_its_argument_marker() { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, @@ -1406,6 +1408,8 @@ fn a_store_into_a_marker_makes_no_field_of_it() { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, @@ -1459,6 +1463,8 @@ fn an_argument_marker_leaves_beside_when_a_valued_member_links() { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, captures: Vec::new(), forwards_super: false, parent: NO_FN, @@ -1506,6 +1512,8 @@ fn marker_model() -> crate::model::Model { lexical_this: false, no_summary: true, gen_step: false, + gen_async: false, + gen_reyields: false, forwards_super: false, parent: NO_FN, captures: Vec::new(), @@ -1715,3 +1723,4 @@ fn a_protocol_touch_of_an_argument_marker_reads_nothing() { assert!(m.marker_use.get(&tj).is_some_and(|&u| u & MARK_ESCAPE != 0)); } mod constructor_values; +mod generator_results; diff --git a/crates/perry-wpfacts/src/tests/generator_results.rs b/crates/perry-wpfacts/src/tests/generator_results.rs new file mode 100644 index 00000000000..c101c05c5f3 --- /dev/null +++ b/crates/perry-wpfacts/src/tests/generator_results.rs @@ -0,0 +1,88 @@ +//! `yield*` pulls its delegate through `js_iterator_delegate_step`: +//! Call(next, iterator, « value »), or the value of that result, read from +//! a generator delegate's record. No unknown code runs; a delegate from +//! unmodeled code still yields ⊤. + +use super::*; + +fn facts_of(outer: &str) -> ProgramFacts { + let src = format!( + r#" + class Svc {{ size = 3; }} + function g(a: any) {{ return a.size; }} + function* inner() {{ yield new Svc(); }} + {outer} + for (const s of outer()) console.log(g(s)); + "# + ); + let ast = perry_parser::parse_typescript(&src, "t.ts").expect("parses"); + let mut hir = perry_hir::lower_module(&ast, "t", "t.ts").expect("lowers"); + perry_transform::transform_generators(&mut hir); + let mut cfg = FactsConfig::default(); + cfg.max_work.get_or_insert(50_000_000); + compute(&[("t.ts".to_string(), &hir)], &cfg) +} + +fn verdict(outer: &str) -> Verdict { + site(&facts_of(outer), "size").verdict.clone() +} + +fn proven(v: &Verdict) -> bool { + !matches!(v, Verdict::No(_)) +} + +/// node: prints `3`. A sync generator's public `next` wraps its step's raw +/// value in a fresh `{ value, done }`. +#[test] +fn a_generator_yields_its_values() { + let v = verdict("function* outer() { yield new Svc(); }"); + assert!(proven(&v), "{v:?}"); +} + +/// node: prints `3` (also through `next().value`). +#[test] +fn next_value_is_the_yielded_value() { + let v = verdict("function* outer() { const i = inner(); yield i.next().value; }"); + assert!(proven(&v), "{v:?}"); +} + +/// node: prints `3`. `yield*` re-yields the delegate's values; its inner +/// result object rides beside them (step kind 2 is returned as-is), so the +/// site sees `Svc` among its receivers, not only a primitive. +#[test] +fn a_delegated_generator_yields_its_values() { + let pf = facts_of("function* outer() { yield* inner(); }"); + let s = site(&pf, "size"); + assert!(s.receivers.iter().any(|r| r.contains("Svc")), "{:?} {:?}", s.receivers, s.verdict); +} + +/// node: prints `1`, `2`. A yielded value reaches its consumer: the site +/// holds both layouts (it is not the `A`-only site it was when the step's +/// raw value was read as its own result object). +#[test] +fn a_yielded_value_joins_the_receivers() { + let src = r#" + class A { k = 1; } + class B { z = 0; k = 2; } + function h(o: any) { return o.k; } + console.log(h(new A())); + function* gen() { yield new B(); } + for (const v of gen()) console.log(h(v)); + "#; + let ast = perry_parser::parse_typescript(src, "t.ts").expect("parses"); + let mut hir = perry_hir::lower_module(&ast, "t", "t.ts").expect("lowers"); + perry_transform::transform_generators(&mut hir); + let mut cfg = FactsConfig::default(); + cfg.max_work.get_or_insert(50_000_000); + let pf = compute(&[("t.ts".to_string(), &hir)], &cfg); + let s = site(&pf, "k"); + assert!(s.receivers.iter().any(|r| r.contains(":B")), "{:?} {:?}", s.receivers, s.verdict); +} + +/// node (`hostIter = [{ size: 'x' }]`): prints `x`. +#[test] +fn a_delegate_from_unmodeled_code_yields_top() { + let v = verdict("function* outer() { yield* (globalThis as any).hostIter; }"); + assert!(!proven(&v), "{v:?}"); +} + From b384cd7be035899e80908c5013baf8e88f08c892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 06:32:22 +0100 Subject: [PATCH 107/144] changelog: number the PR fragment --- ...-result-object.md => 13640-wpfacts-generator-result-object.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-wpfacts-generator-result-object.md => 13640-wpfacts-generator-result-object.md} (100%) diff --git a/changelog.d/0-wpfacts-generator-result-object.md b/changelog.d/13640-wpfacts-generator-result-object.md similarity index 100% rename from changelog.d/0-wpfacts-generator-result-object.md rename to changelog.d/13640-wpfacts-generator-result-object.md From 91ddefa58b8f4f946e6cd734d4eb3e158e399944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 21:13:41 +0100 Subject: [PATCH 108/144] ci(release): move Apple legs from macos-14 to macos-15 GitHub retires the macos-14 runner image on 2026-11-02 and its scheduled brownouts already cancel the aarch64 macOS and iOS release legs. The x86_64 macOS cross leg already runs on macos-15; MACOSX_DEPLOYMENT_TARGET stays pinned at 13.0. --- .github/workflows/release-packages.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 37082e21790..9155b32bbab 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -667,7 +667,7 @@ jobs: fail-fast: false matrix: include: - - os: macos-14 + - os: macos-15 target: aarch64-apple-darwin artifact: perry-macos-aarch64 wasi: true @@ -1766,7 +1766,7 @@ jobs: # manifest.json (perry_version + target_triple + sha256+size per file) # # Each matrix entry runs on the natural host runner for its toolchain: - # - Apple targets: macos-14 (real Xcode + SDKs) + # - Apple targets: macos-15 (real Xcode + SDKs) # - Android : ubuntu-24.04 + pre-installed NDK # - Windows MSVC : windows-latest (clang-cl + lib.exe) # — so no Linux→Apple sysroot or Linux→Windows VM dance is needed inside @@ -1781,7 +1781,7 @@ jobs: matrix: include: # --- Apple, Tier-2 (stable) --------------------------------------- - - os: macos-14 + - os: macos-15 target: aarch64-apple-darwin ui_crate: perry-ui-macos ui_lib: libperry_ui_macos.a @@ -1789,11 +1789,11 @@ jobs: target: x86_64-apple-darwin ui_crate: perry-ui-macos ui_lib: libperry_ui_macos.a - - os: macos-14 + - os: macos-15 target: aarch64-apple-ios ui_crate: perry-ui-ios ui_lib: libperry_ui_ios.a - - os: macos-14 + - os: macos-15 target: aarch64-apple-ios-sim ui_crate: perry-ui-ios ui_lib: libperry_ui_ios.a From 1dde1395a40af8d7e66e2af5987989bdb95d3147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 31 Oct 2026 23:43:53 +0000 Subject: [PATCH 109/144] dce: the link drops every function nothing live references (layer 0) Native objects get one input section per function and per global (function and data sections, set on the target machine by a two-field adapter beside the wasm one), and JS bodies one section each inside perry_js_text. Each body's metadata record and GC map entry live in sections tied to the body's own (ELF SHF_LINK_ORDER, Mach-O live_support, COFF associative COMDAT), so nothing roots a function any more: not a shared section, not the per-module owner table (gone), not a retained GC map directory (gone). Readers find the survivors through linker bounds: function names and sources by IP walk the image's perry_fnrec section, which the owner names by a self-relative offset (module init is unchanged); the GC map (v9) is one 20-byte entry and one records blob per function, the version in every entry. Unloadable images copy their records at registration, one slot per ordinal. wasm-ld has no tied sections, so wasm keeps every record, as before. --- changelog.d/0-dce-layer0-linker.md | 19 + crates/perry-abi/src/function_metadata.rs | 41 +- crates/perry-codegen/build.rs | 19 +- .../src/codegen/emission_order_tests.rs | 18 +- crates/perry-codegen/src/fn_record.rs | 235 +++++++++ crates/perry-codegen/src/function_order.rs | 15 +- crates/perry-codegen/src/gc_map.rs | 298 +++++------- crates/perry-codegen/src/gc_map_tests.rs | 164 +++---- .../src/inprocess/function_layout.rs | 66 +++ .../src/inprocess/optimize_emit.rs | 12 + .../src/inprocess/unique_sections.cpp | 15 + crates/perry-codegen/src/lib.rs | 1 + crates/perry-codegen/src/module.rs | 81 +++- crates/perry-codegen/src/native_emit.rs | 17 +- .../perry-runtime/src/builtins/fn_metadata.rs | 10 +- .../src/builtins/fn_metadata_owner.rs | 451 +++++++++++------- .../src/builtins/fn_metadata_owner_tests.rs | 78 ++- .../perry-runtime/src/gc/roots/stack_maps.rs | 33 +- .../src/gc/roots/stack_maps/census_rows.rs | 4 +- .../src/gc/roots/stack_maps_decode.rs | 383 +++++---------- .../src/gc/roots/stack_maps_decode_tests.rs | 375 ++++++--------- .../gc/roots/stack_maps_frame_walk_tests.rs | 2 +- .../src/gc/roots/stack_maps_index.rs | 3 +- .../src/gc/roots/stack_maps_lazy.rs | 263 +++++----- .../gc/roots/stack_maps_walker_agreement.rs | 2 +- .../commands/compile/link/function_layout.rs | 39 +- scripts/a1/linkpad.py | 13 +- 27 files changed, 1515 insertions(+), 1142 deletions(-) create mode 100644 changelog.d/0-dce-layer0-linker.md create mode 100644 crates/perry-codegen/src/fn_record.rs create mode 100644 crates/perry-codegen/src/inprocess/unique_sections.cpp diff --git a/changelog.d/0-dce-layer0-linker.md b/changelog.d/0-dce-layer0-linker.md new file mode 100644 index 00000000000..1e560218b32 --- /dev/null +++ b/changelog.d/0-dce-layer0-linker.md @@ -0,0 +1,19 @@ +The link now removes every compiled function nothing live references, with no +analysis. Three things used to keep every function of a linked module: its +functions and constants shared one input section each (function and data +sections were off), every function's metadata sat in a per-module owner table +that module init registered, and the GC map was one retained directory per +module. Native objects now have one input section per function and per global +(`-ffunction-sections -fdata-sections` through a two-field adapter beside the +wasm one), JS bodies keep one section each inside `perry_js_text`, each body's +metadata record and GC map entry are tied to the body's own section (ELF +`SHF_LINK_ORDER`, Mach-O `live_support`, COFF associative COMDAT), and readers +walk the linker-bounded record section, which only holds survivors. The owner +keeps its identity and overrides and names its image's records with a +self-relative offset, so module init is unchanged. GC map v9 has one 20-byte +entry and one records blob per function, carrying the version in each entry. +wasm has no tied sections in wasm-ld, so there every record stays, as before. + +Fastify inject is 3.0% smaller and Richards 3.1%; startup Ir does not increase. +Tests cover the record and entry formats, the unit split, the COFF COMDAT tie, +image copies for unloadable images, and a shared object's v9 map. diff --git a/crates/perry-abi/src/function_metadata.rs b/crates/perry-abi/src/function_metadata.rs index 23f47aea597..8b97cb8b76a 100644 --- a/crates/perry-abi/src/function_metadata.rs +++ b/crates/perry-abi/src/function_metadata.rs @@ -7,28 +7,57 @@ pub const FN_HAS_METADATA: u32 = 1 << 25; /// The owner and all its relative info records are permanent image data. pub const METADATA_PERMANENT: u32 = 1; -/// One image's canonical entry inventory. Each signed entry is relative to -/// `entries` and names the info that owns its name/source. Runtime-native -/// owners have one entry and a null entries pointer. `state` owns copied -/// metadata and sparse overrides; null means no mutable payload exists. +/// One module's metadata owner: the identity its infos name (with an +/// ordinal below `count`) and, in `state`, its copied metadata and sparse +/// overrides (null: no mutable payload). It lists no bodies: each body's +/// record lives in its image's records section, which `bounds` names: a +/// signed offset from the `bounds` field itself to the image's +/// [`FunctionRecordBounds`] (a link-time constant, so no relocation), or 0 +/// for none. Runtime-native owners have one ordinal and no records. #[repr(C)] pub struct FunctionMetadataOwner { - pub entries: *const i32, pub count: u32, pub flags: u32, pub state: AtomicPtr, + pub bounds: i32, } unsafe impl Sync for FunctionMetadataOwner {} impl FunctionMetadataOwner { pub const fn native() -> Self { Self { - entries: core::ptr::null(), count: 1, flags: 0, state: AtomicPtr::new(core::ptr::null_mut()), + bounds: 0, } } + + /// The image records this owner's module registers, if any. + pub fn record_bounds(&self) -> Option<&FunctionRecordBounds> { + if self.bounds == 0 { + return None; + } + let at = (&self.bounds as *const i32 as usize).wrapping_add_signed(self.bounds as isize); + // SAFETY: a nonzero offset is the linker-resolved distance to the + // bounds descriptor of the image this owner was emitted into. + unsafe { (at as *const FunctionRecordBounds).as_ref() } + } +} + +/// One image's function records section, as the linker left it: the records +/// of the bodies the link kept, between `start` and `stop`. A record begins +/// with the signed offset from itself to its body's `JsFunctionInfo`; a zero +/// offset is no record (the image sentinel, a section marker, padding). +/// Mach-O records carry a second word (the code, which keeps them live). +#[repr(C)] +pub struct FunctionRecordBounds { + pub start: *const u8, + pub stop: *const u8, + pub sentinel: *const u8, } +unsafe impl Sync for FunctionRecordBounds {} +/// Bytes per record in this target's records section. +pub const FUNCTION_RECORD_BYTES: usize = if cfg!(target_vendor = "apple") { 8 } else { 4 }; /// Indexing never derives identity from a code or owner address. #[repr(C)] diff --git a/crates/perry-codegen/build.rs b/crates/perry-codegen/build.rs index 146bc612bed..e31486b0587 100644 --- a/crates/perry-codegen/build.rs +++ b/crates/perry-codegen/build.rs @@ -12,7 +12,10 @@ fn main() { println!("cargo:rerun-if-env-changed=LLVM_SYS_221_PREFIX"); println!("cargo:rerun-if-changed=src/wasm32/target_options.cpp"); - if std::env::var_os("CARGO_FEATURE_TARGET_WASI").is_some() { + println!("cargo:rerun-if-changed=src/inprocess/unique_sections.cpp"); + let wasi = std::env::var_os("CARGO_FEATURE_TARGET_WASI").is_some(); + let inprocess = std::env::var_os("CARGO_FEATURE_LLVM_INPROCESS").is_some(); + if wasi || inprocess { let include = if let Some(prefix) = std::env::var_os("LLVM_SYS_221_PREFIX") { std::path::PathBuf::from(prefix).join("include") } else { @@ -23,13 +26,19 @@ fn main() { assert!(output.status.success(), "llvm-config --includedir failed"); std::path::PathBuf::from(String::from_utf8(output.stdout).unwrap().trim()) }; - cc::Build::new() + let mut build = cc::Build::new(); + build .cpp(true) .include(include) - .file("src/wasm32/target_options.cpp") .flag_if_supported("-std=c++17") - .flag_if_supported("/std:c++17") - .compile("perry_wasm_target_options"); + .flag_if_supported("/std:c++17"); + if wasi { + build.file("src/wasm32/target_options.cpp"); + } + if inprocess { + build.file("src/inprocess/unique_sections.cpp"); + } + build.compile("perry_llvm_target_options"); } if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { diff --git a/crates/perry-codegen/src/codegen/emission_order_tests.rs b/crates/perry-codegen/src/codegen/emission_order_tests.rs index 1cdc89e17b5..3a4be3862d9 100644 --- a/crates/perry-codegen/src/codegen/emission_order_tests.rs +++ b/crates/perry-codegen/src/codegen/emission_order_tests.rs @@ -247,12 +247,12 @@ fn closure_display_module() -> Module { m } -/// The `func_id` of every closure entry in the name table, in emission order. +/// The `func_id` of every closure's metadata record (`crate::fn_record`), in +/// emission order (the order of the owner's ordinals). fn registered_closure_ids(ir: &str) -> Vec { ir.lines() - .find(|line| line.starts_with("@__perry_function_metadata_") && line.contains("_entries =")) - .into_iter() - .flat_map(|line| line.split("ptrtoint (ptr @perry_closure_").skip(1)) + .filter(|line| line.starts_with("@perry_closure_") && line.contains("$fnrec = ")) + .flat_map(|line| line.split("ptrtoint (ptr @perry_closure_").skip(1).take(1)) .filter_map(|entry| { let symbol = entry.split(" to i64)").next()?; symbol @@ -905,13 +905,13 @@ fn comdat_function_names_reference_images_without_local_aliases() { opts.target = Some("x86_64-unknown-linux-gnu".to_string()); let llvm = String::from_utf8(compile_module(&closure_display_module(), opts).unwrap()).unwrap(); assert!(!llvm.contains(" = internal alias i8, getelementptr")); - let table = llvm + let record = llvm .lines() - .find(|line| line.starts_with("@__perry_function_metadata_") && line.contains("_entries =")) - .unwrap(); + .find(|line| line.contains("$fnrec = ")) + .expect("a body publishing an info has a metadata record"); assert!( - table.contains("$info to i64)"), - "the inventory references canonical infos, not a second name snapshot" + record.contains("$info to i64)"), + "the record references the canonical info, not a second name snapshot" ); let info = llvm.lines().find(|line| line.contains("$info =") && line.contains("ptrtoint (ptr getelementptr (i8, ptr getelementptr (i8, ptr @perry_image_literal_")).expect("function info name points directly at its COMDAT image"); assert!(info.contains("i64 20)")); diff --git a/crates/perry-codegen/src/fn_record.rs b/crates/perry-codegen/src/fn_record.rs new file mode 100644 index 00000000000..39e13906ca8 --- /dev/null +++ b/crates/perry-codegen/src/fn_record.rs @@ -0,0 +1,235 @@ +//! Function metadata records: one per body that publishes a `JsFunctionInfo`, +//! tied to the body's own input section so the link keeps a record exactly +//! when it keeps the body (DCE layer 0). +//! +//! A record is the info's self-relative offset. Readers walk the image's +//! records section between its linker-defined bounds, which hold only the +//! records of bodies the link kept; no table names the bodies. +//! +//! | object format | the record lives and dies with its body through | +//! |---|---| +//! | ELF | `SHF_LINK_ORDER` to the body's section (`!associated`) | +//! | Mach-O | `live_support`: kept iff it references a live atom; the record also names the code, so it is kept exactly when the code is | +//! | COFF | the body's COMDAT, as an associative section (attached right before emission, `inprocess::function_layout`) | +//! | wasm | nothing (wasm-ld has no such section kind): every record stays, as the old table did | +//! +//! Every image also defines one zero record (the sentinel) and a bounds +//! descriptor that each module's metadata owner names (a self-relative +//! offset, so it costs registration nothing): +//! the sentinel guarantees the section exists in an image whose every record +//! was removed, and readers skip a zero record (also COFF's `$a` marker and +//! any alignment padding). + +/// The records section's object-format flavour. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum RecordFormat { + Elf, + MachO, + Coff, + Wasm, +} + +impl RecordFormat { + pub(crate) fn of(triple: &str) -> Self { + if triple.contains("apple") { + Self::MachO + } else if triple.contains("windows") { + Self::Coff + } else if triple.starts_with("wasm") { + Self::Wasm + } else { + Self::Elf + } + } +} + +/// `body`'s record as an IR global name. Records carry no symbol into the +/// link: ELF/COFF/wasm records are `private`; a Mach-O record needs a symbol +/// to be its own atom, and an `l`-prefixed one is linker-private (ld64 drops +/// it from the output). +pub(crate) fn record_global(format: RecordFormat, body: &str) -> String { + match format { + RecordFormat::MachO => format!("@\"\\01l_{body}$fnrec\""), + _ => format!("@{body}$fnrec"), + } +} + +/// The body a record line belongs to, for the codegen-unit splitter: a record +/// must be emitted in the unit that defines its body (ELF links the record's +/// section to the body's section, which only exists in that object), and it +/// is emitted exactly as written (nothing outside that unit names it). +pub(crate) fn record_body(line: &str) -> Option<&str> { + let name = line.strip_prefix('@')?.split_once(" = ")?.0; + let name = match name.strip_prefix("\"\\01l_") { + Some(quoted) => quoted.strip_suffix('"')?, + None => name, + }; + name.strip_suffix("$fnrec") +} + +/// Keep `lines`' records through the optimizer: nothing in the IR names a +/// record, and GlobalDCE would delete it. `llvm.compiler.used` protects it +/// from the compiler only; the link still drops it with its body. +pub(crate) fn push_compiler_used<'a>(ir: &mut String, lines: impl Iterator) { + let names: Vec<&str> = lines + .filter(|line| record_body(line).is_some()) + .filter_map(|line| line.split_once(" = ").map(|(name, _)| name)) + .collect(); + if names.is_empty() { + return; + } + let list: Vec = names.iter().map(|name| format!("ptr {name}")).collect(); + ir.push_str(&format!( + "@llvm.compiler.used = appending global [{} x ptr] [{}], section \"llvm.metadata\"\n", + names.len(), + list.join(", ") + )); +} + +/// The body of a record global's IR name (`$fnrec`), for the passes +/// that see the final LLVM module rather than its text. +pub(crate) fn body_of_record_name(name: &str) -> Option<&str> { + name.strip_prefix("\u{1}l_").unwrap_or(name).strip_suffix("$fnrec") +} + +/// Bounds descriptor every registering module's owner names. +pub(crate) const BOUNDS_SYMBOL: &str = "perry_fnrec_bounds"; +const SENTINEL_SYMBOL: &str = "perry_fnrec_sentinel"; + +fn section(format: RecordFormat) -> &'static str { + match format { + RecordFormat::Elf | RecordFormat::Wasm => "perry_fnrec", + RecordFormat::MachO => "__TEXT,__perry_fnrec,regular,live_support", + RecordFormat::Coff => ".perryfn$m", + } +} + +/// `target - from` as an `i32` constant; both operands are pointer constants +/// (`@sym` or a constant `getelementptr`). +fn rel(target: &str, from: &str) -> String { + format!("i32 trunc (i64 sub (i64 ptrtoint (ptr {target} to i64), i64 ptrtoint (ptr {from} to i64)) to i32)") +} + +/// `body`'s record definition. +pub(crate) fn record_line(format: RecordFormat, body: &str) -> String { + let rec = record_global(format, body); + let info = format!("@{}", crate::fn_info::info_symbol(body)); + let section = section(format); + match format { + RecordFormat::Elf => format!( + "{rec} = private constant {}, section \"{section}\", align 4, !associated !{{ptr @{body}}}", + rel(&info, &rec) + ), + RecordFormat::Wasm => format!( + "{rec} = private constant {}, section \"{section}\", align 4", + rel(&info, &rec) + ), + // The code word is what `live_support` keeps the record by. + RecordFormat::MachO => format!( + "{rec} = internal constant {{ i32, i32 }} {{ {}, {} }}, section \"{section}\", align 4", + rel(&info, &rec), + rel( + &format!("@{body}"), + &format!("getelementptr inbounds ({{ i32, i32 }}, ptr {rec}, i32 0, i32 1)") + ), + ), + RecordFormat::Coff => format!( + "{rec} = private constant {}, section \"{section}\", align 4", + rel(&info, &rec) + ), + } +} + +/// The image-level lines: `(symbol, line)`. External declarations are plain +/// lines; definitions are coalesced to one per image. +pub(crate) fn image_lines(format: RecordFormat) -> Vec<(String, String)> { + let section = section(format); + let bounds = |start: &str, stop: &str| { + ( + BOUNDS_SYMBOL.to_string(), + format!( + "@{BOUNDS_SYMBOL} = linkonce_odr hidden constant {{ ptr, ptr, ptr }} {{ ptr {start}, ptr {stop}, ptr @{SENTINEL_SYMBOL} }}, align 8" + ), + ) + }; + match format { + RecordFormat::Elf | RecordFormat::Wasm => vec![ + ( + "__start_perry_fnrec".into(), + "@__start_perry_fnrec = external hidden global i8".into(), + ), + ( + "__stop_perry_fnrec".into(), + "@__stop_perry_fnrec = external hidden global i8".into(), + ), + ( + SENTINEL_SYMBOL.into(), + format!("@{SENTINEL_SYMBOL} = linkonce_odr hidden constant i32 0, section \"{section}\", align 4"), + ), + bounds("@__start_perry_fnrec", "@__stop_perry_fnrec"), + ], + // ld64 synthesizes `section$start$SEG$SECT` only under that exact + // name (the leading \01 stops the `_` prefix). + RecordFormat::MachO => vec![ + ( + "\"\\01section$start$__TEXT$__perry_fnrec\"".into(), + "@\"\\01section$start$__TEXT$__perry_fnrec\" = external hidden global i8".into(), + ), + ( + "\"\\01section$end$__TEXT$__perry_fnrec\"".into(), + "@\"\\01section$end$__TEXT$__perry_fnrec\" = external hidden global i8".into(), + ), + ( + SENTINEL_SYMBOL.into(), + format!("@{SENTINEL_SYMBOL} = linkonce_odr hidden constant {{ i32, i32 }} zeroinitializer, section \"{section}\", align 4"), + ), + bounds( + "@\"\\01section$start$__TEXT$__perry_fnrec\"", + "@\"\\01section$end$__TEXT$__perry_fnrec\"", + ), + ], + // The linker sorts `.perryfn$a` < `$m` < `$z` within `.perryfn`: the + // two markers bound the records (the `$a` marker is itself a zero + // record, and so is the sentinel). + RecordFormat::Coff => vec![ + ( + "perry_fnrec_a".into(), + "@perry_fnrec_a = linkonce_odr hidden constant i32 0, section \".perryfn$a\", align 4".into(), + ), + ( + "perry_fnrec_z".into(), + "@perry_fnrec_z = linkonce_odr hidden constant i32 0, section \".perryfn$z\", align 4".into(), + ), + ( + SENTINEL_SYMBOL.into(), + format!("@{SENTINEL_SYMBOL} = linkonce_odr hidden constant i32 0, section \"{section}\", align 4"), + ), + bounds("@perry_fnrec_a", "@perry_fnrec_z"), + ], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_record_names_its_body_for_the_unit_splitter() { + for format in [RecordFormat::Elf, RecordFormat::MachO, RecordFormat::Coff, RecordFormat::Wasm] { + let line = record_line(format, "__perry_wrap_m__f"); + assert_eq!(record_body(&line), Some("__perry_wrap_m__f"), "{line}"); + } + assert_eq!(record_body("@__perry_wrap_m__f$info = internal constant i32 0"), None); + } + + #[test] + fn elf_records_are_tied_to_their_body_and_macho_records_name_the_code() { + let elf = record_line(RecordFormat::Elf, "b"); + assert!(elf.ends_with(", !associated !{ptr @b}"), "{elf}"); + let macho = record_line(RecordFormat::MachO, "b"); + assert!(macho.contains("live_support"), "{macho}"); + assert!(macho.contains("ptrtoint (ptr @b to i64), i64 ptrtoint (ptr getelementptr inbounds ({ i32, i32 }, ptr @\"\\01l_b$fnrec\", i32 0, i32 1) to i64)"), "{macho}"); + let coff = record_line(RecordFormat::Coff, "b"); + assert!(coff.contains("section \".perryfn$m\""), "{coff}"); + } +} diff --git a/crates/perry-codegen/src/function_order.rs b/crates/perry-codegen/src/function_order.rs index 9a3824adce5..70ad4e7be7d 100644 --- a/crates/perry-codegen/src/function_order.rs +++ b/crates/perry-codegen/src/function_order.rs @@ -54,6 +54,17 @@ pub const SORTED_SECTION_PREFIX: &str = ".text.sorted."; /// rank order at the front of that output section. pub const JS_SORTED_SECTION_PREFIX: &str = "perry_js_text.sorted."; +/// Prefix of a JS body's own input section (`split_js_text`): the link +/// drops each one nothing references and gathers the rest into the JS text +/// output section ([`JS_TEXT_LINK_RULE`]). +pub const JS_FN_SECTION_PREFIX: &str = "perry_js_text.fn."; + +/// The ELF link rule that gathers the JS text input sections into the +/// `perry_js_text` output section: a C-identifier name, so the linker defines +/// `__start_`/`__stop_perry_js_text` around every JS frame. +pub const JS_TEXT_LINK_RULE: &str = + "SECTIONS {\n perry_js_text : ALIGN(16) {\n *(perry_js_text perry_js_text.fn.*)\n }\n} INSERT AFTER .text;\n"; + /// A parsed order list: function name -> rank (0 runs first). #[derive(Debug, Clone, PartialEq, Eq)] pub struct FunctionOrder { @@ -139,7 +150,7 @@ impl FunctionOrder { // section name, so the linker defines `__start_`/`__stop_perry_js_text` // around both: every JS frame is in that range. out.push_str( - "SECTIONS {\n perry_js_text : ALIGN(16) {\n __perry_hot_js_text_start = .;\n *(SORT(perry_js_text.sorted.*))\n __perry_hot_js_text_end = .;\n __perry_hot_js_text_limit = __perry_hot_js_text_end;\n *(perry_js_text)\n }\n} INSERT AFTER .text;\n", + "SECTIONS {\n perry_js_text : ALIGN(16) {\n __perry_hot_js_text_start = .;\n *(SORT(perry_js_text.sorted.*))\n __perry_hot_js_text_end = .;\n __perry_hot_js_text_limit = __perry_hot_js_text_end;\n *(perry_js_text perry_js_text.fn.*)\n }\n} INSERT AFTER .text;\n", ); Ok(out) } @@ -280,7 +291,7 @@ mod tests { let script = order.elf_linker_script().unwrap(); let js = script.split("perry_js_text : ALIGN(16)").nth(1).expect("JS text output section"); let first = js.find("*(SORT(perry_js_text.sorted.*))").expect("ranked first"); - let rest = js.find("*(perry_js_text)").expect("then the rest"); + let rest = js.find("*(perry_js_text perry_js_text.fn.*)").expect("then the rest"); assert!(first < rest); assert!(js.contains("__perry_hot_js_text_start")); assert!(js.contains("__perry_hot_js_text_limit = __perry_hot_js_text_end")); diff --git a/crates/perry-codegen/src/gc_map.rs b/crates/perry-codegen/src/gc_map.rs index c520ba5af69..7920c6a380f 100644 --- a/crates/perry-codegen/src/gc_map.rs +++ b/crates/perry-codegen/src/gc_map.rs @@ -55,88 +55,71 @@ use std::process::Command; use anyhow::{anyhow, Context, Result}; -/// Magic at the start of every emitted blob. -const GC_MAP_MAGIC: &[u8; 4] = b"PGCM"; /// Format version. Bump on any layout change — the runtime rejects others. /// -/// v8: the blob is split in two. The DIRECTORY (header + one 20-byte entry -/// per function) stays in the map section; the RECORDS (instruction-offset -/// array + varint stream) move to their own section, named by a link-time -/// self-relative offset in the directory header. Building the runtime index -/// reads every directory and nothing else, so a process now keeps only the -/// directories resident (1.4 MB for claude-code) instead of faulting in most -/// of a 15 MB section whose function tables were interleaved with records; a -/// collection reads the records of the frames it walks. Each entry also -/// carries its first record's index, so no prefix sum over the table is -/// needed to find a function's instruction offsets. +/// v9 (DCE layer 0): one directory entry and one records blob PER FUNCTION, +/// each in its own input section tied to the function's section (ELF +/// `SHF_LINK_ORDER`, Mach-O `live_support`, COFF associative COMDAT), so the +/// link keeps a function's map exactly when it keeps the function. Nothing is +/// retained by itself any more: v8's per-module directory, which named every +/// function of the module and was `SHF_GNU_RETAIN` / `.no_dead_strip`, rooted +/// every one of them under `--gc-sections`. The entry carries the version (no +/// module header exists to carry it), and names its records blob by a +/// self-relative offset and length; first-record indices and stream offsets +/// are gone because each blob holds only its own function. /// -/// v6 (#11508): each function's address is a signed 32-bit offset from the -/// blob's own start (`.long fn-_perry_gc_map`) instead of an absolute -/// pointer. That is a link-time constant — PC32/PREL32 on ELF, REL32 on COFF, a -/// SUBTRACTOR pair on Mach-O — so the linked section carries NO dynamic -/// relocations (v5 cost one `R_*_RELATIVE` or chained fixup per function, all -/// applied eagerly at load, dirtying every page of the table), and on ELF and -/// COFF the section can be read-only. The entry is 12 bytes on every target, -/// so the header's old pointer-width flag is gone and flags must be 0. +/// v8: the directory (header + one 20-byte entry per function) and the +/// records (instruction-offset array + varint stream) were split into two +/// sections, so building the index faults in only the directories. /// -/// v5: a `u32 stream_offset` per function follows the function table, so the -/// runtime can decode ONE function's records without decoding every function -/// before it. The record stream and the instruction-offset array are byte for -/// byte what v4 emitted; this is an added array and nothing else. It costs -/// +1.3% of the section, and it is what lets a collection read the 74 records -/// a `claude --help` run needs instead of all 2,078,970. +/// v6 (#11508): each function's address is a signed 32-bit link-time offset +/// (`.long fn-anchor`) — PC32/PREL32 on ELF, REL32 on COFF, a SUBTRACTOR pair +/// on Mach-O — so the linked section carries NO dynamic relocations and on +/// ELF and COFF can be read-only. +/// +/// v5: a function's records can be decoded without decoding every function +/// before it (a collection reads the 74 records a `claude --help` run needs +/// instead of all 2,078,970). v9 keeps that by construction. /// /// v4 (#7803): the record header word gained a has-derived bit and records -/// carry DERIVED (interior) pointer slots tied to their bases. v3 collapsed -/// every statepoint (base, derived) pair to one slot on the false premise -/// that Perry emits no interior pointers; the runtime decoder fails closed on -/// a version mismatch, so both sides bump together. -const GC_MAP_VERSION: u8 = 8; -/// Section the compact map is emitted into, and the label it is given. -const GC_MAP_LABEL: &str = "_perry_gc_map"; -/// Label of this module's records blob (v8), named by the directory header. -const GC_REC_LABEL: &str = "_perry_gc_rec"; -/// Fixed v8 directory header: magic, version, reserved, flags, function count, -/// directory length, records offset, records length, record total, reserved. -const DIRECTORY_HEADER_BYTES: usize = 32; -/// v8 directory entry: function offset, stack size, record count, first -/// record index, stream offset. +/// carry DERIVED (interior) pointer slots tied to their bases. +const GC_MAP_VERSION: u8 = 9; +/// v9 directory entry: function offset, stack size, record count with the +/// version in its top byte, records offset, records length. const DIRECTORY_ENTRY_BYTES: usize = 20; /// Mach-O keeps its own segment so the runtime's lookup is unchanged. ld64 /// maps a custom segment `rw-`, but since v6 there is nothing in it to fix up, /// so dyld never writes its pages and they stay clean, file-backed memory. -const MACHO_SECTION: &str = "__PERRY_GCMAP,__perry_gcmap"; -/// v8 records. The runtime never looks this section up: the directory names -/// its records by a self-relative offset. Kept in the map's segment. +/// `live_support`: ld64 keeps an entry (its own atom, see the labels in +/// [`emit_asm`]) exactly when an atom it references, the function, is live. +const MACHO_SECTION: &str = "__PERRY_GCMAP,__perry_gcmap,regular,live_support"; +/// v9 records: a plain section; a live entry's offset keeps its blob. const MACHO_RECORDS_SECTION: &str = "__PERRY_GCMAP,__perry_gcrec"; /// `a` without `w`: since v6 the section holds no absolute addresses, only -/// link-time-resolved `fn - anchor` differences, so it needs no dynamic -/// relocations and can be read-only. (v5 needed SHF_WRITE for its relocated -/// `.quad`s; without it the linker created a DT_TEXTREL.) -/// -/// `R` is SHF_GNU_RETAIN, the ELF analogue of Mach-O's `.no_dead_strip`. -/// Perry links with `-Wl,--gc-sections`, and nothing in the program -/// references this section — the collector finds it by name at runtime — so -/// without RETAIN the linker discards it and the binary ships with no GC map -/// at all. Measured: the section is present in the object (PROGBITS, SHF_ALLOC, -/// with relocations) and absent from the linked binary. -const ELF_SECTION: &str = ".perry_gcmap,\"aR\",@progbits"; -/// v8 records: retained like the directory (the only reference to it is the -/// directory's offset, which `--gc-sections` would otherwise not count). -const ELF_RECORDS_SECTION: &str = ".perry_gcrec,\"aR\",@progbits"; +/// link-time-resolved differences, so it needs no dynamic relocations. `o` +/// (`SHF_LINK_ORDER`) ties each entry to its function's section: `--gc-sections` +/// keeps it exactly when it keeps the function (GNU ld >= 2.36, lld). The +/// collector finds the section by name at run time. +const ELF_SECTION: &str = ".perry_gcmap,\"ao\",@progbits"; +/// v9 records, tied to the function the same way. +const ELF_RECORDS_SECTION: &str = ".perry_gcrec,\"ao\",@progbits"; /// COFF/PE. The name is SHORT on purpose: a PE image section header has an -/// 8-byte name field, and long names survive only in object files (as a `/nnn` -/// string-table offset) — the linker cannot put `.perry_gcmap` in the image, so -/// the runtime would never find it by name. `dr` is initialised, read-only -/// data: since v6 the function fields are REL32 differences the linker -/// resolves, so the image needs no base relocations for them. -const COFF_SECTION: &str = ".pgcmap,\"dr\""; -/// v8 records; never looked up by name, so the 8-byte limit is only hygiene. -const COFF_RECORDS_SECTION: &str = ".pgcrec,\"dr\""; +/// 8-byte name field, and long names survive only in object files — the +/// linker cannot put `.perry_gcmap` in the image, so the runtime would never +/// find it by name. `$m` groups every object's entries into `.pgcmap`; each is +/// an associative COMDAT of its function's section, so `/OPT:REF` drops it +/// with the function (and ICF folds two functions only when their maps are +/// equal too). `dr` is initialised, read-only data. +const COFF_SECTION: &str = ".pgcmap$m,\"dr\""; +/// v9 records; never looked up by name, so the 8-byte limit is only hygiene. +const COFF_RECORDS_SECTION: &str = ".pgcrec$m,\"dr\""; /// What the runtime looks for in a PE image. Must match `COFF_SECTION`'s name /// and stay within eight bytes. #[cfg(test)] pub(crate) const COFF_SECTION_NAME: &str = ".pgcmap"; +/// `unique` ids for the ELF entry and records sections, far above the ids +/// LLVM gives its own sections in the same object. +const ELF_UNIQUE_BASE: u64 = 1 << 30; /// LLVM stack-map v3 location kinds. Only these two describe a frame slot; /// `Constant`/`ConstIndex` carry the statepoint preamble and `Register` cannot @@ -1167,48 +1150,30 @@ fn verify_roundtrip(functions: &[FunctionMap], compact: &CompactStream) -> Resul /// Assemble the emitted directives for one compact map. /// -/// Layout (little-endian), mirrored by the runtime decoder. v8 emits two -/// blobs per module, the directory in the map section and the records in a -/// section of their own: +/// Layout (little-endian), mirrored by the runtime decoder. v9 emits two +/// blobs per FUNCTION, each in a section tied to the function's: /// /// ```text -/// directory (`_perry_gc_map`, map section): -/// 0 "PGCM" -/// 4 u8 version, u8 reserved, u16 flags (0) -/// 8 u32 function_count -/// 12 u32 total_len -- stride to the next directory in the section -/// 16 i32 records_offset -- `_perry_gc_rec - _perry_gc_map` -/// 20 u32 records_len -/// 24 u32 record_total -/// 28 u32 reserved (0) -/// 32 function_count x { i32 function_offset, u32 stack_size, -/// u32 record_count, u32 first_record, -/// u32 stream_offset } -/// records (`_perry_gc_rec`, records section): -/// record_total x u32 instruction_offset -/// varint root stream (see `encode_stream`) +/// entry (map section, 4-byte aligned, `e` = the entry's own address): +/// 0 i32 function - e +/// 4 u32 stack_size +/// 8 u32 record_count | version << 24 +/// 12 i32 records - e +/// 16 u32 records_len +/// records (records section, byte aligned): +/// record_count x u32 instruction_offset +/// varint root stream of this function (see `encode_stream`) /// ``` /// -/// The v5 `stream_offset` array is where each function's records begin in the -/// varint stream. Without it the stream is only readable from the front — it -/// is delta- and repeat-chained — so a collector that wanted one frame's live -/// set had to decode the whole section. It is placed AFTER the function table -/// rather than inside it so the function entry keeps its v4 shape. -/// -/// v6: `function_offset` is `function - blob_start`, where `blob_start` is the -/// `_perry_gc_map` label at the magic. Every object's blob has its own label, -/// so concatenated blobs each anchor to themselves, and the runtime recovers -/// the address as `address_of(blob) + function_offset`. It is signed because -/// the linker may place text on either side of the map; the ±2 GiB reach is -/// the same assumption every PC32/PREL32 relocation in the program already -/// makes. Every field is 4 bytes, so the whole blob is 4-byte aligned. +/// The linked map section is the entries of the functions the link kept, in +/// whatever order the link placed them; the runtime sorts by address. Offsets +/// are signed because the linker may place text on either side of the map; +/// the ±2 GiB reach is the same assumption every PC32/PREL32 relocation in +/// the program already makes. /// /// Instruction offsets are a fixed-width array rather than part of the varint /// stream because at `-O3` they are **label differences the assembler /// evaluates** (`Ltmp9-_main`), so their values do not exist at rewrite time. -/// That costs ~4 bytes per record — 18.7x compaction instead of 31.8x — and -/// buys not having to assemble twice just to learn numbers the assembler is -/// about to compute anyway. #[derive(Clone, Copy, PartialEq, Eq)] enum ObjectFormat { MachO, @@ -1305,82 +1270,81 @@ fn emit_asm( entry_labels: &HashMap, ) -> String { let stream = &compact.bytes[..]; - let record_total: usize = functions.iter().map(|f| f.records.len()).sum(); - let directory_len = directory_len(functions); - let records_len = records_len(functions, stream.len()); - let (directory_section, records_section) = match format { - ObjectFormat::MachO => (MACHO_SECTION, MACHO_RECORDS_SECTION), - ObjectFormat::Elf => (ELF_SECTION, ELF_RECORDS_SECTION), - ObjectFormat::Coff => (COFF_SECTION, COFF_RECORDS_SECTION), - }; let mut out = String::new(); - // Records first: the asm that follows the map (re-emitted symbol lines) - // keeps landing after the directory, exactly where it landed before v8. - out.push_str(&format!("\t.section\t{records_section}\n")); - out.push_str("\t.p2align\t2\n"); - out.push_str(&format!("{GC_REC_LABEL}:\n")); - for function in functions { + for (index, function) in functions.iter().enumerate() { + let begin = compact.function_offsets[index] as usize; + let end = compact + .function_offsets + .get(index + 1) + .map_or(stream.len(), |&next| next as usize); + let own_stream = &stream[begin..end]; + // Section directives that tie each blob to the function's section. + // Mach-O labels start with `l`: linker-private symbols, each the start + // of its own atom (`.subsections_via_symbols`), dropped from the + // output; ELF and COFF tie sections, so `.L` temporaries do. + let (map_section, rec_section, map_label, rec_label) = match format { + ObjectFormat::Elf => ( + format!( + "{ELF_SECTION},{},unique,{}", + function.symbol, + ELF_UNIQUE_BASE + 2 * index as u64 + ), + format!( + "{ELF_RECORDS_SECTION},{},unique,{}", + function.symbol, + ELF_UNIQUE_BASE + 2 * index as u64 + 1 + ), + format!(".Lperry_gcmap{index}"), + format!(".Lperry_gcrec{index}"), + ), + ObjectFormat::MachO => ( + MACHO_SECTION.to_string(), + MACHO_RECORDS_SECTION.to_string(), + format!("lperry_gcmap{index}"), + format!("lperry_gcrec{index}"), + ), + ObjectFormat::Coff => ( + format!("{COFF_SECTION},associative,{}", function.symbol), + format!("{COFF_RECORDS_SECTION},associative,{}", function.symbol), + format!(".Lperry_gcmap{index}"), + format!(".Lperry_gcrec{index}"), + ), + }; + out.push_str(&format!("\t.section\t{rec_section}\n")); + out.push_str(&format!("{rec_label}:\n")); for record in &function.records { out.push_str(&format!("\t.long\t{}\n", record.instruction_offset)); } - } - for chunk in stream.chunks(32) { - let bytes: Vec = chunk.iter().map(|b| b.to_string()).collect(); - out.push_str(&format!("\t.byte\t{}\n", bytes.join(","))); - } - - out.push_str(&format!("\t.section\t{directory_section}\n")); - out.push_str("\t.p2align\t3\n"); - out.push_str(&format!("{GC_MAP_LABEL}:\n")); - out.push_str(&format!( - "\t.ascii\t\"{}\"\n", - std::str::from_utf8(GC_MAP_MAGIC).expect("magic is ASCII") - )); - out.push_str(&format!("\t.byte\t{GC_MAP_VERSION}\n")); - out.push_str("\t.byte\t0\n"); - // Header flags: none are defined. - out.push_str("\t.short\t0\n"); - out.push_str(&format!("\t.long\t{}\n", functions.len())); - // Stride to the next directory: the directories are all this section holds. - out.push_str(&format!("\t.long\t{directory_len}\n")); - // A link-time difference, like the function fields: no relocation survives - // into the image, on any format. - out.push_str(&format!("\t.long\t{GC_REC_LABEL}-{GC_MAP_LABEL}\n")); - out.push_str(&format!("\t.long\t{records_len}\n")); - out.push_str(&format!("\t.long\t{record_total}\n")); - out.push_str("\t.long\t0\n"); - let mut first_record = 0usize; - for (function, stream_offset) in functions.iter().zip(&compact.function_offsets) { - // v6: a link-time difference against this blob's own label, never an - // absolute address — that is what keeps the section free of dynamic - // relocations. + for chunk in own_stream.chunks(32) { + let bytes: Vec = chunk.iter().map(|b| b.to_string()).collect(); + out.push_str(&format!("\t.byte\t{}\n", bytes.join(","))); + } + let records_len = function.records.len() * 4 + own_stream.len(); + out.push_str(&format!("\t.section\t{map_section}\n")); + out.push_str("\t.p2align\t2\n"); + out.push_str(&format!("{map_label}:\n")); + // v6: link-time differences, never absolute addresses — that is what + // keeps the section free of dynamic relocations. let entry = entry_labels .get(&function.symbol) .unwrap_or(&function.symbol); - out.push_str(&format!("\t.long\t{entry}-{GC_MAP_LABEL}\n")); + out.push_str(&format!("\t.long\t{entry}-{map_label}\n")); out.push_str(&format!("\t.long\t{}\n", function.stack_size as u32)); - out.push_str(&format!("\t.long\t{}\n", function.records.len())); - out.push_str(&format!("\t.long\t{first_record}\n")); - out.push_str(&format!("\t.long\t{stream_offset}\n")); - first_record += function.records.len(); + out.push_str(&format!( + "\t.long\t{}\n", + function.records.len() as u32 | u32::from(GC_MAP_VERSION) << 24 + )); + out.push_str(&format!("\t.long\t{rec_label}-{map_label}\n")); + out.push_str(&format!("\t.long\t{records_len}\n")); } out } -/// Byte length of one v8 directory: header plus one entry per function. -fn directory_len(functions: &[FunctionMap]) -> usize { - DIRECTORY_HEADER_BYTES + functions.len() * DIRECTORY_ENTRY_BYTES -} - -/// Byte length of one v8 records blob: instruction offsets, then the stream. -fn records_len(functions: &[FunctionMap], stream_len: usize) -> usize { - let record_total: usize = functions.iter().map(|f| f.records.len()).sum(); - record_total * 4 + stream_len -} - -/// Both halves of one module's map. +/// Byte length of every v9 blob of one module: an entry and a records blob +/// per function. fn compact_len(functions: &[FunctionMap], stream_len: usize) -> usize { - directory_len(functions) + records_len(functions, stream_len) + let record_total: usize = functions.iter().map(|f| f.records.len()).sum(); + functions.len() * DIRECTORY_ENTRY_BYTES + record_total * 4 + stream_len } /// Statistics for the caller to log — a compaction that silently did nothing @@ -1433,13 +1397,11 @@ fn compact_stack_map_asm(asm: &str, target: &str) -> Result String { /// offset** and a `.word` **32-bit `Offset` field per location**, which is /// what makes the width of `.word` load-bearing rather than cosmetic. #[test] -fn every_target_emits_the_same_blob_with_a_relative_function_field() { - // v6 (#11508): the function field is `fn - blob_start`, a 32-bit link-time +fn every_target_emits_the_same_entry_with_a_relative_function_field() { + // v6 (#11508): the function field is `fn - entry`, a 32-bit link-time // constant, on every target. An absolute `.quad fn` would put one dynamic // relocation per function into the linked image (applied eagerly at load, // dirtying the table's pages) and force the section writable. - let mut blobs = Vec::new(); + let mut entries = Vec::new(); for target in [ "arm64-apple-macosx15.0.0", "arm64_32-apple-watchos", @@ -102,124 +107,120 @@ fn every_target_emits_the_same_blob_with_a_relative_function_field() { .unwrap_or_else(|e| panic!("{target} must parse: {e}")) .unwrap_or_else(|| panic!("{target} must be rewritten")); // ELF names the entry by a local label (see - // `elf_names_each_function_by_a_local_entry_label`). + // `elf_names_each_function_by_a_local_entry_label`); Mach-O labels are + // linker-private `l` symbols, each its own atom. let entry = if target.contains("linux") { ".Lperry_gcmap_fn0" } else { "_probe_fn" }; + let (map, rec) = if target.contains("apple") { + ("lperry_gcmap0", "lperry_gcrec0") + } else { + (".Lperry_gcmap0", ".Lperry_gcrec0") + }; assert!( - out.contains(&format!("\t.long\t{entry}-_perry_gc_map\n")), - "{target}: the function field must be relative to the blob:\n{out}" + out.contains(&format!("\t.long\t{entry}-{map}\n")), + "{target}: the function field must be relative to the entry:\n{out}" ); assert!( !out.contains(".quad"), "{target}: an absolute address is a dynamic relocation:\n{out}" ); + assert_eq!(GC_MAP_VERSION, 9); assert!( - out.contains(&format!("\t.byte\t{GC_MAP_VERSION}\n")), - "the emitted blob must declare the version the runtime expects:\n{out}" + out.contains(&format!("\t.long\t{}\n", 1 | u32::from(GC_MAP_VERSION) << 24)), + "the entry must carry its record count and the version the runtime expects:\n{out}" ); - assert_eq!(GC_MAP_VERSION, 8); - // v8 directory: 32-byte header + one 20-byte function entry; records: - // one 4-byte instruction offset + a 3-byte root stream, on ILP32 and - // LP64 alike. - assert_eq!(stats.compact_bytes, 32 + 20 + 4 + 3, "{target}"); + // v9: one 20-byte entry; records: one 4-byte instruction offset + a + // 3-byte root stream, on ILP32 and LP64 alike. + assert_eq!(stats.compact_bytes, 20 + 4 + 3, "{target}"); assert!( - out.contains("\t.long\t_perry_gc_rec-_perry_gc_map\n"), - "{target}: the directory names its records by a link-time offset:\n{out}" + out.contains(&format!("\t.long\t{rec}-{map}\n")), + "{target}: the entry names its records by a link-time offset:\n{out}" ); - // Everything after the section directive is format-independent. - let blob = &out[out.find("_perry_gc_map:").expect("label")..]; - blobs.push(blob.replace(entry, "ENTRY")); + assert!(out.contains("\t.long\t7\n"), "{target}: records length:\n{out}"); + // Everything after the entry label is format-independent. + let blob = &out[out.find(&format!("{map}:")).expect("label")..]; + entries.push(blob.replace(entry, "ENTRY").replace(map, "MAP").replace(rec, "REC")); } assert!( - blobs.windows(2).all(|pair| pair[0] == pair[1]), - "{blobs:#?}" + entries.windows(2).all(|pair| pair[0] == pair[1]), + "{entries:#?}" ); } #[test] fn the_map_section_is_read_only_where_the_format_allows() { // Nothing in a v6 blob needs a load-time fixup, so ELF drops SHF_WRITE - // (keeping SHF_GNU_RETAIN, or `--gc-sections` discards the map) and COFF - // is `dr`, not `dw`. + // and COFF is `dr`, not `dw`. v9: nothing is retained by itself; ELF ties + // each section to its function's (`o`), COFF makes it associative. let (elf, _) = compact_stack_map_asm(&x86_64_elf_sample_asm(""), "x86_64-unknown-linux-gnu") .expect("parses") .expect("rewritten"); assert!( - elf.contains("\t.section\t.perry_gcmap,\"aR\",@progbits\n"), + elf.contains("\t.section\t.perry_gcmap,\"ao\",@progbits,probe_fn,unique,1073741824\n"), "{elf}" ); assert!( - elf.contains("\t.section\t.perry_gcrec,\"aR\",@progbits\n"), + elf.contains("\t.section\t.perry_gcrec,\"ao\",@progbits,probe_fn,unique,1073741825\n"), "{elf}" ); let (coff, _) = compact_stack_map_asm(&sample_asm(), "x86_64-pc-windows-msvc") .expect("parses") .expect("rewritten"); - assert!(coff.contains("\t.section\t.pgcmap,\"dr\"\n"), "{coff}"); - assert!(coff.contains("\t.section\t.pgcrec,\"dr\"\n"), "{coff}"); + assert!( + coff.contains("\t.section\t.pgcmap$m,\"dr\",associative,_probe_fn\n"), + "{coff}" + ); + assert!( + coff.contains("\t.section\t.pgcrec$m,\"dr\",associative,_probe_fn\n"), + "{coff}" + ); + assert!(!elf.contains("\"aR\"") && !coff.contains(".no_dead_strip")); } -/// v8: the directory (what the runtime reads to build its index) and the +/// v8/v9: the entries (what the runtime reads to build its index) and the /// records (what a collection reads per frame) are separate sections, so the -/// linker gathers every module's directory into one contiguous run and the -/// records stay untouched until a walk names them. Emitting the records -/// inside the directory section would interleave them again — every -/// directory page would fault in its neighbours' records. +/// linker gathers every entry into one contiguous run and the records stay +/// untouched until a walk names them. #[test] -fn records_live_in_their_own_section_after_no_directory_bytes() { +fn records_live_in_their_own_section_after_no_entry_bytes() { let (out, _) = compact_stack_map_asm(&x86_64_elf_sample_asm(""), "x86_64-unknown-linux-gnu") .expect("parses") .expect("rewritten"); - let rec = out.find("_perry_gc_rec:").expect("records label"); - let dir = out.find("_perry_gc_map:").expect("directory label"); + let rec = out.find(".Lperry_gcrec0:").expect("records label"); + let map = out.find(".Lperry_gcmap0:").expect("entry label"); let rec_section = out[..rec].rfind("\t.section\t").expect("records section"); - let dir_section = out[..dir].rfind("\t.section\t").expect("directory section"); + let map_section = out[..map].rfind("\t.section\t").expect("entry section"); assert!( out[rec_section..].starts_with("\t.section\t.perry_gcrec"), "{out}" ); assert!( - out[dir_section..].starts_with("\t.section\t.perry_gcmap"), + out[map_section..].starts_with("\t.section\t.perry_gcmap"), "{out}" ); - // Nothing but directory words between the directory label and the next - // section switch: 8 header directives + 5 per function. - let tail = &out[dir..]; + // Nothing but the entry's five words between its label and the next + // section switch. + let tail = &out[map..]; let end = tail[1..].find("\t.section").map_or(tail.len(), |i| i + 1); - let words = tail[..end] - .lines() - .filter(|l| { - l.starts_with("\t.long") - || l.starts_with("\t.short") - || l.starts_with("\t.byte") - || l.starts_with("\t.ascii") - }) - .count(); - assert_eq!(words, 1 + 2 + 1 + 6 + 5, "{}", &tail[..end]); - // The header and the one entry, as the runtime reads them. let longs: Vec<&str> = tail[..end] .lines() .filter_map(|l| l.strip_prefix("\t.long\t")) .collect(); - assert_eq!(longs[0], "1", "function count"); - assert_eq!( - longs[1], "52", - "directory length: 32-byte header + one 20-byte entry" - ); - assert_eq!(longs[2], "_perry_gc_rec-_perry_gc_map", "records offset"); - assert_eq!( - longs[4], longs[8], - "record total == the only function's record count" - ); - assert_eq!(longs[5], "0", "reserved"); + let words = tail[..end] + .lines() + .filter(|l| l.starts_with("\t.long") || l.starts_with("\t.short") || l.starts_with("\t.byte")) + .count(); + assert_eq!(words, 5, "{}", &tail[..end]); + assert_eq!(longs[0], ".Lperry_gcmap_fn0-.Lperry_gcmap0", "function field"); assert_eq!( - longs[9], "0", - "the first function's records start at record 0" + longs[2].parse::().unwrap() >> 24, + u32::from(GC_MAP_VERSION), + "the version rides in the record count's top byte" ); - assert_eq!(longs[10], "0", "and at stream offset 0"); + assert_eq!(longs[3], ".Lperry_gcrec0-.Lperry_gcmap0", "records offset"); } /// A Linux plugin is linked `cc -shared` without `-Bsymbolic`, so an exported @@ -237,7 +238,7 @@ fn elf_names_each_function_by_a_local_entry_label() { "the label must sit at the function's entry, right after its definition:\n{out}" ); assert!( - out.contains("\t.long\t.Lperry_gcmap_fn0-_perry_gc_map\n"), + out.contains("\t.long\t.Lperry_gcmap_fn0-.Lperry_gcmap0\n"), "{out}" ); assert!( @@ -348,11 +349,11 @@ fn every_apple_target_is_accepted() { .unwrap_or_else(|e| panic!("{target} must parse: {e}")) .unwrap_or_else(|| panic!("{target} must be rewritten")); assert!( - out.contains("\t.long\t_probe_fn-_perry_gc_map\n"), - "{target} must emit a blob-relative function field:\n{out}" + out.contains("\t.long\t_probe_fn-lperry_gcmap0\n"), + "{target} must emit an entry-relative function field:\n{out}" ); assert!( - out.contains("__PERRY_GCMAP,__perry_gcmap"), + out.contains("__PERRY_GCMAP,__perry_gcmap,regular,live_support"), "{target} must emit the section the runtime looks up:\n{out}" ); } @@ -402,8 +403,8 @@ fn aarch64_elf_word_directives_decode_to_the_right_root() { assert_eq!(stats.records, 1); // Four locations in, one root out: the three preamble constants drop. assert_eq!(stats.roots, 1, "the SP-relative root must survive"); - assert!(out.contains("_perry_gc_map:")); - assert!(out.contains(".long\t.Lperry_gcmap_fn0-_perry_gc_map")); + assert!(out.contains(".Lperry_gcmap0:")); + assert!(out.contains(".long\t.Lperry_gcmap_fn0-.Lperry_gcmap0")); assert!(!out.contains("llvm_stackmaps")); } @@ -456,14 +457,15 @@ fn compacts_and_keeps_only_real_roots() { stats.compact_bytes, stats.original_bytes ); - assert!(out.contains("_perry_gc_map:")); - assert!(out.contains(".long\t_probe_fn-_perry_gc_map")); + assert!(out.contains("lperry_gcmap0:")); + assert!(out.contains(".long\t_probe_fn-lperry_gcmap0")); // The old section must be gone, and nothing may still name its label. assert!(!out.contains("__llvm_stackmaps")); assert!(!out.contains("__LLVM_StackMaps")); - // The dead-strip guard must survive, retargeted. - assert!(out.contains(".no_dead_strip\t_perry_gc_map")); - assert!(out.contains(".no_dead_strip\t_perry_gc_rec")); + // v9: nothing is kept by itself; `live_support` keeps each entry with + // its function, so no dead-strip guard is emitted. + assert!(!out.contains(".no_dead_strip")); + assert!(out.contains("__PERRY_GCMAP,__perry_gcmap,regular,live_support")); // Guard the -O3 ELF shapes that broke the aarch64-linux arm. Both are // GNU-as symbol assignments -- zero bytes, no leading directive -- so @@ -597,7 +599,7 @@ fn elf_personality_slot_attributes_survive_the_rewrite() { // Order: map, then the attributes, then the slot's own section — the // layout LLVM printed, so the assembler sees exactly what it would // have seen without the rewrite. - let map = out.find("_perry_gc_map:").expect("compact map label"); + let map = out.find("perry_gcmap0:").expect("compact map label"); let hidden = out.find("\t.hidden\tDW.ref").expect("hidden directive"); let weak = out.find("\t.weak\tDW.ref").expect("weak directive"); let section = out @@ -623,7 +625,7 @@ fn the_map_labels_own_attributes_are_not_carried() { .expect("an x86-64 ELF stack map must parse") .expect("an x86-64 ELF stack map must be rewritten"); assert!(!out.contains("__LLVM_StackMaps"), "{out}"); - assert!(out.contains("_perry_gc_map:"), "{out}"); + assert!(out.contains("perry_gcmap0:"), "{out}"); } /// The -O3 ELF absolute-symbol aliases land inside the block too. They @@ -679,7 +681,7 @@ fn macho_alias_attributes_after_the_map_are_carried() { let assign = out .find("\n_m_ts_.fnname.0 = _perry_image_literal_0_66+8+20\n") .expect("the alias assignment must be re-emitted"); - let map = out.find("_perry_gc_map:").expect("compact map label"); + let map = out.find("perry_gcmap0:").expect("compact map label"); let tail = out.find(".subsections_via_symbols").expect("epilogue"); assert!(map < alt && alt < assign && assign < tail, "{out}"); } @@ -785,7 +787,7 @@ fn encodes_a_foreign_register_base() { .expect("block parses") .expect("a foreign base must still encode"); assert_eq!(stats.roots, 1); - assert!(out.contains("_perry_gc_map:")); + assert!(out.contains("perry_gcmap0:")); } /// #7803: derived (interior) slots survive the encode/decode round trip, diff --git a/crates/perry-codegen/src/inprocess/function_layout.rs b/crates/perry-codegen/src/inprocess/function_layout.rs index 15f788d3d91..7bb4e902e4f 100644 --- a/crates/perry-codegen/src/inprocess/function_layout.rs +++ b/crates/perry-codegen/src/inprocess/function_layout.rs @@ -10,6 +10,72 @@ use inkwell::AddressSpace; use crate::function_order::{FunctionLayout, FunctionOrder, RECORD_HOOK}; pub(crate) fn apply(module: &Module<'_>, layout: &FunctionLayout, effective_target: &str) { + apply_order(module, layout, effective_target); + split_js_text(module); + if crate::codegen::target_os_of(effective_target) == Some("windows") { + tie_coff_records(module); + } +} + +/// COFF: put each function that has a metadata record (`crate::fn_record`) +/// in a COMDAT it keys, and its record and its info in the same COMDAT, so +/// both are associative sections of the body's: `/OPT:REF` keeps them exactly +/// when it keeps the body (or, for the info, when live code names it; the +/// info names the body, so that keeps the body too). The info matters as much +/// as the record: a private global cannot lead a COFF COMDAT, so without this +/// every info of the unit shares one `.rdata` section, and one live info keeps +/// every body the others name. A body already in a COMDAT keeps that one (the +/// two then live with the COMDAT's leader, as the body does). Done here, on +/// the final module, because a COMDAT names a global the unit defines. +fn tie_coff_records(module: &Module<'_>) { + use inkwell::comdat::ComdatSelectionKind; + let mut global = module.get_first_global(); + while let Some(record) = global { + global = record.get_next_global(); + let name = record.get_name().to_string_lossy().into_owned(); + let Some(body) = crate::fn_record::body_of_record_name(&name) else { + continue; + }; + let Some(function) = module.get_function(body) else { + continue; + }; + let body_global = function.as_global_value(); + let comdat = body_global.get_comdat().unwrap_or_else(|| { + let comdat = module.get_or_insert_comdat(body); + comdat.set_selection_kind(ComdatSelectionKind::NoDuplicates); + body_global.set_comdat(comdat); + comdat + }); + record.set_comdat(comdat); + if let Some(info) = module.get_global(&crate::fn_info::info_symbol(body)) { + if info.get_comdat().is_none() && !info.is_declaration() { + info.set_comdat(comdat); + } + } + } +} + +/// Give every JS body still in the shared JS text section its own input +/// section, `perry_js_text.fn.`: LLVM keeps one section for an +/// explicit section name even under function sections, and the link drops +/// unreferenced code only a whole input section at a time. The link rule +/// (`JS_TEXT_LINK_RULE`) gathers them back into the `perry_js_text` output +/// section. Bodies the order list placed already have a section of their own. +fn split_js_text(module: &Module<'_>) { + let js_text = crate::codegen::helpers::JS_TEXT_SECTION_NAME; + for f in defined_functions(module) { + let global = f.as_global_value(); + if global.get_section().is_some_and(|s| s.to_bytes() == js_text.as_bytes()) { + let name = f.get_name().to_string_lossy(); + global.set_section(Some(&format!( + "{}{name}", + crate::function_order::JS_FN_SECTION_PREFIX + ))); + } + } +} + +fn apply_order(module: &Module<'_>, layout: &FunctionLayout, effective_target: &str) { match layout { FunctionLayout::Default => {} FunctionLayout::Record => record_first_calls(module), diff --git a/crates/perry-codegen/src/inprocess/optimize_emit.rs b/crates/perry-codegen/src/inprocess/optimize_emit.rs index ff99d88de53..4557dccbcec 100644 --- a/crates/perry-codegen/src/inprocess/optimize_emit.rs +++ b/crates/perry-codegen/src/inprocess/optimize_emit.rs @@ -85,6 +85,18 @@ pub(super) fn optimize_and_emit( perry_llvm_enable_wasm_eh(tm.as_mut_ptr().cast()); } } + // One input section per function and per global (`-ffunction-sections + // -fdata-sections`), so the link drops every one nothing references (DCE + // layer 0). wasm-ld already links per function and keeps its own data + // layout. + if !effective_target.starts_with("wasm32") { + extern "C" { + fn perry_llvm_unique_sections(machine: *mut core::ffi::c_void); + } + unsafe { + perry_llvm_unique_sections(tm.as_mut_ptr().cast()); + } + } // Same trust order as the subprocess path: `-target` wins over whatever // triple the module text states, and the module optimizes under the diff --git a/crates/perry-codegen/src/inprocess/unique_sections.cpp b/crates/perry-codegen/src/inprocess/unique_sections.cpp new file mode 100644 index 00000000000..be53aeca6d5 --- /dev/null +++ b/crates/perry-codegen/src/inprocess/unique_sections.cpp @@ -0,0 +1,15 @@ +// LLVM's C API exposes no function-sections or data-sections option. Keep +// this tiny adapter tied to the same LLVM headers as llvm-sys; it changes +// only this machine (two fields of its options), so no global flag is set. +// +// One input section per function and per global is what lets the link drop +// each one nothing references (DCE layer 0): with the defaults, a module's +// functions share `.text` and its constants share `.data.rel.ro`, so one live +// symbol keeps every function the rest of its section names. +#include "llvm/Target/TargetMachine.h" + +extern "C" void perry_llvm_unique_sections(void *machine) { + auto *target = static_cast(machine); + target->Options.FunctionSections = true; + target->Options.DataSections = true; +} diff --git a/crates/perry-codegen/src/lib.rs b/crates/perry-codegen/src/lib.rs index 4c846c5e7c5..5f1ce11795c 100644 --- a/crates/perry-codegen/src/lib.rs +++ b/crates/perry-codegen/src/lib.rs @@ -22,6 +22,7 @@ pub mod ext_registry; pub mod facts_consumed; pub mod fallback_diag; pub(crate) mod fn_info; +pub(crate) mod fn_record; pub mod function; pub mod function_order; pub(crate) mod gc_call_effects; diff --git a/crates/perry-codegen/src/module.rs b/crates/perry-codegen/src/module.rs index 00d5cce9071..fbea21bee39 100644 --- a/crates/perry-codegen/src/module.rs +++ b/crates/perry-codegen/src/module.rs @@ -617,19 +617,39 @@ impl LlModule { } } } - let entries = format!("{owner}_entries"); - let values: Vec<_> = bodies.iter().enumerate().map(|(ordinal, body)| { + // One record per body, tied to the body's own section: the link keeps + // it exactly when it keeps the body (`fn_record_line`). No table names + // the bodies, so nothing here roots a function. + let format = crate::fn_record::RecordFormat::of(&self.target_triple); + for (ordinal, body) in bodies.iter().enumerate() { self.note_fn_info(body, |f| f.metadata = Some((owner.clone(), ordinal as u32))); - format!("i32 trunc (i64 sub (i64 ptrtoint (ptr @{} to i64), i64 ptrtoint (ptr @{entries} to i64)) to i32)", crate::fn_info::info_symbol(body)) - }).collect(); + self.add_raw_global(crate::fn_record::record_line(format, body)); + } + if !bodies.is_empty() { + for (name, line) in crate::fn_record::image_lines(format) { + if is_external_global(&line) { + self.add_raw_global(line); + } else { + self.add_coalesced_global(&name, line); + } + } + } + // One writable word per image; no per-function mutable allocation. + // `count` bounds the ordinals assigned here (the link may drop some); + // the last field is the self-relative offset to the image's records + // (0: none), so registration passes nothing more than before. + let bounds = if bodies.is_empty() { + "i32 0".to_string() + } else { + format!( + "i32 trunc (i64 sub (i64 ptrtoint (ptr @{} to i64), i64 ptrtoint (ptr getelementptr inbounds ({{ i32, i32, ptr, i32 }}, ptr @{owner}, i32 0, i32 3) to i64)) to i32)", + crate::fn_record::BOUNDS_SYMBOL + ) + }; self.add_raw_global(format!( - "@{entries} = private constant [{} x i32] [{}], align 4", - bodies.len(), - values.join(", ") + "@{owner} = internal global {{ i32, i32, ptr, i32 }} {{ i32 {}, i32 0, ptr null, {bounds} }}", + bodies.len() )); - // One writable word per image; no per-function mutable allocation. - // The first two fields are immutable descriptor operands. - self.add_raw_global(format!("@{owner} = internal global {{ ptr, i32, i32, ptr }} {{ ptr @{entries}, i32 {}, i32 0, ptr null }}", bodies.len())); } /// A defined function by symbol name. @@ -1080,6 +1100,7 @@ impl LlModule { self.push_global_definition(&mut ir, g); ir.push('\n'); } + crate::fn_record::push_compiler_used(&mut ir, self.globals.iter().map(String::as_str)); ir.push('\n'); let funcs = self.deduped_function_refs(); let defined: HashSet<&str> = funcs.iter().map(|f| f.name.as_str()).collect(); @@ -1129,6 +1150,7 @@ impl LlModule { self.push_global_definition(&mut ir, g); ir.push('\n'); } + crate::fn_record::push_compiler_used(&mut ir, self.globals.iter().map(String::as_str)); ir.push('\n'); let funcs = self.deduped_function_refs(); @@ -1558,6 +1580,33 @@ impl LlModule { } } } + // A function record (`crate::fn_record`) is tied to its body's own + // section, which exists only in the body's object: it is owned by the + // body's unit, and what it names (the body's info) is needed there. + let body_unit: HashMap<&str, usize> = bucket_names + .iter() + .enumerate() + .flat_map(|(bi, names)| names.iter().map(move |n| (n.as_str(), bi))) + .collect(); + for gi in 0..all_globals.len() { + let Some(&bi) = crate::fn_record::record_body(all_globals[gi]) + .and_then(|body| body_unit.get(body)) + else { + continue; + }; + global_owners[gi] = bi; + let mut work = vec![gi]; + bucket_needs[bi].insert(gi); + while let Some(gj) = work.pop() { + for nm in &global_refs[gj] { + if let Some(&next) = global_index.get(nm.as_str()) { + if bucket_needs[bi].insert(next) { + work.push(next); + } + } + } + } + } // #10152: otherwise-unreferenced globals are retained in unit 0, but // their initializers were absent from the function-rooted closure // above. An orphaned string dispatch descriptor can still name bytes @@ -1633,12 +1682,23 @@ impl LlModule { pre.push_str("module asm \".no_dead_strip __LLVM_StackMaps\"\n\n"); } + let mut unit_records: Vec<&str> = Vec::new(); for (gi, def) in all_globals.iter().enumerate() { let referenced = bucket_needs[bi].contains(&gi); // Unreferenced globals (anchors, `llvm.*`, appending lists) // keep a home in unit 0 so nothing is lost. let owns = global_owners[gi] == bi; let replicated = replicate_globals && alias_target(def).is_none(); + if crate::fn_record::record_body(def).is_some() { + // Exactly as written, in its body's unit only: nothing + // else names a record. + if owns { + self.push_global_definition(&mut pre, def); + pre.push('\n'); + unit_records.push(def); + } + continue; + } if (replicated && referenced) || owns { if replicated { if !has_local_linkage(def) { @@ -1665,6 +1725,7 @@ impl LlModule { pre.push('\n'); } } + crate::fn_record::push_compiler_used(&mut pre, unit_records.into_iter()); pre.push('\n'); // Declares for everything this unit REFERENCES but does not diff --git a/crates/perry-codegen/src/native_emit.rs b/crates/perry-codegen/src/native_emit.rs index 450c9da9fa3..3339e5b3938 100644 --- a/crates/perry-codegen/src/native_emit.rs +++ b/crates/perry-codegen/src/native_emit.rs @@ -1309,6 +1309,17 @@ mod tests { } } + /// v9 keeps a function's records in a section of their own. + fn compact_gc_records_section_name() -> &'static [u8] { + if cfg!(target_os = "macos") { + b"__perry_gcrec" + } else if cfg!(target_os = "windows") { + b".pgcrec" + } else { + b".perry_gcrec" + } + } + fn object_contains(object: &[u8], needle: &[u8]) -> bool { object.windows(needle.len()).any(|window| window == needle) } @@ -1320,8 +1331,8 @@ mod tests { "{label} object has no compact GC-map section" ); assert!( - object_contains(object, b"PGCM"), - "{label} compact GC-map section has no map payload" + object_contains(object, compact_gc_records_section_name()), + "{label} compact GC-map section has no records payload" ); } @@ -1331,7 +1342,7 @@ mod tests { "{label} shadow-stack object unexpectedly has a compact GC-map section" ); assert!( - !object_contains(object, b"PGCM"), + !object_contains(object, compact_gc_records_section_name()), "{label} shadow-stack object unexpectedly has a compact GC-map payload" ); } diff --git a/crates/perry-runtime/src/builtins/fn_metadata.rs b/crates/perry-runtime/src/builtins/fn_metadata.rs index 0463fab3a74..bffc2c341b9 100644 --- a/crates/perry-runtime/src/builtins/fn_metadata.rs +++ b/crates/perry-runtime/src/builtins/fn_metadata.rs @@ -77,7 +77,7 @@ fn with_registered_name( read: impl FnOnce(&str) -> R, ) -> Option { if let Some(record) = Record::for_info(info) { - record.with_name(read) + record.with_name(info, read) } else { info_name_bytes(info) .and_then(decode_registered_bytes) @@ -93,18 +93,18 @@ pub(crate) fn registered_name_string(info: &crate::closure::JsFunctionInfo) -> O /// precedence. Input need only outlive this call. No code-pointer token or /// address routing is accepted. pub(crate) fn register_function_name(info: &crate::closure::JsFunctionInfo, bytes: &[u8]) -> bool { - Record::for_info(info).is_some_and(|record| record.write_name(bytes, false)) + Record::for_info(info).is_some_and(|record| record.write_name(info, bytes, false)) } pub(crate) fn register_function_name_if_absent(info: &crate::closure::JsFunctionInfo, name: &str) { if let Some(record) = Record::for_info(info) { - record.write_name(name.as_bytes(), true); + record.write_name(info, name.as_bytes(), true); } } pub(crate) fn function_is_non_strict_ordinary_for_info( info: &crate::closure::JsFunctionInfo, ) -> bool { Record::for_info(info) - .map(|r| r.ordinary(info.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0)) + .map(|r| r.ordinary(info)) .unwrap_or(info.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0) } /// The image string a compiled body's name range is the text of @@ -267,7 +267,7 @@ pub fn function_source_for_info_or_native(info: &crate::closure::JsFunctionInfo) if let Some(source) = retained_source_for_info(info) { return source; } - if let Some(source) = Record::for_info(info).and_then(|r| r.source()) { + if let Some(source) = Record::for_info(info).and_then(|r| r.source(info)) { return source; } let name = registered_name_string(info).unwrap_or_default(); diff --git a/crates/perry-runtime/src/builtins/fn_metadata_owner.rs b/crates/perry-runtime/src/builtins/fn_metadata_owner.rs index 3140da87bfe..b71a8ea5998 100644 --- a/crates/perry-runtime/src/builtins/fn_metadata_owner.rs +++ b/crates/perry-runtime/src/builtins/fn_metadata_owner.rs @@ -1,8 +1,20 @@ -//! Canonical metadata ownership. Startup publishes an image descriptor, not -//! function-address keys. Closure operations index that owner directly. -use crate::codegen_abi::{FunctionMetadataIdentity, FunctionMetadataOwner, JsFunctionInfo}; -use std::sync::{atomic::Ordering, Arc, Mutex, OnceLock}; - +//! Canonical metadata ownership. Startup publishes an owner identity and its +//! image's function records, not function-address keys. Closure operations +//! read the info they hold; only the rare by-address paths (stack frame names, +//! the source of raw method code) walk an image's records. +//! +//! Records (`perry_codegen::fn_record`) live in a linker-owned section, one +//! per body that publishes an info, tied to the body's own input section: the +//! link keeps a record exactly when it keeps the body, so the section between +//! its bounds lists the surviving bodies and nothing roots the others. +use crate::codegen_abi::{ + FunctionMetadataIdentity, FunctionMetadataOwner, FunctionRecordBounds, JsFunctionInfo, + FUNCTION_RECORD_BYTES, +}; +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, OnceLock, +}; #[derive(Default)] struct Overrides { name: Option>, @@ -16,8 +28,12 @@ struct CopiedEntry { } /// Payload belongs to its descriptor. It contains no GC-managed pointers. /// Overrides are sparse, ordered by ordinal and allocated only on a write. +/// `copied` (an unloadable image's bytes) has one slot per ordinal; the slot +/// of a body the link dropped stays empty. struct OwnerState { - copied: OnceLock>, + copied: OnceLock]>>, + /// Set once the owner's copies are listed under their image. + listed: std::sync::atomic::AtomicBool, overrides: Mutex>, /// The owner's registration index plus one (0: not registered): the /// order module init published it in, a deterministic owner identity @@ -28,38 +44,55 @@ impl OwnerState { fn new(copied: Option>) -> Self { let storage = OnceLock::new(); if let Some(entries) = copied { - let _ = storage.set(entries); + let _ = storage.set(entries.into_iter().map(OnceLock::from).collect()); } Self { copied: storage, + listed: std::sync::atomic::AtomicBool::new(false), overrides: Mutex::new(Vec::new()), registration: std::sync::atomic::AtomicU32::new(0), } } } +/// One image's records, as registration found them: the linker-owned +/// section of a permanent image, or, for an unloadable one, the owners whose +/// copies were taken from it (its section is never read again). #[derive(Clone, Copy)] -enum RegisteredOwner { - Image(&'static FunctionMetadataOwner), - Copied(&'static OwnerState), -} -fn owners() -> &'static Mutex> { - static OWNERS: OnceLock>> = OnceLock::new(); - crate::once_init::get_or_init(&OWNERS, || Mutex::new(Vec::new())) +enum ImageRecords { + Image(&'static FunctionRecordBounds), + Copied(&'static [&'static OwnerState]), } -fn image_info(owner: &FunctionMetadataOwner, ordinal: usize) -> Option<&JsFunctionInfo> { - if ordinal >= owner.count as usize || owner.entries.is_null() { - return None; - } - // SAFETY: publication validates the descriptor's lifetime; each relative - // entry names a complete info record emitted with this owner. - unsafe { - let delta = owner.entries.add(ordinal).read(); - let address = (owner.entries as usize).wrapping_add_signed(delta as isize); - (address as *const JsFunctionInfo).as_ref() - } +fn images() -> &'static Mutex> { + static IMAGES: OnceLock>> = OnceLock::new(); + crate::once_init::get_or_init(&IMAGES, || Mutex::new(Vec::new())) } -/// The owner's `state` word: null, an `OwnerState` (aligned, low bit clear), +/// The next owner registration index plus one (CKI S2): owners take indices +/// in the order module init registers them. +static NEXT_REGISTRATION: AtomicU32 = AtomicU32::new(1); + +/// The infos of an image's records, in section order. A zero offset is no +/// record (the image sentinel, a section marker, alignment padding). +/// +/// # Safety +/// `bounds` names the records section of a loaded image. +unsafe fn image_infos( + bounds: &FunctionRecordBounds, +) -> impl DoubleEndedIterator { + let start = bounds.start as usize; + let count = (bounds.stop as usize).saturating_sub(start) / FUNCTION_RECORD_BYTES; + (0..count).filter_map(move |index| { + let record = start + index * FUNCTION_RECORD_BYTES; + // SAFETY: inside the section; records are 4-byte aligned. + let delta = unsafe { (record as *const i32).read() }; + if delta == 0 { + return None; + } + let info = record.wrapping_add_signed(delta as isize) as *const JsFunctionInfo; + // SAFETY: a record names the complete info emitted with its body. + unsafe { info.as_ref() } + }) +}/// The owner's `state` word: null, an `OwnerState` (aligned, low bit clear), /// or, while no payload exists, the owner's registration index plus one /// shifted over a set low bit. Registration therefore allocates nothing, and /// the index stays on the owner record (CKI S2). @@ -128,13 +161,14 @@ fn registration(owner: &FunctionMetadataOwner) -> u32 { .map_or(0, |st| st.registration.load(Ordering::Acquire)) } -/// Register one image's canonical owner. All entry ordinals and info ranges -/// already exist. Permanent publication does no address-keyed name work. +/// Register one module's owner and its image's records (the owner names +/// them). Every ordinal and info already exists; permanent publication does +/// no per-function work. /// /// # Safety -/// Owner and its relative entries/infos must be valid for this call and while -/// its function objects remain live. A nonzero `permanent` promises process -/// lifetime; otherwise the canonical owner copies bytes before image unload. +/// `owner` must be valid for this call. A nonzero `permanent` promises +/// process lifetime; otherwise the image's metadata bytes are copied here, +/// before the image can unload. #[no_mangle] pub unsafe extern "C" fn js_register_function_metadata( owner: *const FunctionMetadataOwner, @@ -146,45 +180,65 @@ pub unsafe extern "C" fn js_register_function_metadata( if owner.count == 0 { return; } - let registered = if permanent != 0 { - RegisteredOwner::Image(owner) + // Module init may be repeated by another agent: an owner keeps the index + // it was first given, and the owner record carries it (CKI S2). + if registration(owner) == 0 { + set_registration(owner, NEXT_REGISTRATION.fetch_add(1, Ordering::Relaxed)); + } + let Some(bounds) = owner.record_bounds() else { + return; + }; + let Ok(mut list) = images().lock() else { + return; + }; + // Every module of an image passes the same bounds, so the image is added + // once; images register in runs, so the newest entry is the usual match. + let known = list.iter().rev().any(|item| match item { + ImageRecords::Image(b) => std::ptr::eq(*b, bounds), + ImageRecords::Copied(_) => false, + }); + if known { + return; + } + if permanent != 0 { + list.push(ImageRecords::Image(bounds)); } else { - // A cyclic importer may infer a name before this module publishes. - // Preserve those overrides and still establish copied image custody. + list.push(ImageRecords::Copied(copy_image(bounds))); + } +} +/// An unloadable image: copy every record's bytes into its owner's state +/// now, one slot per ordinal. A cyclic importer may already have written an +/// override; it is preserved. The section is not read again. +/// +/// # Safety +/// `bounds` names the records section of a loaded image. +#[cold] +#[inline(never)] +unsafe fn copy_image(bounds: &FunctionRecordBounds) -> &'static [&'static OwnerState] { + let mut states: Vec<&'static OwnerState> = Vec::new(); + for info in image_infos(bounds) { + let Some(FunctionMetadataIdentity { owner, ordinal }) = info.metadata_identity() else { + continue; + }; + // SAFETY: `metadata_identity` validated the owner pointer. + let owner = &*owner; let owned = state(owner).unwrap_or_else(|| install_state(owner, OwnerState::new(None))); - owned.copied.get_or_init(|| { - (0..owner.count as usize) - .map(|ordinal| { - let info = image_info(owner, ordinal).expect("metadata info entry"); - CopiedEntry { - code: info.code as usize, - name: super::fn_metadata::info_name_bytes(info).map(Arc::from), - source: super::fn_metadata::retained_source_for_info(info) - .map(|s| Arc::from(s.as_bytes())), - ordinary: info.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0, - } - }) - .collect() + let slots = owned + .copied + .get_or_init(|| (0..owner.count).map(|_| OnceLock::new()).collect()); + // An ordinal copied twice keeps its first copy, like its identity. + let _ = slots[ordinal as usize].set(CopiedEntry { + code: info.code as usize, + name: super::fn_metadata::info_name_bytes(info).map(Arc::from), + source: super::fn_metadata::retained_source_for_info(info) + .map(|s| Arc::from(s.as_bytes())), + ordinary: info.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0, }); - RegisteredOwner::Copied(owned) - }; - if let Ok(mut list) = owners().lock() { - // Module init may be repeated by another agent. Compare owners only; - // no code address participates and no per-function entry is inserted. - let index = match list.iter().position(|item| match (item, registered) { - (RegisteredOwner::Image(a), RegisteredOwner::Image(b)) => std::ptr::eq(*a, b), - (RegisteredOwner::Copied(a), RegisteredOwner::Copied(b)) => std::ptr::eq(*a, b), - _ => false, - }) { - Some(index) => index, - None => { - list.push(registered); - list.len() - 1 - } - }; - // The owner record carries its own index (CKI S2). - set_registration(owner, index as u32 + 1); + if !owned.listed.swap(true, Ordering::AcqRel) { + states.push(owned); + } } + Box::leak(states.into_boxed_slice()) } /// A ConstFn body's word in the shape-identity fold (CKI S2): its owner's @@ -229,6 +283,8 @@ pub(crate) fn test_registered_infos(count: usize) -> Vec { } /// A borrowed canonical record. The info's flag licenses its owner tail. +/// Two words, so it travels in registers; the methods that read the image's +/// bytes take the info the record was made from. #[derive(Clone, Copy)] pub(super) struct Record<'a> { owner: &'a FunctionMetadataOwner, @@ -242,13 +298,14 @@ impl<'a> Record<'a> { ordinal, }) } - fn image_info(self) -> Option<&'a JsFunctionInfo> { - image_info(self.owner, self.ordinal as usize) - } fn copied(self) -> Option<&'static CopiedEntry> { - state(self.owner)?.copied.get()?.get(self.ordinal as usize) + state(self.owner)?.copied.get()?.get(self.ordinal as usize)?.get() } - pub(super) fn with_name(self, read: impl FnOnce(&str) -> R) -> Option { + pub(super) fn with_name( + self, + info: &JsFunctionInfo, + read: impl FnOnce(&str) -> R, + ) -> Option { if let Some(state) = state(self.owner) { if let Ok(slots) = state.overrides.lock() { if let Ok(index) = slots.binary_search_by_key(&self.ordinal, |s| s.0) { @@ -266,15 +323,14 @@ impl<'a> Record<'a> { let bytes = if let Some(copy) = self.copied() { copy.name.as_deref() } else { - self.image_info() - .and_then(super::fn_metadata::info_name_bytes) + super::fn_metadata::info_name_bytes(info) }; bytes .and_then(super::fn_metadata::decode_registered_bytes) .map(read) } - pub(super) fn write_name(self, bytes: &[u8], infer: bool) -> bool { - if bytes.is_empty() || (infer && self.with_name(|_| ()).is_some()) { + pub(super) fn write_name(self, info: &JsFunctionInfo, bytes: &[u8], infer: bool) -> bool { + if bytes.is_empty() || (infer && self.with_name(info, |_| ()).is_some()) { return false; } let state = @@ -301,7 +357,7 @@ impl<'a> Record<'a> { false } } - pub(super) fn source(self) -> Option { + pub(super) fn source(self, info: &JsFunctionInfo) -> Option { if let Some(state) = state(self.owner) { if let Ok(slots) = state.overrides.lock() { if let Ok(index) = slots.binary_search_by_key(&self.ordinal, |s| s.0) { @@ -323,9 +379,9 @@ impl<'a> Record<'a> { .filter(|s| !s.is_empty()) .map(str::to_owned); } - super::fn_metadata::retained_source_for_info(self.image_info()?) + super::fn_metadata::retained_source_for_info(info) } - pub(super) fn ordinary(self, fallback: bool) -> bool { + pub(super) fn ordinary(self, info: &JsFunctionInfo) -> bool { if let Some(state) = state(self.owner) { if let Ok(slots) = state.overrides.lock() { if let Ok(index) = slots.binary_search_by_key(&self.ordinal, |s| s.0) { @@ -335,11 +391,10 @@ impl<'a> Record<'a> { } } } - self.copied().map(|e| e.ordinary).unwrap_or_else(|| { - self.image_info() - .map(|i| i.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0) - .unwrap_or(fallback) - }) + self.copied().map_or( + info.flags & crate::codegen_abi::FN_NON_STRICT_ORDINARY != 0, + |e| e.ordinary, + ) } #[cfg(test)] pub(super) fn write_source(self, bytes: &[u8], ordinary: bool) { @@ -357,128 +412,189 @@ impl<'a> Record<'a> { } } -/// Borrow the canonical owners for IP resolution. This is a view, not a name +/// Borrow the images' records for IP resolution. This is a view, not a name /// snapshot or address-to-id cache. The rare stack path orders actual code /// locations at query time; ordinary closure reads never search this list. -pub(crate) struct BundleFunctionNames<'a>(&'a [RegisteredOwner]); +pub(crate) struct BundleFunctionNames<'a>(&'a [ImageRecords]); impl BundleFunctionNames<'_> { - fn entries(&self) -> impl Iterator + '_ { - self.0.iter().flat_map(|owner| { - let count = match owner { - RegisteredOwner::Image(o) => o.count as usize, - RegisteredOwner::Copied(s) => s.copied.get().map_or(0, Vec::len), - }; - (0..count).filter_map(move |ordinal| match owner { - RegisteredOwner::Image(o) => { - let info = image_info(*o, ordinal)?; - let bytes = super::fn_metadata::info_name_bytes(info)?; - (!bytes.is_empty()).then_some((info.code as usize, bytes)) + /// Every named body, `(code, name)`, until `visit` returns true. One + /// non-generic walk shared by every query. + fn visit(&self, visit: &mut dyn FnMut(usize, &'static [u8]) -> bool) { + for image in self.0 { + match *image { + // SAFETY: a permanent image's records stay mapped. + ImageRecords::Image(bounds) => { + for info in unsafe { image_infos(bounds) } { + let Some(bytes) = super::fn_metadata::info_name_bytes(info) else { + continue; + }; + if !bytes.is_empty() && visit(info.code as usize, bytes) { + return; + } + } } - RegisteredOwner::Copied(s) => { - let entry = &(*s).copied.get()?[ordinal]; - let bytes = entry.name.as_deref()?; - (!bytes.is_empty()).then_some((entry.code, bytes)) + ImageRecords::Copied(states) => { + for state in states { + for entry in state.copied.get().into_iter().flatten() { + let Some(entry) = entry.get() else { continue }; + let Some(bytes) = entry.name.as_deref() else { + continue; + }; + if !bytes.is_empty() && visit(entry.code, bytes) { + return; + } + } + } } - }) - }) + } + } + } + /// The entry the key order picks: the greatest under `better`. + fn best( + &self, + keep: &dyn Fn(usize) -> bool, + better: &dyn Fn(usize, usize) -> bool, + ) -> Option<(usize, &'static [u8])> { + let mut best: Option<(usize, &'static [u8])> = None; + self.visit(&mut |code, bytes| { + if keep(code) && best.is_none_or(|(seen, _)| better(code, seen)) { + best = Some((code, bytes)); + } + false + }); + best } pub(crate) fn len(&self) -> usize { - self.entries().count() + let mut count = 0; + self.visit(&mut |_, _| { + count += 1; + false + }); + count } pub(crate) fn contains_key(&self, address: &usize) -> bool { - self.entries().any(|(code, _)| code == *address) + let mut found = false; + self.visit(&mut |code, _| { + found = code == *address; + found + }); + found } + /// The named body at the greatest code address at or below `ip` (the + /// last such entry on a tie, as `max_by_key` picks). pub(crate) fn predecessor(&self, ip: usize) -> Option<(usize, &'static [u8])> { - self.entries() - .filter(|(code, _)| *code <= ip) - .max_by_key(|(code, _)| *code) + self.best(&|code| code <= ip, &|code, seen| code >= seen) } pub(crate) fn first_key_value(&self) -> Option<(usize, &'static [u8])> { - self.entries().min_by_key(|(code, _)| *code) + self.best(&|_| true, &|code, seen| code < seen) } pub(crate) fn last_key_value(&self) -> Option<(usize, &'static [u8])> { - self.entries().max_by_key(|(code, _)| *code) + self.best(&|_| true, &|code, seen| code >= seen) } } pub(crate) fn with_bundle_function_names( read: impl FnOnce(&BundleFunctionNames<'_>) -> R, ) -> Option { - let owners = owners().lock().ok()?; - Some(read(&BundleFunctionNames(&owners))) + let images = images().lock().ok()?; + Some(read(&BundleFunctionNames(&images))) } /// Raw method/accessor dispatch still carries a code location rather than a -/// closure info. Resolve it against the same canonical descriptors; do not -/// invent an address-keyed registration ABI or a second routing table. +/// closure info. Resolve it against the same image records; do not invent an +/// address-keyed registration ABI or a second routing table. pub(super) fn source_for_code(code: usize) -> Option { - let owners = owners().lock().ok()?; - owners.iter().rev().find_map(|owner| match owner { - RegisteredOwner::Image(owner) => (0..owner.count as usize).rev().find_map(|ordinal| { - let info = image_info(owner, ordinal)?; - (info.code as usize == code) - .then(|| super::fn_metadata::retained_source_for_info(info)) - .flatten() - }), - RegisteredOwner::Copied(state) => state - .copied - .get()? - .iter() + let images = images().lock().ok()?; + images.iter().rev().find_map(|image| match *image { + // SAFETY: a permanent image's records stay mapped. + ImageRecords::Image(bounds) => unsafe { image_infos(bounds) } .rev() - .find(|entry| entry.code == code) - .and_then(|e| e.source.as_deref()) - .and_then(super::fn_metadata::decode_registered_bytes) - .map(str::to_owned), + .find(|info| info.code as usize == code) + .and_then(super::fn_metadata::retained_source_for_info), + ImageRecords::Copied(states) => states.iter().rev().find_map(|state| { + state + .copied + .get()? + .iter() + .rev() + .filter_map(OnceLock::get) + .find(|entry| entry.code == code) + .and_then(|e| e.source.as_deref()) + .and_then(super::fn_metadata::decode_registered_bytes) + .map(str::to_owned) + }), }) } pub(super) fn name_for_code(code: usize) -> Option { with_bundle_function_names(|names| { - names - .entries() - .filter(|(address, _)| *address == code) - .last() - .and_then(|(_, bytes)| super::fn_metadata::decode_registered_bytes(bytes)) + let mut last = None; + names.visit(&mut |address, bytes| { + if address == code { + last = Some(bytes); + } + false + }); + last.and_then(super::fn_metadata::decode_registered_bytes) .map(str::to_owned) }) .flatten() } pub(crate) fn function_registries_census() -> Vec { - let Ok(owners) = owners().lock() else { + let Ok(images) = images().lock() else { return Vec::new(); }; - let mut entries = 0; - let mut bytes = owners.capacity() * std::mem::size_of::(); - for owner in owners.iter() { - let state = match owner { - RegisteredOwner::Image(o) => state(o), - RegisteredOwner::Copied(s) => Some(*s), - }; - if let Some(state) = state { - bytes += std::mem::size_of::(); - if let Some(copied) = state.copied.get() { - bytes += copied.capacity() * std::mem::size_of::(); - bytes += copied - .iter() - .map(|e| { - e.name.as_ref().map_or(0, |b| b.len() + 16) - + e.source.as_ref().map_or(0, |b| b.len() + 16) - }) - .sum::(); + // The owners with a payload, found through the images' records (a census + // walk, not a list the runtime keeps). + let mut states: Vec<&'static OwnerState> = Vec::new(); + for image in images.iter() { + match *image { + // SAFETY: a permanent image's records stay mapped. + ImageRecords::Image(bounds) => { + for info in unsafe { image_infos(bounds) } { + if let Some(st) = Record::for_info(info).and_then(|r| state(r.owner)) { + if !states.iter().any(|seen| std::ptr::eq(*seen, st)) { + states.push(st); + } + } + } } - if let Ok(slots) = state.overrides.lock() { - entries += slots.len(); - bytes += slots.capacity() * std::mem::size_of::<(u32, Overrides)>(); - bytes += slots - .iter() - .map(|(_, v)| { - v.name.as_ref().map_or(0, |b| b.len() + 16) - + v.source.as_ref().map_or(0, |(b, _)| b.len() + 16) - }) - .sum::(); + ImageRecords::Copied(copied) => { + for st in copied { + if !states.iter().any(|seen| std::ptr::eq(*seen, *st)) { + states.push(st); + } + } } } } + let mut entries = 0; + let mut bytes = images.capacity() * std::mem::size_of::(); + for state in states { + bytes += std::mem::size_of::(); + if let Some(copied) = state.copied.get() { + bytes += copied.len() * std::mem::size_of::>(); + bytes += copied + .iter() + .filter_map(OnceLock::get) + .map(|e| { + e.name.as_ref().map_or(0, |b| b.len() + 16) + + e.source.as_ref().map_or(0, |b| b.len() + 16) + }) + .sum::(); + } + if let Ok(slots) = state.overrides.lock() { + entries += slots.len(); + bytes += slots.capacity() * std::mem::size_of::<(u32, Overrides)>(); + bytes += slots + .iter() + .map(|(_, v)| { + v.name.as_ref().map_or(0, |b| b.len() + 16) + + v.source.as_ref().map_or(0, |(b, _)| b.len() + 16) + }) + .sum::(); + } + } vec![ - ("fn.metadata_owners", owners.len(), bytes), + ("fn.metadata_images", images.len(), bytes), ("fn.metadata_overrides(entries)", entries, 0), ] } @@ -494,13 +610,14 @@ pub(crate) fn test_bundle_names(entries: &[(usize, &[u8])]) -> BundleFunctionNam ordinary: false, }) .collect(); - let state = Box::leak(Box::new(OwnerState::new(Some(copied)))); - BundleFunctionNames(Box::leak(Box::new([RegisteredOwner::Copied(state)]))) + let state: &'static OwnerState = Box::leak(Box::new(OwnerState::new(Some(copied)))); + let states: &'static [&'static OwnerState] = Box::leak(Box::new([state])); + BundleFunctionNames(Box::leak(Box::new([ImageRecords::Copied(states)]))) } #[cfg(test)] pub(crate) fn test_publish_bundle_name(code: usize, bytes: &[u8]) { let view = test_bundle_names(&[(code, bytes)]); - owners().lock().unwrap().extend_from_slice(view.0); + images().lock().unwrap().extend_from_slice(view.0); } #[cfg(test)] #[path = "fn_metadata_owner_tests.rs"] diff --git a/crates/perry-runtime/src/builtins/fn_metadata_owner_tests.rs b/crates/perry-runtime/src/builtins/fn_metadata_owner_tests.rs index 8f9e432b616..183964b32e8 100644 --- a/crates/perry-runtime/src/builtins/fn_metadata_owner_tests.rs +++ b/crates/perry-runtime/src/builtins/fn_metadata_owner_tests.rs @@ -9,10 +9,14 @@ struct EntryImage { name: [u8; 24], source: [u8; 48], } +/// i32 words per record in this target's records section. +const WORDS: usize = FUNCTION_RECORD_BYTES / 4; #[repr(C)] struct Image { owner: FunctionMetadataOwner, - entries: [i32; 2], + /// The image's records section: one record per info, as the link keeps it. + section: [i32; 2 * WORDS], + bounds: FunctionRecordBounds, records: [EntryImage; 2], } fn image(code: usize) -> Box { @@ -30,15 +34,27 @@ fn image(code: usize) -> Box { }; let mut image = Box::new(Image { owner: FunctionMetadataOwner { - entries: std::ptr::null(), count: 2, flags: 0, state: std::sync::atomic::AtomicPtr::new(std::ptr::null_mut()), + bounds: 0, + }, + section: [0; 2 * WORDS], + bounds: FunctionRecordBounds { + start: std::ptr::null(), + stop: std::ptr::null(), + sentinel: std::ptr::null(), }, - entries: [0; 2], records: [entry(code), entry(code + 256)], }); - image.owner.entries = image.entries.as_ptr(); + let start = image.section.as_ptr().cast::(); + image.bounds = FunctionRecordBounds { + start, + stop: start.wrapping_add(2 * FUNCTION_RECORD_BYTES), + sentinel: start, + }; + let field = &image.owner.bounds as *const i32 as isize; + image.owner.bounds = (&image.bounds as *const FunctionRecordBounds as isize - field) as i32; let owner = &image.owner as *const _; for (ordinal, record) in image.records.iter_mut().enumerate() { record.name[..5].copy_from_slice(if ordinal == 0 { b"alpha" } else { b"bravo" }); @@ -57,20 +73,50 @@ fn image(code: usize) -> Box { owner, ordinal: ordinal as u32, }; - image.entries[ordinal] = (info as isize - image.owner.entries as isize) as i32; + let word = &image.section[ordinal * WORDS] as *const i32 as isize; + image.section[ordinal * WORDS] = (info as isize - word) as i32; } image } +/// A record with the info it was made from, so the tests read like the +/// callers in `fn_metadata.rs`. +#[derive(Clone, Copy)] +struct Bound<'a> { + record: Record<'a>, + info: &'a JsFunctionInfo, + ordinal: u32, +} +fn bound(info: &JsFunctionInfo) -> Option> { + let record = Record::for_info(info)?; + Some(Bound { record, info, ordinal: record.ordinal }) +} +impl Bound<'_> { + fn with_name(self, read: impl FnOnce(&str) -> R) -> Option { + self.record.with_name(self.info, read) + } + fn write_name(self, bytes: &[u8], infer: bool) -> bool { + self.record.write_name(self.info, bytes, infer) + } + fn source(self) -> Option { + self.record.source(self.info) + } + fn ordinary(self, _fallback: bool) -> bool { + self.record.ordinary(self.info) + } + fn write_source(self, bytes: &[u8], ordinary: bool) { + self.record.write_source(bytes, ordinary) + } +} fn registered(info: &JsFunctionInfo) -> Option { - Record::for_info(info)?.with_name(str::to_owned) + bound(info)?.with_name(str::to_owned) } #[test] fn owner_and_ordinal_ignore_code_layout_and_duplicate_spelling() { for code in [0x4a01_0000, 0x6b12_0400] { let mut image = image(code); image.records[1].name[..5].copy_from_slice(b"alpha"); - let first = Record::for_info(&image.records[0].info).unwrap(); - let second = Record::for_info(&image.records[1].info).unwrap(); + let first = bound(&image.records[0].info).unwrap(); + let second = bound(&image.records[1].info).unwrap(); assert_eq!(first.ordinal, 0); assert_eq!(second.ordinal, 1); first.write_name(b"override", false); @@ -87,7 +133,7 @@ fn owner_and_ordinal_ignore_code_layout_and_duplicate_spelling() { fn owned_name_copies_and_invalid_utf8_falls_back_to_borrowed() { let image = image(0x4a02_0000); let info = &image.records[0].info; - let record = Record::for_info(info).unwrap(); + let record = bound(info).unwrap(); let mut bytes = b"mutable".to_vec(); record.write_name(&bytes, false); bytes.fill(b'x'); @@ -108,7 +154,7 @@ fn inference_copies_first_valid_name_and_preserves_invalid_override_fallback() { unsafe { JsFunctionInfo::from_code(0x4a03_0000 as *const u8, 0) }, &OWNER, ); - let record = Record::for_info(&native.info).unwrap(); + let record = bound(&native.info).unwrap(); record.write_name(b"\xff", false); record.write_name("世界".as_bytes(), true); record.write_name(b"later", true); @@ -123,7 +169,7 @@ fn native_aliases_share_only_explicit_canonical_owners() { let alias = NativeFunctionInfo::new(body, &SHARED); let declaration = NativeFunctionInfo::new(body, &SHARED); let same_thunk = NativeFunctionInfo::new(body, &OTHER); - Record::for_info(&declaration.info) + bound(&declaration.info) .unwrap() .write_name(b"shared", false); assert_eq!(registered(&alias.info).as_deref(), Some("shared")); @@ -143,7 +189,7 @@ fn closure_info_name_and_registered_override_keep_distinct_precedence() { info, props: std::ptr::null_mut(), }; - Record::for_info(info) + bound(info) .unwrap() .write_name(b"override", false); assert_eq!(registered(info).as_deref(), Some("override")); @@ -161,7 +207,7 @@ fn closure_info_name_and_registered_override_keep_distinct_precedence() { fn source_overrides_keep_copy_fallback_kind_and_info_precedence() { let image = image(0x4a06_0000); let info = &image.records[0].info; - let record = Record::for_info(info).unwrap(); + let record = bound(info).unwrap(); let mut source = b"function override(){}".to_vec(); record.write_source(&source, true); source.fill(b'x'); @@ -185,11 +231,11 @@ fn publication_does_not_allocate_per_entry_and_repeated_init_is_idempotent() { // registration index (CKI S2). assert!(state(&image.owner).is_none()); assert_ne!(registration(&image.owner), 0); - let count = owners().lock().unwrap().len(); + let count = images().lock().unwrap().len(); unsafe { js_register_function_metadata(&image.owner, 1); } - assert_eq!(owners().lock().unwrap().len(), count); + assert_eq!(images().lock().unwrap().len(), count); assert_eq!( with_bundle_function_names(|names| names.predecessor(0x4a07_0010).unwrap().1.to_vec()), Some(b"alpha".to_vec()) @@ -219,7 +265,7 @@ fn unloadable_image_bytes_survive_unload_and_address_reuse() { #[test] fn inference_before_unloadable_publication_does_not_skip_image_copy() { let image = image(0x4a09_0000); - let record = Record::for_info(&image.records[0].info).unwrap(); + let record = bound(&image.records[0].info).unwrap(); record.write_name(b"early override", false); unsafe { js_register_function_metadata(&image.owner, 0); diff --git a/crates/perry-runtime/src/gc/roots/stack_maps.rs b/crates/perry-runtime/src/gc/roots/stack_maps.rs index 8d5823c9727..d3db5d25080 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps.rs @@ -30,12 +30,17 @@ use std::ffi::c_void; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{OnceLock, RwLock, RwLockReadGuard}; -/// Magic and version of the compact map the compiler emits +/// Version of the compact map the compiler emits /// (`perry-codegen/src/gc_map.rs`). LLVM's own stack-map section is rewritten /// at assembly time and never reaches the binary: >50% of it was the /// statepoint constant preamble and base/derived duplicates that this parser /// discarded anyway, and shipping it cost 3.9 MB on a real application. -const GC_MAP_MAGIC: &[u8; 4] = b"PGCM"; +/// +/// v9 (DCE layer 0): one entry per FUNCTION in this section and one records +/// blob per function, each tied by the linker to its function's section, so +/// the map of a function the link removed is removed with it. The entry +/// carries the version (top byte of its record count); there is no header. +/// /// v8: each blob is a DIRECTORY (header + one entry per function) in this /// section, and its records (instruction offsets + varint stream) live in a /// records section the directory names by a link-time self-relative offset. @@ -62,7 +67,7 @@ const GC_MAP_MAGIC: &[u8; 4] = b"PGCM"; /// after a move. Version mismatch still fails closed (the parser returns /// None and `stack_maps()` panics), so an older binary cannot run on this /// runtime half-understood. -const GC_MAP_VERSION: u8 = 8; +const GC_MAP_VERSION: u8 = 9; const MAX_SAFEPOINT_RETURN_DELTA: usize = 16; #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] struct StackMapLocation { @@ -127,11 +132,8 @@ struct StackMapIndex { /// on whether it has an oracle to check itself against, and a test can /// build one without racing a process-wide `OnceLock`. mode: IndexMode, - /// The loaded map (directory) sections, one per image. + /// The loaded map (entry) sections, one per image. sections: Vec<&'static [u8]>, - /// One records blob per directory, indexed by `FunctionEntry::blob`. Held - /// so a function's records are reachable without a pointer per function. - records: Vec<&'static [u8]>, /// Sorted by address. Duplicates are kept, not deduplicated: two entries /// can share a relocated address and each brings its own records. functions: Vec, @@ -895,18 +897,13 @@ fn build_index_from_sections( // Sized exactly before the build: the table lives for the process, and a // doubling Vec would leave up to half of it (and the copies it made on the // way) resident for nothing. - let mut capacity = 0usize; - for section in §ions { - capacity = capacity.saturating_add( - decode::directory_function_count(section) - .unwrap_or_else(|| undecodable_section(section.len())), - ); - } + let capacity = sections + .iter() + .fold(0usize, |sum, section| sum.saturating_add(decode::entry_count(section))); let mut functions = Vec::with_capacity(capacity); - let mut records = Vec::new(); for (index, section) in sections.iter().enumerate() { let origin = origins[index]; - if lazy::parse_function_table(section, origin, &mut functions, &mut records).is_none() { + if lazy::parse_function_table(section, origin, &mut functions).is_none() { undecodable_section(section.len()); } } @@ -927,7 +924,6 @@ fn build_index_from_sections( StackMapIndex { mode, sections, - records, functions, eager, #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] @@ -960,10 +956,9 @@ fn build_eager_index(sections: &[&'static [u8]], origins: &[usize]) -> EagerInde fn undecodable_section(len: usize) -> ! { panic!( "perry: a GC map section (__perry_gcmap / .perry_gcmap, {len} bytes) is \ - present but could not be decoded — expected format {:?} v{}. This binary's \ + present but could not be decoded — expected format v{}. This binary's \ compiler and runtime disagree about the map layout; continuing would run \ the collector with missing roots and corrupt the heap silently.", - std::str::from_utf8(GC_MAP_MAGIC).unwrap_or("PGCM"), GC_MAP_VERSION, ); } diff --git a/crates/perry-runtime/src/gc/roots/stack_maps/census_rows.rs b/crates/perry-runtime/src/gc/roots/stack_maps/census_rows.rs index abeef4d6769..1929f0c407e 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps/census_rows.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps/census_rows.rs @@ -23,8 +23,8 @@ pub(crate) fn stack_map_index_census() -> Vec { )); rows.push(( "stackmap.records(file-backed)", - ix.records.len(), - ix.records.iter().map(|s| s.len()).sum(), + ix.functions.len(), + ix.functions.iter().map(|f| f.records_len as usize).sum(), )); rows.push(( "stackmap.functions", diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_decode.rs b/crates/perry-runtime/src/gc/roots/stack_maps_decode.rs index ff5d6c93821..6c8df129d00 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_decode.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_decode.rs @@ -18,14 +18,13 @@ //! because only one piece of code reads one. use super::lazy::{collect_derived, collect_slots, Payload, RecordWalk, Step}; -use super::{StackMapDerived, StackMapLocation, StackMapRecord, GC_MAP_MAGIC, GC_MAP_VERSION}; +use super::{StackMapDerived, StackMapLocation, StackMapRecord, GC_MAP_VERSION}; -/// Decode every concatenated compact map in the section. +/// Decode every function entry in the section. /// -/// The linker concatenates one directory per object file, so this walks them -/// one by one (see [`for_each_directory`]) rather than assuming a single map — -/// a decoder that reads only the first header silently drops every other -/// object's roots, which is invisible until a collection frees a live object. +/// The linker concatenates the entries of every function it kept (v9: one +/// entry per function, each in an input section tied to its function's), so +/// the section is an array of fixed-size entries in link order. /// /// `origin` is the runtime address of `bytes[0]` (see [`function_address`]). pub(super) fn parse_gc_map( @@ -40,74 +39,61 @@ pub(super) fn parse_gc_map( let mut roots: Vec = Vec::new(); let mut derived: Vec = Vec::new(); - for_each_directory(bytes, |directory| { - let blob = directory.records; + for_each_entry(bytes, origin, |entry| { + let blob = entry.records; let blob_end = blob.len(); - let stream_base = directory.stream_base(); - let mut walk = RecordWalk::new(blob, blob_end, stream_base, 0); - - for index in 0..directory.function_count { - let function = directory.function(bytes, origin, index)?; - let function_address = function.address; - let stack_size = u64::from(function.stack_size); - let record_count = function.record_count; - - // The recorded per-function offset must BE where the sequential - // walk stands. The compiler proves this for every function of - // every binary it emits (`verify_roundtrip`); proving it again - // here, against the shipped bytes, is what lets the lazy lookup - // start a walk at a recorded offset and trust the result. - if stream_base.checked_add(function.stream_offset)? != walk.cursor() { - return None; - } - - // The repeat chain is per FUNCTION: the first record of a function - // can never be a repeat, because the encoder re-arms there too. - walk.restart(record_count); - - let mut previous: Option<(Payload, (u32, u32, u32, u32))> = None; - for record in 0..record_count as usize { - let instruction_offset = read_u32( - blob, - function.first_record.checked_add(record)?.checked_mul(4)?, - )?; - - let payload = match walk.next() { - Step::Record(payload) => payload, - Step::Done | Step::Malformed => return None, - }; - - // 77% of records repeat the previous live set. Point them at - // one copy instead of duplicating 154k entries — the same - // sharing v4 did, keyed on the payload the walk reports. - let range = match previous { - Some((seen, range)) if seen == payload => range, - _ => { - let start = u32::try_from(roots.len()).ok()?; - collect_slots( - blob, - payload.roots_at, - blob_end, - payload.roots_len, - &mut roots, - )?; - let derived_start = u32::try_from(derived.len()).ok()?; - collect_derived(blob, &payload, blob_end, &mut derived)?; - (start, payload.roots_len, derived_start, payload.derived_len) - } - }; - previous = Some((payload, range)); - - records.push(StackMapRecord { - pc: function_address.checked_add(instruction_offset as usize)?, - function_address, - stack_size, - roots_start: range.0, - roots_len: range.1, - derived_start: range.2, - derived_len: range.3, - }); - } + let function_address = entry.address; + let stack_size = u64::from(entry.stack_size); + let record_count = entry.record_count; + // The repeat chain is per FUNCTION and so is the blob: the walk starts + // after the function's instruction offsets with nothing to repeat. + let mut walk = RecordWalk::new(blob, blob_end, entry.stream_at(), record_count); + + let mut previous: Option<(Payload, (u32, u32, u32, u32))> = None; + for record in 0..record_count as usize { + let instruction_offset = read_u32(blob, record.checked_mul(4)?)?; + + let payload = match walk.next() { + Step::Record(payload) => payload, + Step::Done | Step::Malformed => return None, + }; + + // 77% of records repeat the previous live set. Point them at + // one copy instead of duplicating 154k entries — the same + // sharing v4 did, keyed on the payload the walk reports. + let range = match previous { + Some((seen, range)) if seen == payload => range, + _ => { + let start = u32::try_from(roots.len()).ok()?; + collect_slots( + blob, + payload.roots_at, + blob_end, + payload.roots_len, + &mut roots, + )?; + let derived_start = u32::try_from(derived.len()).ok()?; + collect_derived(blob, &payload, blob_end, &mut derived)?; + (start, payload.roots_len, derived_start, payload.derived_len) + } + }; + previous = Some((payload, range)); + + records.push(StackMapRecord { + pc: function_address.checked_add(instruction_offset as usize)?, + function_address, + stack_size, + roots_start: range.0, + roots_len: range.1, + derived_start: range.2, + derived_len: range.3, + }); + } + // The blob is exactly this function's offsets and stream: a stream + // that ends early or runs on describes some other layout. This is what + // lets the lazy lookup trust a blob it never walked to the end. + if walk.cursor() != blob_end { + return None; } Some(()) })?; @@ -115,237 +101,102 @@ pub(super) fn parse_gc_map( Some((records, roots, derived)) } -/// v8 directory header: magic, version, reserved byte, flags, function count, -/// total length (stride to the next directory), records offset, records -/// length, record total, reserved word. -pub(super) const DIRECTORY_HEADER_BYTES: usize = 32; - -/// v8 directory entry: `i32 function_offset, u32 stack_size, -/// u32 record_count, u32 first_record, u32 stream_offset`. +/// v9 entry: `i32 function - e, u32 stack_size, u32 record_count | version +/// << 24, i32 records - e, u32 records_len`, `e` being the entry's address. pub(super) const FUNCTION_ENTRY_BYTES: usize = 20; +/// The record count's bits; the top byte is the format version. +const RECORD_COUNT_MASK: u32 = (1 << 24) - 1; -/// One validated v8 directory. -pub(super) struct Directory<'a> { - /// Offset of the directory's first byte within its section. - pub(super) base: usize, - pub(super) function_count: usize, - /// The records blob the header names: the `u32` instruction offsets of - /// every record, then the varint stream. - pub(super) records: &'a [u8], - pub(super) record_total: usize, -} - -/// One function of a [`Directory`], with its fields checked against the -/// directory they came from. -pub(super) struct DirectoryFunction { +/// One validated v9 entry. +pub(super) struct Entry<'a> { pub(super) address: usize, pub(super) stack_size: u32, pub(super) record_count: u32, - /// Index of the function's first record in the instruction-offset array. - pub(super) first_record: usize, - /// Start of the function's records, relative to the stream's start. - pub(super) stream_offset: usize, + /// This function's records: `record_count` u32 instruction offsets, then + /// its varint stream. + pub(super) records: &'a [u8], } -impl Directory<'_> { +impl Entry<'_> { /// Offset of the varint stream within [`Self::records`]. - pub(super) fn stream_base(&self) -> usize { - self.record_total * 4 - } - - /// Function `index`. Its bounds were proven by [`for_each_directory`], - /// which read every entry before handing the directory out. - pub(super) fn function( - &self, - section: &[u8], - origin: usize, - index: usize, - ) -> Option { - let at = self - .base - .checked_add(DIRECTORY_HEADER_BYTES)? - .checked_add(index.checked_mul(FUNCTION_ENTRY_BYTES)?)?; - Some(DirectoryFunction { - address: function_address(origin, self.base, read_u32(section, at)?)?, - stack_size: read_u32(section, at + 4)?, - record_count: read_u32(section, at + 8)?, - first_record: read_u32(section, at + 12)? as usize, - stream_offset: read_u32(section, at + 16)? as usize, - }) + pub(super) fn stream_at(&self) -> usize { + self.record_count as usize * 4 } } -/// Sum of the function counts of every directory in `bytes`, reading headers -/// only. `None` for a section that does not walk; the full parse will refuse -/// it with the reason. -pub(super) fn directory_function_count(bytes: &[u8]) -> Option { - let mut count = 0usize; - walk_headers(bytes, |base, function_count, _total| { - let _ = base; - count = count.checked_add(function_count)?; - Some(()) - })?; - Some(count) +/// Number of entry slots in `bytes`, for sizing the index exactly. +pub(super) fn entry_count(bytes: &[u8]) -> usize { + bytes.len() / FUNCTION_ENTRY_BYTES } -/// The one walk over a map section's directories. Both the lazy build and -/// the eager parser go through it, so the two cannot disagree about where a -/// directory starts, how long it is, or whether it is valid. +/// The one walk over a map section's entries. Both the lazy build and the +/// eager parser go through it, so the two cannot disagree about where an +/// entry is or whether it is valid. /// -/// Every directory is validated before `visit` sees it: the version, the -/// flags, the length against the entries, the records blob against the -/// directory (it may follow the directory in-line, as the decode tests lay it -/// out, or live in a section of its own, as the compiler emits it — but it may -/// never overlap the entries), and every entry's record range and stream -/// offset against the records blob. -pub(super) fn for_each_directory<'a>( +/// Every entry is validated before `visit` sees it: the version, the record +/// count against the records blob, and the blob against the section (it may +/// sit before or after the entries, as the compiler's own section does, but +/// never on them). An all-zero slot is linker padding; a trailing partial +/// slot must be zero too. +pub(super) fn for_each_entry<'a>( bytes: &'a [u8], - mut visit: impl FnMut(&Directory<'a>) -> Option<()>, + origin: usize, + mut visit: impl FnMut(&Entry<'a>) -> Option<()>, ) -> Option<()> { - walk_headers(bytes, |base, function_count, total_len| { - let entries_end = base - .checked_add(DIRECTORY_HEADER_BYTES)? - .checked_add(function_count.checked_mul(FUNCTION_ENTRY_BYTES)?)?; - if entries_end > base.checked_add(total_len)? { - return None; + let full = bytes.len() - bytes.len() % FUNCTION_ENTRY_BYTES; + if bytes[full..].iter().any(|byte| *byte != 0) { + return None; + } + for base in (0..full).step_by(FUNCTION_ENTRY_BYTES) { + let slot = &bytes[base..base + FUNCTION_ENTRY_BYTES]; + if slot.iter().all(|byte| *byte == 0) { + continue; } - let records_offset = read_u32(bytes, base + 16)? as i32 as isize; - let records_len = read_u32(bytes, base + 20)? as usize; - let record_total = read_u32(bytes, base + 24)? as usize; - if read_u32(bytes, base + 28)? != 0 { + let counted = read_u32(bytes, base + 8)?; + if counted >> 24 != u32::from(GC_MAP_VERSION) { return None; } - if record_total.checked_mul(4)? > records_len { + let record_count = counted & RECORD_COUNT_MASK; + let records_offset = read_u32(bytes, base + 12)? as i32 as isize; + let records_len = read_u32(bytes, base + 16)? as usize; + if (record_count as usize).checked_mul(4)? > records_len { return None; } - // The records may sit before or after the directory, never on it. let records_start = (base as isize).checked_add(records_offset)?; - let entries_start = base as isize; let records_end = records_start.checked_add(isize::try_from(records_len).ok()?)?; - if records_start < entries_end as isize && records_end > entries_start { + if records_start < full as isize && records_end > 0 { return None; } // SAFETY: the offset is a link-time difference between two labels of - // the same object (`_perry_gc_rec - _perry_gc_map`), resolved by the - // linker exactly like the function fields this map already trusts, so - // it names `records_len` bytes of the same loaded image — or, in a - // test, of the same allocation. Image sections are never unmapped - // while their code can be on a stack. + // the same object (`records - entry`), resolved by the linker exactly + // like the function field this map already trusts, so it names + // `records_len` bytes of the same loaded image — or, in a test, of the + // same allocation. Image sections are never unmapped while their code + // can be on a stack. let records: &'a [u8] = unsafe { core::slice::from_raw_parts(bytes.as_ptr().offset(records_start), records_len) }; - let directory = Directory { - base, - function_count, + visit(&Entry { + address: function_address(origin, base, read_u32(bytes, base)?)?, + stack_size: read_u32(bytes, base + 4)?, + record_count, records, - record_total, - }; - // Prove every entry against the records before anyone walks them: the - // encoder emits functions in stream order, so the first record index - // is a running sum, the first stream offset is 0 and the rest never - // decrease. - let stream_len = records_len - record_total * 4; - let mut next_record = 0usize; - let mut previous_stream_offset = 0usize; - for index in 0..function_count { - let at = base + DIRECTORY_HEADER_BYTES + index * FUNCTION_ENTRY_BYTES; - let record_count = read_u32(bytes, at + 8)? as usize; - let first_record = read_u32(bytes, at + 12)? as usize; - let stream_offset = read_u32(bytes, at + 16)? as usize; - if first_record != next_record { - return None; - } - next_record = next_record.checked_add(record_count)?; - if next_record > record_total { - return None; - } - if (index == 0 && stream_offset != 0) - || stream_offset < previous_stream_offset - || stream_offset > stream_len - { - return None; - } - previous_stream_offset = stream_offset; - } - if next_record != record_total { - return None; - } - visit(&directory) - }) -} - -/// Walk the directory headers of a section: magic, version, flags, and a -/// length that advances the cursor. `visit(base, function_count, total_len)`. -fn walk_headers( - bytes: &[u8], - mut visit: impl FnMut(usize, usize, usize) -> Option<()>, -) -> Option<()> { - let mut base = 0usize; - while base + DIRECTORY_HEADER_BYTES <= bytes.len() { - if bytes.get(base..base + 4)? != GC_MAP_MAGIC { - // Linkers pad between input sections; a zero tail is the end. - if bytes[base..].iter().all(|byte| *byte == 0) { - break; - } - base += 1; - continue; - } - if read_u8(bytes, base + 4)? != GC_MAP_VERSION { - return None; - } - // No header flags are defined; anything set is a layout this decoder - // does not know. Fail closed. - if read_u16(bytes, base + 6)? != 0 { - return None; - } - let function_count = read_u32(bytes, base + 8)? as usize; - let total_len = read_u32(bytes, base + 12)? as usize; - // A directory must at least cover its header. Without this a - // `total_len` of 0 leaves `base` unchanged — and because the magic - // still matches at that offset the resynchronisation path above is - // never reached, so the loop spins forever inside - // `OnceLock::get_or_init`: a process hang at the first collection - // rather than a fail-closed panic. - if total_len < DIRECTORY_HEADER_BYTES { - return None; - } - let blob_end = base.checked_add(total_len)?; - if blob_end > bytes.len() { - return None; - } - visit(base, function_count, total_len)?; - let next = align_up(blob_end, 8)?; - if next <= base { - return None; - } - base = next; + })?; } Some(()) } -/// v6 (#11508): a function field is a signed 32-bit offset from the first byte -/// of its own blob, resolved by the linker (`.long fn-_perry_gc_map`) so the -/// section needs no load-time relocations. `origin` is the runtime address of -/// the section slice's first byte and `blob` the blob's offset within it. -pub(super) fn function_address(origin: usize, blob: usize, field: u32) -> Option { +/// v6 (#11508): a function field is a signed 32-bit offset from its own entry, +/// resolved by the linker (`.long fn-entry`) so the section needs no +/// load-time relocations. `origin` is the runtime address of the section +/// slice's first byte and `entry` the entry's offset within it. +pub(super) fn function_address(origin: usize, entry: usize, field: u32) -> Option { origin - .checked_add(blob)? + .checked_add(entry)? .checked_add_signed(field as i32 as isize) } -fn align_up(value: usize, alignment: usize) -> Option { - value - .checked_add(alignment.checked_sub(1)?) - .map(|value| value & !(alignment - 1)) -} - -fn read_u8(bytes: &[u8], offset: usize) -> Option { - bytes.get(offset).copied() -} - -/// Used by the map header's flags field and by ELF section headers. It was -/// briefly Linux-gated, which broke the Linux build the moment the map itself -/// needed a 16-bit read — keep it unconditional. +/// Used by the ELF section headers. pub(super) fn read_u16(bytes: &[u8], offset: usize) -> Option { Some(u16::from_le_bytes( bytes.get(offset..offset + 2)?.try_into().ok()?, diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs b/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs index acf4edc54f5..264f169c1ca 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_decode_tests.rs @@ -6,7 +6,7 @@ #[cfg(test)] mod tests { - use super::super::decode::{DIRECTORY_HEADER_BYTES, FUNCTION_ENTRY_BYTES}; + use super::super::decode::FUNCTION_ENTRY_BYTES; use super::super::*; fn push_varint(out: &mut Vec, mut value: u64) { @@ -49,7 +49,7 @@ mod tests { fn one_map_with_derived( function: u64, records: &[(u32, Vec<(u16, i32)>, Vec<(u32, u16, i32)>, bool)], - ) -> Vec { + ) -> &'static [u8] { let mut offsets = Vec::new(); let mut stream = Vec::new(); for (instruction_offset, roots, derived, repeat) in records { @@ -75,41 +75,18 @@ mod tests { } } - // v8 with the records in-line after the directory (see - // `lazy::test_blob_multi_at`). One function per blob here, so its - // records start at 0 in the stream and at record 0. - let directory_len = DIRECTORY_HEADER_BYTES + FUNCTION_ENTRY_BYTES; - let records_len = offsets.len() + stream.len(); - let mut bytes = Vec::new(); - bytes.extend_from_slice(GC_MAP_MAGIC); - bytes.push(GC_MAP_VERSION); - bytes.push(0); - bytes.extend_from_slice(&0u16.to_le_bytes()); - bytes.extend_from_slice(&1u32.to_le_bytes()); - bytes.extend_from_slice(&((directory_len + records_len) as u32).to_le_bytes()); - bytes.extend_from_slice(&(directory_len as i32).to_le_bytes()); - bytes.extend_from_slice(&(records_len as u32).to_le_bytes()); - bytes.extend_from_slice(&(records.len() as u32).to_le_bytes()); - bytes.extend_from_slice(&0u32.to_le_bytes()); - // v6: the function field is relative to the blob's own first byte. - // Every test here decodes at origin 0 with this blob first, so the - // field is the address itself; `decodes_linker_concatenated_input_sections` - // shows what a blob that is not first must encode. - bytes.extend_from_slice(&i32::try_from(function).expect("fits i32").to_le_bytes()); - bytes.extend_from_slice(&32u32.to_le_bytes()); - bytes.extend_from_slice(&(records.len() as u32).to_le_bytes()); - bytes.extend_from_slice(&0u32.to_le_bytes()); - bytes.extend_from_slice(&0u32.to_le_bytes()); - bytes.extend_from_slice(&offsets); - bytes.extend_from_slice(&stream); - while bytes.len() % 8 != 0 { - bytes.push(0); - } - bytes + // v9: one entry, its records after it in the same allocation (see + // `lazy::test_section_at`). Every test here decodes at origin 0 with + // this entry first, so the function field is the address itself. + let offsets = offsets + .chunks(4) + .map(|word| u32::from_le_bytes(word.try_into().unwrap())) + .collect(); + super::super::lazy::test_section_at(0, &[(function, 32, offsets, stream)]) } /// The v3-shaped builder every existing test uses: roots only. - fn one_map(function: u64, records: &[(u32, Vec<(u16, i32)>, bool)]) -> Vec { + fn one_map(function: u64, records: &[(u32, Vec<(u16, i32)>, bool)]) -> &'static [u8] { let with_derived: Vec<(u32, Vec<(u16, i32)>, Vec<(u32, u16, i32)>, bool)> = records .iter() .map(|(off, roots, repeat)| (*off, roots.clone(), Vec::new(), *repeat)) @@ -117,7 +94,7 @@ mod tests { one_map_with_derived(function, &with_derived) } - fn simple(function: u64, offset: u32, frame_offset: i32) -> Vec { + fn simple(function: u64, offset: u32, frame_offset: i32) -> &'static [u8] { one_map(function, &[(offset, vec![(29, frame_offset)], false)]) } @@ -140,19 +117,21 @@ mod tests { #[test] fn decodes_linker_concatenated_input_sections() { - // Each object's blob anchors its function fields to ITSELF (its own - // `_perry_gc_map` label), so the second blob's field is relative to - // where it lands in the concatenated section — which is what the - // linker computes for `.long fn-_perry_gc_map`. - let mut bytes = simple(0x1000, 0x10, -8); - let second_at = bytes.len() as u64; - bytes.extend_from_slice(&simple(0x2000 - second_at, 0x20, -16)); - let (records, _, _) = parse_gc_map(&bytes, 0).expect("concatenated maps"); + // Each entry anchors its function field to ITSELF, so the second + // entry's field is relative to where it lands in the concatenated + // section — which is what the linker computes for `.long fn-entry`. + let bytes = super::super::lazy::test_blob_multi(&[ + (0x1000, 32, vec![(0x10, vec![(29, -8)])]), + (0x2000, 32, vec![(0x20, vec![(29, -16)])]), + ]); + let second = i32::from_le_bytes(bytes[FUNCTION_ENTRY_BYTES..][..4].try_into().unwrap()); + assert_eq!(second as usize, 0x2000 - FUNCTION_ENTRY_BYTES); + let (records, _, _) = parse_gc_map(bytes, 0).expect("concatenated maps"); assert_eq!(records.len(), 2); assert_eq!(records[0].pc, 0x1010); assert_eq!(records[1].pc, 0x2020); - // And the origin the section is mapped at shifts every blob alike. - let (moved, _, _) = parse_gc_map(&bytes, 0x7000_0000).expect("concatenated maps"); + // And the origin the section is mapped at shifts every entry alike. + let (moved, _, _) = parse_gc_map(bytes, 0x7000_0000).expect("concatenated maps"); assert_eq!(moved[0].function_address, 0x7000_1000); assert_eq!(moved[1].function_address, 0x7000_2000); } @@ -175,7 +154,7 @@ mod tests { assert_eq!(starts, [origin - 0x3000, origin + 0x40]); let mut functions = Vec::new(); - super::super::lazy::parse_function_table(&blob, origin, &mut functions, &mut Vec::new()) + super::super::lazy::parse_function_table(blob, origin, &mut functions) .expect("the lazy table agrees"); let lazy: Vec = functions.iter().map(|f| f.address).collect(); assert_eq!(lazy, starts); @@ -221,9 +200,7 @@ mod tests { let _ = index_records(records, roots, derived); // The published index is the LAZY one; build it the way the // collector does, from section bytes. - super::super::build_index_from_sections_lazy(vec![Box::leak( - simple(function, 0x20, -8).into_boxed_slice(), - )]) + super::super::build_index_from_sections_lazy(vec![simple(function, 0x20, -8)]) } fn force_reversed_publication(store: Arc, expected_generation: u64) { @@ -301,9 +278,16 @@ mod tests { } } - // The function field (bytes 32..36) is left 0 here: the assembler - // and linker fill it in, exactly as they do for a compiled module. + // The entry's two offsets (bytes 0..4 and 12..16) are written as + // label differences here: the assembler and linker fill them in, + // exactly as they do for a compiled module. let map = simple(0, 0x20, -8); + let records_at = i32::from_le_bytes(map[12..16].try_into().unwrap()) as isize; + let records_len = u32::from_le_bytes(map[16..20].try_into().unwrap()) as usize; + // SAFETY: the test section's records follow its entries in one + // allocation (`lazy::test_section_at`). + let records = + unsafe { std::slice::from_raw_parts(map.as_ptr().offset(records_at), records_len) }; let unique = format!( "perry-stack-map-dylib-{}-{:?}", std::process::id(), @@ -324,27 +308,33 @@ mod tests { line.push('\n'); line }; - // v6, in the shape `perry-codegen/src/gc_map.rs` emits for ELF: a - // read-only RETAIN section, and the function field as a link-time - // difference from a LOCAL label at the function's entry. The function - // itself is exported, i.e. preemptible in this `-shared` link — the - // case that rules out naming the global symbol. + // v9, in the shape `perry-codegen/src/gc_map.rs` emits for ELF: the + // entry and its records in read-only sections tied to the function's + // own section (`SHF_LINK_ORDER`), and the function field as a + // link-time difference from a LOCAL label at the function's entry. + // The function itself is exported, i.e. preemptible in this `-shared` + // link — the case that rules out naming the global symbol. let asm = format!( - "\t.section\t.perry_gcmap,\"aR\",@progbits\n\ - \t.p2align\t3\n\ - perry_test_map:\n\ - {head}\ - \t.long\t.Lperry_test_entry-perry_test_map\n\ - {tail}\ - \t.text\n\ + "\t.section\t.text.perry_test_anchor,\"ax\",@progbits\n\ \t.globl\tperry_test_anchor\n\ \t.type\tperry_test_anchor,%function\n\ perry_test_anchor:\n\ .Lperry_test_entry:\n\ \tret\n\ + \t.section\t.perry_gcrec,\"ao\",@progbits,perry_test_anchor,unique,1\n\ + .Lperry_test_rec:\n\ + {records}\ + \t.section\t.perry_gcmap,\"ao\",@progbits,perry_test_anchor,unique,2\n\ + \t.p2align\t2\n\ + perry_test_map:\n\ + \t.long\t.Lperry_test_entry-perry_test_map\n\ + {middle}\ + \t.long\t.Lperry_test_rec-perry_test_map\n\ + {tail}\ \t.section\t.note.GNU-stack,\"\",%progbits\n", - head = byte_line(&map[..32]), - tail = byte_line(&map[36..]), + records = byte_line(records), + middle = byte_line(&map[4..12]), + tail = byte_line(&map[16..20]), ); std::fs::write(&source, asm).expect("write dylib source"); let compiler = std::env::var_os("CC").unwrap_or_else(|| "cc".into()); @@ -373,8 +363,9 @@ mod tests { assert!( sections .iter() - .any(|section| section.starts_with(&map[..32]) - && section[36..].starts_with(&map[36..])), + .any(|section| section.len() >= 20 + && section[4..12] == map[4..12] + && section[16..20] == map[16..20]), "the later-loaded shared object's GC map was not discovered" ); // v6: the decoded address is the section's mapped address plus the @@ -579,7 +570,7 @@ mod tests { fn an_x19_base_round_trips_and_stays_fast_walkable() { let blob = super::super::lazy::test_blob(0x1000, 64, &[(0, vec![(19, -40)])]); let index = - super::super::build_index_from_sections_lazy(vec![Box::leak(blob.into_boxed_slice())]); + super::super::build_index_from_sections_lazy(vec![blob]); let matched = index.match_records(0x1000).expect("the record matches"); let decoded = index.materialise(Some(&matched)); assert_eq!(decoded.len(), 1); @@ -610,7 +601,7 @@ mod tests { fn an_unsupported_base_register_is_not_fast_walkable() { let blob = super::super::lazy::test_blob(0x1000, 64, &[(0, vec![(5, -40)])]); let index = - super::super::build_index_from_sections_lazy(vec![Box::leak(blob.into_boxed_slice())]); + super::super::build_index_from_sections_lazy(vec![blob]); let matched = index.match_records(0x1000).expect("the record matches"); let decoded = index.materialise(Some(&matched)); assert_eq!(decoded[0].1[0].dwarf_reg, 5); @@ -627,71 +618,76 @@ mod tests { } } + /// A copy of a test section's entries, for tests that corrupt one: the + /// copy's records offsets are rebased so they still name the original + /// records. + fn corruptible(section: &'static [u8]) -> &'static mut [u8] { + let copy: &'static mut [u8] = Box::leak(section.to_vec().into_boxed_slice()); + let shift = copy.as_ptr() as isize - section.as_ptr() as isize; + for at in (0..copy.len()).step_by(FUNCTION_ENTRY_BYTES) { + let old = i32::from_le_bytes(copy[at + 12..at + 16].try_into().unwrap()); + let new = i32::try_from(old as isize - shift).expect("test allocations are close"); + copy[at + 12..at + 16].copy_from_slice(&new.to_le_bytes()); + } + copy + } + #[test] - fn rejects_a_blob_with_header_flags_set() { - // v6 defines no flags. v5's bit 0 announced an 8-byte absolute address - // field; a blob still carrying it (or any other bit) is a layout this - // decoder would misread, so both decoders refuse it. - let mut bytes = simple(0x1000, 0x10, -8); - bytes[6..8].copy_from_slice(&1u16.to_le_bytes()); - assert!( - parse_gc_map(&bytes, 0).is_none(), - "a map with unknown header flags must be refused" - ); - let mut functions = Vec::new(); - assert!(super::super::lazy::parse_function_table( - &bytes, - 0, - &mut functions, - &mut Vec::new() - ) - .is_none()); + fn a_rebased_copy_still_decodes() { + // The negative control for every corruption below. + let bytes = corruptible(simple(0x1000, 0x10, -8)); + let (records, _, _) = parse_gc_map(bytes, 0).expect("the copy decodes"); + assert_eq!(records[0].pc, 0x1010); } #[test] - fn rejects_a_blob_whose_length_cannot_advance_the_cursor() { - // `total_len` comes straight from the header. A zero (or too-small) - // value leaves `base` where it was, and because the magic still - // matches there the resync path never runs — the loop spins forever - // inside `OnceLock::get_or_init`, hanging the process at the first - // collection instead of failing closed. - let mut bytes = simple(0x1000, 0x10, -8); - bytes[12..16].copy_from_slice(&0u32.to_le_bytes()); + fn rejects_truncated_or_wrong_version_sections() { + assert!(parse_gc_map(&[], 0).unwrap().0.is_empty()); + let bytes = corruptible(simple(0x1000, 0x10, -8)); + bytes[11] = GC_MAP_VERSION + 1; assert!( - parse_gc_map(&bytes, 0).is_none(), - "a blob that cannot advance the cursor must be rejected, not looped on" + parse_gc_map(bytes, 0).is_none(), + "an unknown version must not be guessed at" ); - - // Long enough to look plausible, still short of header + function table. - let mut bytes = simple(0x1000, 0x10, -8); - bytes[12..16].copy_from_slice(&20u32.to_le_bytes()); - assert!(parse_gc_map(&bytes, 0).is_none()); + let bytes = corruptible(simple(0x1000, 0x10, -8)); + bytes[11] = 8; + assert!(parse_gc_map(bytes, 0).is_none(), "a v8 entry is refused"); + let mut functions = Vec::new(); + assert!(super::super::lazy::parse_function_table(bytes, 0, &mut functions).is_none()); + // A section cut inside an entry must fail rather than read on. + let bytes = simple(0x1000, 0x10, -8); + assert!(parse_gc_map(&bytes[..FUNCTION_ENTRY_BYTES - 4], 0).is_none()); } + /// Linkers may pad between input sections; a zero slot is padding, not a + /// function, and the entries around it still decode. #[test] - fn rejects_a_truncated_function_table() { - // The record counts size the fixed-width offset array; a short read - // there must not be rounded down to zero, or every later varint is - // decoded from the wrong offset. - let bytes = simple(0x1000, 0x10, -8); - let truncated = &bytes[..DIRECTORY_HEADER_BYTES + 8]; - assert!(parse_gc_map(truncated, 0).is_none()); + fn zero_padding_between_entries_is_skipped() { + let one = simple(0x1000, 0x10, -8); + let padded: &'static mut [u8] = Box::leak(vec![0u8; 2 * FUNCTION_ENTRY_BYTES].into_boxed_slice()); + padded[FUNCTION_ENTRY_BYTES..].copy_from_slice(one); + let shift = padded.as_ptr() as isize + FUNCTION_ENTRY_BYTES as isize - one.as_ptr() as isize; + let at = FUNCTION_ENTRY_BYTES; + let old = i32::from_le_bytes(padded[at + 12..at + 16].try_into().unwrap()); + padded[at + 12..at + 16] + .copy_from_slice(&i32::try_from(old as isize - shift).unwrap().to_le_bytes()); + let old = i32::from_le_bytes(padded[at..at + 4].try_into().unwrap()); + padded[at..at + 4].copy_from_slice(&(old - FUNCTION_ENTRY_BYTES as i32).to_le_bytes()); + let (records, _, _) = parse_gc_map(padded, 0).expect("padding is skipped"); + assert_eq!(records.len(), 1); + assert_eq!(records[0].pc, 0x1010); } + /// The record count sizes the fixed-width offset array; a blob too short + /// for it must not be rounded down, or every later varint is decoded from + /// the wrong offset. #[test] - fn rejects_truncated_or_wrong_version_sections() { - assert!(parse_gc_map(&[], 0).is_none() || parse_gc_map(&[], 0).unwrap().0.is_empty()); - let mut bytes = simple(0x1000, 0x10, -8); - bytes[4] = GC_MAP_VERSION + 1; - assert!( - parse_gc_map(&bytes, 0).is_none(), - "an unknown version must not be guessed at" - ); - // A total_len that runs past the section must fail rather than read on. - let mut bytes = simple(0x1000, 0x10, -8); - let len = bytes.len(); - bytes[12..16].copy_from_slice(&((len as u32) + 64).to_le_bytes()); - assert!(parse_gc_map(&bytes, 0).is_none()); + fn rejects_a_record_count_the_blob_cannot_hold() { + let bytes = corruptible(simple(0x1000, 0x10, -8)); + bytes[16..20].copy_from_slice(&3u32.to_le_bytes()); + assert!(parse_gc_map(bytes, 0).is_none()); + let mut functions = Vec::new(); + assert!(super::super::lazy::parse_function_table(bytes, 0, &mut functions).is_none()); } /// A safepoint at the end of A must not be matched for an `ip` early in B @@ -704,7 +700,7 @@ mod tests { (0x2000, 32, vec![(0x40, vec![(29, -8)])]), ]); let index = - super::super::build_index_from_sections_lazy(vec![Box::leak(blob.into_boxed_slice())]); + super::super::build_index_from_sections_lazy(vec![blob]); // 0x2004 is 8 bytes past A's last safepoint but lives in B. assert!( index.match_records(0x2004).is_none(), @@ -734,7 +730,7 @@ mod tests { (0x3000, 32, vec![(0x10, vec![(29, -16)])]), ]); let index = - super::super::build_index_from_sections_lazy(vec![Box::leak(blob.into_boxed_slice())]); + super::super::build_index_from_sections_lazy(vec![blob]); let matched = index.match_records(0x3010).expect("both entries match"); let mut offsets: Vec = index .materialise(Some(&matched)) @@ -763,7 +759,7 @@ mod tests { (0x4080, 32, Vec::new()), ]); let index = - super::super::build_index_from_sections_lazy(vec![Box::leak(blob.into_boxed_slice())]); + super::super::build_index_from_sections_lazy(vec![blob]); assert_eq!( index.functions.len(), 1, @@ -794,7 +790,7 @@ mod tests { (0x6000, 64, vec![(0x08, vec![(31, 8), (31, 24), (29, -40)])]), (0x7000, 16, vec![(0x00, Vec::new())]), ]); - let leaked: &'static [u8] = Box::leak(blob.into_boxed_slice()); + let leaked: &'static [u8] = blob; let index = super::super::build_index_from_sections_lazy(vec![leaked]); let (records, roots, derived) = parse_gc_map(leaked, 0).expect("the eager parse succeeds"); let eager = super::super::index_records(records, roots, derived); @@ -817,108 +813,43 @@ mod tests { assert_eq!(eager.records.len(), 5); } - /// The v5 stream offsets must describe the stream that follows them. A - /// first offset that is not 0, or a non-monotonic one, is a map whose - /// per-function decode would start inside another function's records. + /// v9: a function's blob is exactly its offsets and its own stream. A + /// length that runs past the stream (into whatever follows) or stops + /// inside it describes another layout; the eager parse refuses both. #[test] - fn rejects_stream_offsets_that_do_not_describe_the_stream() { + fn rejects_a_records_length_that_does_not_end_the_stream() { let good = super::super::lazy::test_blob_multi(&[ (0x1000, 32, vec![(0x00, vec![(29, -8)])]), (0x2000, 32, vec![(0x00, vec![(29, -8)])]), ]); - // v8: each directory entry carries its stream offset at +16. - let first = DIRECTORY_HEADER_BYTES + 16; - let second = first + FUNCTION_ENTRY_BYTES; - let parse = |bytes: &[u8]| { - super::super::lazy::parse_function_table(bytes, 0, &mut Vec::new(), &mut Vec::new()) - .is_some() - }; - assert!(parse(&good), "the unmodified blob must parse"); - - let mut first_nonzero = good.clone(); - first_nonzero[first] = 1; - assert!( - !parse(&first_nonzero), - "the first function's records start at 0 in the stream" - ); - - let mut backwards = good.clone(); - backwards[second] = 0; - backwards[first] = 4; - assert!( - !parse(&backwards), - "stream offsets are emitted in stream order" - ); - } - - /// v8: each entry names its first record. They are a running sum in - /// emission order; an entry naming another function's first record would - /// pair this function's records with that one's instruction offsets. - #[test] - fn rejects_first_records_that_are_not_a_running_sum() { - let good = super::super::lazy::test_blob_multi(&[ - ( - 0x1000, - 32, - vec![(0x00, vec![(29, -8)]), (0x08, vec![(29, -8)])], - ), - (0x2000, 32, vec![(0x00, vec![(29, -8)])]), - ]); - let second_first_record = DIRECTORY_HEADER_BYTES + FUNCTION_ENTRY_BYTES + 12; - assert_eq!(good[second_first_record], 2); - let mut shifted = good.clone(); - shifted[second_first_record] = 1; - assert!(super::super::lazy::parse_function_table( - &shifted, - 0, - &mut Vec::new(), - &mut Vec::new() - ) - .is_none()); - assert!(parse_gc_map(&shifted, 0).is_none()); + assert!(parse_gc_map(good, 0).is_some(), "the unmodified section must parse"); + let len = u32::from_le_bytes(good[16..20].try_into().unwrap()); + for wrong in [len + 1, len - 1] { + let bytes = corruptible(good); + bytes[16..20].copy_from_slice(&wrong.to_le_bytes()); + assert!(parse_gc_map(bytes, 0).is_none(), "records_len {wrong} (true {len})"); + } } - /// v8: a records blob that overlaps its own directory is a header that - /// lies about where the records are; both decoders refuse it. + /// A records blob that overlaps the entries is an entry that lies about + /// where its records are; both decoders refuse it. #[test] - fn rejects_records_that_overlap_the_directory() { - let mut bytes = simple(0x1000, 0x10, -8); - bytes[16..20].copy_from_slice(&8i32.to_le_bytes()); - assert!(parse_gc_map(&bytes, 0).is_none()); - assert!(super::super::lazy::parse_function_table( - &bytes, - 0, - &mut Vec::new(), - &mut Vec::new() - ) - .is_none()); + fn rejects_records_that_overlap_the_entries() { + let bytes = corruptible(simple(0x1000, 0x10, -8)); + bytes[12..16].copy_from_slice(&8i32.to_le_bytes()); + assert!(parse_gc_map(bytes, 0).is_none()); + assert!(super::super::lazy::parse_function_table(bytes, 0, &mut Vec::new()).is_none()); } - /// v8: the lazy build reads directories and nothing else. Records that - /// live outside the section slice the index was built from — as the - /// compiler emits them, in a section of their own — are still found - /// through the header's self-relative offset. + /// v9: the lazy build reads entries and nothing else. Records that live + /// outside the section slice the index was built from — as the compiler + /// emits them, in a section of their own — are found through the entry's + /// self-relative offset (`test_section_at` returns only the entries). #[test] - fn records_outside_the_directory_section_are_found_by_offset() { - let inline = + fn records_outside_the_entry_section_are_found_by_offset() { + let section = super::super::lazy::test_blob_multi(&[(0x1000, 32, vec![(0x10, vec![(29, -8)])])]); - let directory_len = DIRECTORY_HEADER_BYTES + FUNCTION_ENTRY_BYTES; - // Lay the records out BEFORE the directory in one allocation, and hand - // the index only the directory: the section-split shape. - let records = &inline[directory_len..]; - let mut image = Vec::new(); - image.extend_from_slice(records); - while image.len() % 8 != 0 { - image.push(0); - } - let directory_at = image.len(); - image.extend_from_slice(&inline[..directory_len]); - let records_offset = -(directory_at as i32); - image[directory_at + 12..directory_at + 16] - .copy_from_slice(&(directory_len as u32).to_le_bytes()); - image[directory_at + 16..directory_at + 20].copy_from_slice(&records_offset.to_le_bytes()); - let image: &'static [u8] = Box::leak(image.into_boxed_slice()); - let section = &image[directory_at..]; + assert_eq!(section.len(), FUNCTION_ENTRY_BYTES); let index = super::super::build_index_from_sections_lazy(vec![section]); let matched = index.match_records(0x1010).expect("the record is found"); let roots: Vec<_> = index @@ -1248,7 +1179,7 @@ mod cross_check_tests { (0x2000, 48, vec![(0x00, vec![(31, 40), (31, 56)])]), ]); build_index_from_sections( - vec![Box::leak(blob.into_boxed_slice())], + vec![blob], &[0], IndexMode::CrossCheck, ) @@ -1274,8 +1205,9 @@ mod cross_check_tests { #[should_panic(expected = "disagrees with the whole-section index")] fn a_stream_offset_naming_another_functions_records_is_caught() { let mut index = two_function_index(); - let first = index.functions[0].stream_at; - index.functions[1].stream_at = first; + let first = index.functions[0]; + index.functions[1].records = first.records; + index.functions[1].records_len = first.records_len; let matched = index.match_records(0x2000); index.cross_check(0x2000, matched.as_ref()); } @@ -1286,8 +1218,9 @@ mod cross_check_tests { #[should_panic(expected = "disagrees with the whole-section index")] fn the_reverse_swap_is_caught_too() { let mut index = two_function_index(); - let second = index.functions[1].stream_at; - index.functions[0].stream_at = second; + let second = index.functions[1]; + index.functions[0].records = second.records; + index.functions[0].records_len = second.records_len; let matched = index.match_records(0x1000); index.cross_check(0x1000, matched.as_ref()); } diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs index 0f413c52315..9bda02931d6 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_frame_walk_tests.rs @@ -200,7 +200,7 @@ fn index_for(state: &State, with_inner: bool) -> StackMapIndex { let origin = functions[0].0 as usize; let blob = super::lazy::test_blob_multi_at(origin as u64, &functions); let mut index = super::build_index_from_sections( - vec![Box::leak(blob.into_boxed_slice())], + vec![blob], &[origin], super::IndexMode::Lazy, ); diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_index.rs b/crates/perry-runtime/src/gc/roots/stack_maps_index.rs index 9c3145494de..e9b08b6bb49 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_index.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_index.rs @@ -33,7 +33,6 @@ impl StackMapIndex { pub(super) fn match_records(&self, ip: usize) -> Option { let matched = lazy::match_records( &self.functions, - &self.records, ip, MAX_SAFEPOINT_RETURN_DELTA, ); @@ -54,7 +53,7 @@ impl StackMapIndex { } pub(super) fn matched(&self, matched: &lazy::RecordMatch) -> lazy::MatchedRecords<'_> { - lazy::MatchedRecords::new(&self.functions, &self.records, matched) + lazy::MatchedRecords::new(&self.functions, matched) } /// Materialise a match, for the cross-check and the diagnostic dump only. diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs b/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs index 4ab2b1a0be7..6c421ca9687 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_lazy.rs @@ -30,23 +30,18 @@ use super::{StackMapDerived, StackMapLocation, DWARF_REG_FP_AARCH64, DWARF_REG_S /// One function's position in the map, in the form the root scan needs. /// /// 32 bytes, one per function with records: 2.3 MB for claude-code's 72,669, -/// against the ~117 MB of materialised records it replaces. Byte offsets are -/// relative to the function's records blob rather than pointers, so the entry -/// stays small and a stale entry cannot be dereferenced without going through -/// `records[blob]`, whose length is also the bound every varint read in this -/// function is checked against. +/// against the ~117 MB of materialised records it replaces. v9 gives every +/// function its own records blob, so the entry names it directly; its length +/// is the bound every varint read in this function is checked against. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(super) struct FunctionEntry { - /// Relocated function address, from the map's directory. + /// Relocated function address, from the map's entry. pub(super) address: usize, - /// Offset of this function's first instruction offset in its records - /// blob's fixed-width `u32` array. - pub(super) offsets_at: u32, - /// Offset of this function's first record header in its records blob. - pub(super) stream_at: u32, + /// Address of this function's records blob: `record_count` u32 + /// instruction offsets, then its stream. + pub(super) records: usize, + pub(super) records_len: u32, pub(super) record_count: u32, - /// Index into the index's records-blob list. - pub(super) blob: u32, /// The largest instruction offset of this function's records. An `ip` /// more than the match window beyond it matches nothing here, which /// [`match_records`] then says in one search instead of scanning every @@ -58,6 +53,21 @@ pub(super) struct FunctionEntry { pub(super) stack_size: u32, } +impl FunctionEntry { + /// The function's records blob. + pub(super) fn bytes(&self) -> &'static [u8] { + // SAFETY: built from a validated entry of a loaded image's map (or a + // test's leaked allocation); image sections stay mapped while their + // code can be on a stack. + unsafe { core::slice::from_raw_parts(self.records as *const u8, self.records_len as usize) } + } + + /// Offset of the stream within [`Self::bytes`]. + pub(super) fn stream_at(&self) -> usize { + self.record_count as usize * 4 + } +} + /// Where one record's live set lives in the stream. /// /// A repeat record carries the PREVIOUS record's payload, which is how 63.1% @@ -107,15 +117,6 @@ impl<'a> RecordWalk<'a> { } } - /// The cursor is not reset between functions by the caller that walks a - /// whole blob; `parse_gc_map` re-arms per function so the repeat chain - /// cannot cross a function boundary, exactly as the v4 decoder did (its - /// `previous` was declared inside the function loop). - pub(super) fn restart(&mut self, count: u32) { - self.previous = None; - self.remaining = count; - } - pub(super) fn cursor(&self) -> usize { self.cursor } @@ -403,52 +404,39 @@ pub(super) fn unzigzag(value: u32) -> i32 { ((value >> 1) as i32) ^ -((value & 1) as i32) } -/// Walk every directory and append one [`FunctionEntry`] per function that -/// has records, and one records blob per directory. +/// Walk every entry and append one [`FunctionEntry`] per function that has +/// records. /// -/// This is the whole of what a lazy build reads: v8 directories, 1.4 MB of +/// This is the whole of what a lazy build reads: the entries, 1.4 MB of /// claude-code's map, against the 22.7 MB read and ~117 MB written by the /// eager build. The records themselves are not touched until a walk names a /// frame of theirs. /// /// `origin` is the runtime address of `bytes[0]`; the v6 function fields are -/// offsets from their blob, so the table cannot be read without it. -pub(super) fn parse_function_table<'a>( - bytes: &'a [u8], +/// offsets from their entry, so the table cannot be read without it. +pub(super) fn parse_function_table( + bytes: &'static [u8], origin: usize, out: &mut Vec, - blobs: &mut Vec<&'a [u8]>, ) -> Option<()> { - super::decode::for_each_directory(bytes, |directory| { - let blob = u32::try_from(blobs.len()).ok()?; - blobs.push(directory.records); - let stream_base = directory.stream_base(); - for index in 0..directory.function_count { - let function = directory.function(bytes, origin, index)?; - // A function with no records contributes nothing and MUST NOT - // enter the table: it would sit between two real functions and - // shadow the containing one for every `ip` inside it — a wrong - // (empty) live set rather than a missing entry. The v4 index - // derived its function list from records and so excluded these by - // construction; excluding them here keeps that property. - if function.record_count == 0 { - continue; - } - let offsets_at = function.first_record.checked_mul(4)?; - let offsets = directory.records.get( - offsets_at - ..offsets_at.checked_add((function.record_count as usize).checked_mul(4)?)?, - )?; - out.push(FunctionEntry { - address: function.address, - offsets_at: u32::try_from(offsets_at).ok()?, - stream_at: u32::try_from(stream_base.checked_add(function.stream_offset)?).ok()?, - record_count: function.record_count, - blob, - max_offset: max_record_offset(offsets), - stack_size: function.stack_size, - }); + super::decode::for_each_entry(bytes, origin, |entry| { + // A function with no records contributes nothing and MUST NOT enter + // the table: it would sit between two real functions and shadow the + // containing one for every `ip` inside it — a wrong (empty) live set + // rather than a missing entry. The v4 index derived its function list + // from records and so excluded these by construction; excluding them + // here keeps that property. + if entry.record_count == 0 { + return Some(()); } + out.push(FunctionEntry { + address: entry.address, + records: entry.records.as_ptr() as usize, + records_len: u32::try_from(entry.records.len()).ok()?, + record_count: entry.record_count, + max_offset: max_record_offset(entry.records.get(..entry.stream_at())?), + stack_size: entry.stack_size, + }); Some(()) }) } @@ -560,7 +548,6 @@ pub(super) struct RecordMatch { /// to express. pub(super) fn match_records( functions: &[FunctionEntry], - blobs: &[&'static [u8]], ip: usize, max_delta: usize, ) -> Option { @@ -586,9 +573,9 @@ pub(super) fn match_records( let mut best: Option<(u32, u32)> = None; // (distance, offset) for entry in &functions[start..end] { - let bytes = *blobs.get(entry.blob as usize)?; + let bytes = entry.bytes(); for index in 0..entry.record_count { - let at = entry.offsets_at as usize + index as usize * 4; + let at = index as usize * 4; let Some(offset) = read_u32(bytes, at) else { continue; }; @@ -615,21 +602,15 @@ pub(super) fn match_records( /// inside it — without the borrow of the index outliving the body. pub(super) struct MatchedRecords<'a> { functions: &'a [FunctionEntry], - blobs: &'a [&'static [u8]], range: core::ops::Range, offset: u32, walk: Option<(usize, RecordWalk<'static>, u32)>, } impl<'a> MatchedRecords<'a> { - pub(super) fn new( - functions: &'a [FunctionEntry], - blobs: &'a [&'static [u8]], - matched: &RecordMatch, - ) -> Self { + pub(super) fn new(functions: &'a [FunctionEntry], matched: &RecordMatch) -> Self { Self { functions, - blobs, range: matched.functions.clone(), offset: matched.offset, walk: None, @@ -642,24 +623,19 @@ impl<'a> MatchedRecords<'a> { if self.walk.is_none() { let index = self.range.next()?; let entry = self.functions.get(index)?; - let bytes = *self.blobs.get(entry.blob as usize)?; + let bytes = entry.bytes(); self.walk = Some(( index, - RecordWalk::new( - bytes, - bytes.len(), - entry.stream_at as usize, - entry.record_count, - ), + RecordWalk::new(bytes, bytes.len(), entry.stream_at(), entry.record_count), 0, )); } let (index, walk, record) = self.walk.as_mut()?; let entry = self.functions.get(*index)?; - let bytes = *self.blobs.get(entry.blob as usize)?; + let bytes = entry.bytes(); match walk.next() { Step::Record(payload) => { - let at = entry.offsets_at as usize + *record as usize * 4; + let at = *record as usize * 4; let offset = read_u32(bytes, at); *record += 1; if offset == Some(self.offset) { @@ -697,7 +673,7 @@ pub(super) fn malformed(function_address: usize) -> ! { ); } -/// Build a v8 blob, mirroring `perry-codegen/src/gc_map.rs`. +/// Build a v9 map section, mirroring `perry-codegen/src/gc_map.rs`. /// /// Shared by every test that needs real section bytes rather than hand-made /// records — which, since the walkers decode from the section, is all of them. @@ -709,21 +685,21 @@ pub(super) fn test_blob( address: u64, stack_size: u32, records: &[(u32, Vec<(u16, i32)>)], -) -> Vec { +) -> &'static [u8] { test_blob_multi(&[(address, stack_size, records.to_vec())]) } -/// A blob read at origin 0 (what `build_index_from_sections_lazy` passes), so -/// each function field is simply its address — which must then fit in an i32. +/// A section read at origin 0 (what `build_index_from_sections_lazy` passes), +/// so each function field is its address less its entry's offset. #[cfg(test)] -pub(super) fn test_blob_multi(functions: &[TestFunction]) -> Vec { +pub(super) fn test_blob_multi(functions: &[TestFunction]) -> &'static [u8] { test_blob_multi_at(0, functions) } -/// A blob whose function fields are relative to `origin`: the address the -/// decoder will be told the blob's first byte lives at. +/// A section whose function fields are relative to `origin`: the address the +/// decoder will be told the section's first byte lives at. #[cfg(test)] -pub(super) fn test_blob_multi_at(origin: u64, functions: &[TestFunction]) -> Vec { +pub(super) fn test_blob_multi_at(origin: u64, functions: &[TestFunction]) -> &'static [u8] { fn push_varint(out: &mut Vec, mut value: u64) { while value >= 0x80 { out.push((value as u8 & 0x7F) | 0x80); @@ -734,73 +710,66 @@ pub(super) fn test_blob_multi_at(origin: u64, functions: &[TestFunction]) -> Vec fn zigzag(value: i32) -> u64 { ((value << 1) ^ (value >> 31)) as u32 as u64 } - - let mut offsets = Vec::new(); - let mut stream = Vec::new(); - let mut stream_offsets = Vec::new(); - for (_, _, records) in functions { - stream_offsets.push(stream.len() as u32); - for (instruction_offset, roots) in records { - offsets.extend_from_slice(&instruction_offset.to_le_bytes()); - push_varint(&mut stream, (roots.len() as u64) << 2); - let mut last: Option = None; - for (reg, offset) in roots { - let tag = match *reg { - DWARF_REG_FP_AARCH64 => 0u64, - DWARF_REG_SP_AARCH64 => 1, - _ => 2, - }; - let delta = match last { - None => *offset, - Some(previous) => offset.wrapping_sub(previous), - }; - push_varint(&mut stream, (zigzag(delta) << 2) | tag); - if tag == 2 { - push_varint(&mut stream, u64::from(*reg)); + let maps: Vec<(u64, u32, Vec, Vec)> = functions + .iter() + .map(|(address, stack_size, records)| { + let mut stream = Vec::new(); + for (_, roots) in records { + push_varint(&mut stream, (roots.len() as u64) << 2); + let mut last: Option = None; + for (reg, offset) in roots { + let tag = match *reg { + DWARF_REG_FP_AARCH64 => 0u64, + DWARF_REG_SP_AARCH64 => 1, + _ => 2, + }; + let delta = match last { + None => *offset, + Some(previous) => offset.wrapping_sub(previous), + }; + push_varint(&mut stream, (zigzag(delta) << 2) | tag); + if tag == 2 { + push_varint(&mut stream, u64::from(*reg)); + } + last = Some(*offset); } - last = Some(*offset); } - } - } + let offsets = records.iter().map(|(offset, _)| *offset).collect(); + (*address, *stack_size, offsets, stream) + }) + .collect(); + test_section_at(origin, &maps) +} - // v8, in the in-line form: the records follow the directory inside the - // blob's stride, so a test section stays one self-contained byte string - // that can be concatenated like linker input sections. The compiler puts - // them in a section of their own; the decoder reads both the same way, - // through the header's self-relative `records_offset`. - let header = super::decode::DIRECTORY_HEADER_BYTES; +/// The v9 section for `(address, stack_size, instruction offsets, stream)` +/// per function: the entries, as the linker concatenates them, with every +/// records blob after them in the same leaked allocation (the compiler puts +/// them in a section of their own; the decoder finds both through the +/// entry's self-relative offset). Only the entries are returned. +#[cfg(test)] +pub(super) fn test_section_at(origin: u64, maps: &[(u64, u32, Vec, Vec)]) -> &'static [u8] { let entry = super::decode::FUNCTION_ENTRY_BYTES; - let directory_len = header + functions.len() * entry; - let records_len = offsets.len() + stream.len(); - let record_total = offsets.len() / 4; - let mut bytes = Vec::new(); - bytes.extend_from_slice(super::GC_MAP_MAGIC); - bytes.push(super::GC_MAP_VERSION); - bytes.push(0); - bytes.extend_from_slice(&0u16.to_le_bytes()); - bytes.extend_from_slice(&(functions.len() as u32).to_le_bytes()); - bytes.extend_from_slice(&((directory_len + records_len) as u32).to_le_bytes()); - bytes.extend_from_slice(&(directory_len as i32).to_le_bytes()); - bytes.extend_from_slice(&(records_len as u32).to_le_bytes()); - bytes.extend_from_slice(&(record_total as u32).to_le_bytes()); - bytes.extend_from_slice(&0u32.to_le_bytes()); - let mut first_record = 0u32; - for ((address, stack_size, records), stream_offset) in functions.iter().zip(&stream_offsets) { - let offset = i32::try_from(address.wrapping_sub(origin) as i64) - .expect("a test function must sit within +-2 GiB of its blob"); - bytes.extend_from_slice(&offset.to_le_bytes()); - bytes.extend_from_slice(&stack_size.to_le_bytes()); - bytes.extend_from_slice(&(records.len() as u32).to_le_bytes()); - bytes.extend_from_slice(&first_record.to_le_bytes()); - bytes.extend_from_slice(&stream_offset.to_le_bytes()); - first_record += records.len() as u32; - } - bytes.extend_from_slice(&offsets); - bytes.extend_from_slice(&stream); - while bytes.len() % 8 != 0 { - bytes.push(0); + let entries_len = maps.len() * entry; + let mut bytes = vec![0u8; entries_len]; + for (index, (address, stack_size, offsets, stream)) in maps.iter().enumerate() { + let at = index * entry; + let records_at = bytes.len(); + for offset in offsets { + bytes.extend_from_slice(&offset.to_le_bytes()); + } + bytes.extend_from_slice(stream); + let field = i32::try_from(address.wrapping_sub(origin + at as u64) as i64) + .expect("a test function must sit within +-2 GiB of its entry"); + let counted = offsets.len() as u32 | u32::from(super::GC_MAP_VERSION) << 24; + bytes[at..at + 4].copy_from_slice(&field.to_le_bytes()); + bytes[at + 4..at + 8].copy_from_slice(&stack_size.to_le_bytes()); + bytes[at + 8..at + 12].copy_from_slice(&counted.to_le_bytes()); + bytes[at + 12..at + 16].copy_from_slice(&((records_at - at) as i32).to_le_bytes()); + let records_len = (bytes.len() - records_at) as u32; + bytes[at + 16..at + 20].copy_from_slice(&records_len.to_le_bytes()); } - bytes + let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice()); + &leaked[..entries_len] } #[cfg(test)] diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_walker_agreement.rs b/crates/perry-runtime/src/gc/roots/stack_maps_walker_agreement.rs index dab48ef5c6e..5138d48e895 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_walker_agreement.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_walker_agreement.rs @@ -167,7 +167,7 @@ fn index_for(frame: Frame, return_address: usize, offset: i32) -> StackMapIndex // Leaked on purpose: the index holds `&'static` section slices because the // real sections are parts of loaded images, which outlive every collection. super::build_index_from_sections( - vec![Box::leak(blob.into_boxed_slice())], + vec![blob], &[origin], super::IndexMode::Lazy, ) diff --git a/crates/perry/src/commands/compile/link/function_layout.rs b/crates/perry/src/commands/compile/link/function_layout.rs index 7ea2f53454c..ce5caceedb3 100644 --- a/crates/perry/src/commands/compile/link/function_layout.rs +++ b/crates/perry/src/commands/compile/link/function_layout.rs @@ -12,17 +12,29 @@ pub(super) fn add_link_args( direct_apple: bool, xlinker_apple: bool, ) -> Result<()> { - let perry_codegen::FunctionLayout::Order(order) = perry_codegen::program_function_layout() - else { - return Ok(()); - }; - if order.is_empty() { - return Ok(()); - } let dir = cache.join("function-order"); + let elf = linux && !android && !harmony; + let order = match perry_codegen::program_function_layout() { + perry_codegen::FunctionLayout::Order(order) if !order.is_empty() => order, + _ => { + // Every JS body has its own input section (so the link can drop + // each one nothing references); gather them into the one JS text + // output section whose bounds the stack capture reads. + if elf { + fs::create_dir_all(&dir)?; + let rule = perry_codegen::function_order::JS_TEXT_LINK_RULE; + let script = dir.join("js-text.v1.ld"); + if fs::read_to_string(&script).ok().as_deref() != Some(rule) { + write_atomically(&script, rule)?; + } + cmd.arg(format!("-Wl,-T,{}", script.display())); + } + return Ok(()); + } + }; fs::create_dir_all(&dir)?; - if linux && !android && !harmony { - let script = dir.join(format!("{:016x}.v4.ld", order.digest())); + if elf { + let script = dir.join(format!("{:016x}.v5.ld", order.digest())); fs::write( &script, order.elf_linker_script().map_err(anyhow::Error::msg)?, @@ -56,3 +68,12 @@ pub(super) fn add_link_args( } Ok(()) } + +/// Concurrent links share the cache: a reader never sees a half-written +/// script (write a sibling, then rename over the final name). +fn write_atomically(path: &Path, contents: &str) -> Result<()> { + let tmp = path.with_extension(format!("tmp{}", std::process::id())); + fs::write(&tmp, contents)?; + fs::rename(&tmp, path)?; + Ok(()) +} diff --git a/scripts/a1/linkpad.py b/scripts/a1/linkpad.py index 2e85181ba59..10f8454827f 100755 --- a/scripts/a1/linkpad.py +++ b/scripts/a1/linkpad.py @@ -299,12 +299,21 @@ def anchored(pads, mk_m, mk_h, st_m, st_h): return False +def comparable_names(secs_m, secs_h): + """Section names of both arms, in order, for the layout comparison. A perry_* records section that only one + arm has (an arm that adds one) is never padded: like the size difference of any records section, its bytes + are taken up by the next padded section, so it is left out of the order check.""" + m, h = [s[0] for s in secs_m], [s[0] for s in secs_h] + one_arm = {n for n in set(m) ^ set(h) if n.startswith("perry_")} + return [n for n in m if n not in one_arm], [n for n in h if n not in one_arm] + + def plan(main_bin, head_bin, pads_json, pad_dir): empty = lambda: {"mid": {}, "end": {}} pads = json.load(open(pads_json)) if os.path.exists(pads_json) else {"main": empty(), "head": empty()} secs_m, mk_m, cnt_m = marks(main_bin) secs_h, mk_h, cnt_h = marks(head_bin) - names_m, names_h = [s[0] for s in secs_m], [s[0] for s in secs_h] + names_m, names_h = comparable_names(secs_m, secs_h) if names_m != names_h: sys.exit(f"section order differs:\n main {names_m}\n head {names_h}") missing = [n for n in list(PADDABLE) + [EH] if n in names_m and n not in mk_m or n in names_h and n not in mk_h] @@ -358,7 +367,7 @@ def check(main_bin, head_bin, rt_lib=None): ok = True secs_m, mk_m, cnt_m = marks(main_bin) secs_h, mk_h, cnt_h = marks(head_bin) - names_m, names_h = [s[0] for s in secs_m], [s[0] for s in secs_h] + names_m, names_h = comparable_names(secs_m, secs_h) if names_m != names_h: print(f"section order differs:\n main {names_m}\n head {names_h}") print("LAYOUT-DIFFERS") From ffcd11880510d7622e23c547981fdb50287dfe74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 00:46:40 +0100 Subject: [PATCH 110/144] changelog: number the PR fragment --- .../{0-dce-layer0-linker.md => 13636-dce-layer0-linker.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0-dce-layer0-linker.md => 13636-dce-layer0-linker.md} (100%) diff --git a/changelog.d/0-dce-layer0-linker.md b/changelog.d/13636-dce-layer0-linker.md similarity index 100% rename from changelog.d/0-dce-layer0-linker.md rename to changelog.d/13636-dce-layer0-linker.md From f82ca7dcd9cdb7af4d6d66a7f08a0901246d1997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 1 Nov 2026 08:39:39 +0100 Subject: [PATCH 111/144] docs: refresh runtime, memory and program facts across languages --- .../pending-runtime-docs-all-languages.md | 16 + docs/TRANSLATING.md | 15 +- docs/po/de.po | 10525 ++++++++------- docs/po/es.po | 10500 ++++++++------- docs/po/fr.po | 10530 +++++++++------- docs/po/id.po | 10467 ++++++++------- docs/po/it.po | 10496 ++++++++------- docs/po/ja.po | 10284 ++++++++------- docs/po/ko.po | 10225 ++++++++------- docs/po/messages.pot | 8407 ++++++------ docs/po/th.po | 10288 ++++++++------- docs/po/vi.po | 10422 ++++++++------- docs/po/zh-CN.po | 10122 ++++++++------- docs/src/SUMMARY.md | 1 + docs/src/cli/flags.md | 4 +- docs/src/contributing/architecture.md | 42 +- docs/src/internals/garbage-collector.md | 62 +- .../internals/local-binding-type-evidence.md | 2 + docs/src/internals/memory-model.md | 46 +- docs/src/internals/program-facts.md | 13 + docs/src/language/limitations.md | 163 +- docs/src/language/supported-features.md | 8 +- docs/src/language/type-system.md | 14 +- docs/src/threading/overview.md | 29 +- 24 files changed, 59887 insertions(+), 52794 deletions(-) create mode 100644 changelog.d/pending-runtime-docs-all-languages.md create mode 100644 docs/src/internals/program-facts.md diff --git a/changelog.d/pending-runtime-docs-all-languages.md b/changelog.d/pending-runtime-docs-all-languages.md new file mode 100644 index 00000000000..4d3d52d4513 --- /dev/null +++ b/changelog.d/pending-runtime-docs-all-languages.md @@ -0,0 +1,16 @@ +Refresh the runtime and compiler documentation against the current implementation. + +- Describe copying and non-moving collection, target-specific precise roots, + young non-moving large-object storage, inline births, native handles, weak + processing, and the distinction between JavaScript GC and Rust ownership. +- Explain per-module program-facts solving, cross-module contracts, field/call + specialization, guarded fallback, proof diagnostics, and generic versus native + representations. Correct stale eval, CommonJS, Node-API and threading claims. +- Translate the revised passages into all ten published languages and regenerate + gettext catalogs with the pinned toolchain. Keep section anchors stable across + translations. Existing unrelated translation gaps retain current-English fallback. + +Validation: English and all ten localized mdBook builds; gettext syntax/format +checks; translated-passage coverage and code/link preservation; catalog extraction +and sync reproducibility; GC claim/knob checks and i18n toolchain tests. This is a +documentation-only change; no compiler or runtime behavior changes. diff --git a/docs/TRANSLATING.md b/docs/TRANSLATING.md index 7fe98dff7f8..f19b57b96fa 100644 --- a/docs/TRANSLATING.md +++ b/docs/TRANSLATING.md @@ -10,12 +10,15 @@ Site layout: Japanese (`ja`), Korean (`ko`), Thai (`th`), Vietnamese (`vi`), and Simplified Chinese (`zh-CN`). -The current catalogs contain a translation for every source message. Much of -that coverage was generated automatically and has not been reviewed by a -native speaker, so complete coverage is not a guarantee of publication-ready -language. Review prose for technical meaning and natural phrasing rather than -treating a non-empty `msgstr` as automatically approved. Future source edits -can introduce empty, fuzzy, or missing entries; those fall back to English. +Catalog coverage varies as the English source changes. Empty, fuzzy, or missing +entries fall back to current English. A synchronized catalog therefore does not +imply complete translation coverage. The runtime documentation refresh translates +its changed GC, memory, types, program-facts and compatibility passages in every +listed language; unrelated existing gaps still need review. + +Much of the catalog prose was generated automatically and has not been reviewed +by a native speaker. Review technical meaning and natural phrasing rather than +treating a non-empty `msgstr` as automatically approved. ## Translating an existing language diff --git a/docs/po/de.po b/docs/po/de.po index 9a1822992c9..584e61761aa 100644 --- a/docs/po/de.po +++ b/docs/po/de.po @@ -65,7 +65,7 @@ msgid "Native Layout Values" msgstr "Werte mit nativem Speicherlayout" #: src/SUMMARY.md:21 src/language/decorators.md:1 -#: src/language/limitations.md:176 src/packages/porting.md:144 +#: src/language/limitations.md:85 src/packages/porting.md:144 msgid "Decorators" msgstr "Dekoratoren" @@ -222,7 +222,7 @@ msgstr "Hooks" #: src/SUMMARY.md:68 src/threading/parallel-map.md:157 #: src/threading/parallel-filter.md:78 src/threading/spawn.md:191 -#: src/tui/examples.md:1 src/cli/flags.md:508 +#: src/tui/examples.md:1 src/cli/flags.md:530 msgid "Examples" msgstr "Beispiele" @@ -246,7 +246,7 @@ msgstr "Plattformen" #: src/system/audio.md:99 src/system/media.md:86 src/system/other.md:20 #: src/system/other.md:37 src/plugins/native-extensions.md:302 #: src/plugins/appstore-review.md:96 src/testing/geisterhand.md:259 -#: src/testing/geisterhand.md:331 src/cli/flags.md:103 +#: src/testing/geisterhand.md:331 src/cli/flags.md:125 #: src/cli/app-updates.md:125 msgid "macOS" msgstr "macOS" @@ -315,7 +315,7 @@ msgstr "Veröffentlichung im App Store" #: src/widgets/platforms.md:18 src/widgets/platforms.md:25 #: src/widgets/platforms.md:49 src/widgets/platforms.md:76 #: src/plugins/native-extensions.md:303 src/plugins/appstore-review.md:109 -#: src/testing/geisterhand.md:261 src/cli/flags.md:108 src/cli/flags.md:109 +#: src/testing/geisterhand.md:261 src/cli/flags.md:130 src/cli/flags.md:131 #: src/cli/perry-toml.md:528 msgid "Android" msgstr "Android" @@ -326,7 +326,7 @@ msgstr "Android" msgid "Wear OS" msgstr "Wear OS" -#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:117 +#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:139 msgid "HarmonyOS NEXT" msgstr "HarmonyOS NEXT" @@ -345,7 +345,7 @@ msgstr "HarmonyOS NEXT" #: src/system/keychain.md:28 src/system/notifications.md:123 #: src/system/audio.md:103 src/system/media.md:92 src/system/other.md:23 #: src/system/other.md:40 src/testing/geisterhand.md:263 -#: src/testing/geisterhand.md:392 src/cli/flags.md:121 +#: src/testing/geisterhand.md:392 src/cli/flags.md:143 #: src/cli/app-updates.md:127 msgid "Windows" msgstr "Windows" @@ -368,11 +368,11 @@ msgstr "Linux (GTK4)" #: src/system/preferences.md:39 src/system/keychain.md:30 #: src/system/notifications.md:125 src/system/audio.md:104 #: src/system/audio_module.md:99 src/system/media.md:95 src/system/other.md:25 -#: src/system/other.md:42 src/cli/flags.md:120 +#: src/system/other.md:42 src/cli/flags.md:142 msgid "Web" msgstr "Web" -#: src/SUMMARY.md:86 src/cli/flags.md:119 +#: src/SUMMARY.md:86 src/cli/flags.md:141 msgid "WebAssembly" msgstr "WebAssembly" @@ -642,11 +642,11 @@ msgstr "`PERRY_SANDBOX_BUILDRS`" msgid "`--emit-attest` (binary attestation sidecar)" msgstr "`--emit-attest` (binärer Attestierungsanhang)" -#: src/SUMMARY.md:178 src/cli/flags.md:428 +#: src/SUMMARY.md:178 src/cli/flags.md:450 msgid "`--emit-sandbox`" msgstr "`--emit-sandbox`" -#: src/SUMMARY.md:179 src/cli/flags.md:429 +#: src/SUMMARY.md:179 src/cli/flags.md:451 msgid "`--lockdown`" msgstr "`--lockdown`" @@ -719,53 +719,57 @@ msgstr "Explizite Speicherverwaltung" msgid "The GC rooting invariant (codegen)" msgstr "Die GC-Rooting-Invariante (Codegenerierung)" -#: src/SUMMARY.md:201 src/internals/local-binding-type-evidence.md:1 +#: src/SUMMARY.md:201 src/internals/program-facts.md:1 +msgid "Program facts" +msgstr "Programmfakten" + +#: src/SUMMARY.md:202 src/internals/local-binding-type-evidence.md:1 msgid "Local binding type evidence" msgstr "Typnachweise für lokale Bindungen" -#: src/SUMMARY.md:202 src/internals/codegen-mechanisms.md:1 +#: src/SUMMARY.md:203 src/internals/codegen-mechanisms.md:1 msgid "Codegen mechanisms and workload evidence" msgstr "" -#: src/SUMMARY.md:203 +#: src/SUMMARY.md:204 msgid "Incremental GC step bounds" msgstr "Grenzen inkrementeller GC-Schritte" -#: src/SUMMARY.md:204 src/internals/rfc-rooting-by-construction.md:1 +#: src/SUMMARY.md:205 src/internals/rfc-rooting-by-construction.md:1 msgid "RFC: rooting by construction" msgstr "RFC: Rooting durch Konstruktion" -#: src/SUMMARY.md:205 src/internals/node-api-host.md:1 +#: src/SUMMARY.md:206 src/internals/node-api-host.md:1 msgid "Node-API host design" msgstr "Node-API host design" -#: src/SUMMARY.md:206 src/internals/fallback-diagnostics.md:1 +#: src/SUMMARY.md:207 src/internals/fallback-diagnostics.md:1 #, fuzzy msgid "Generic fallback diagnostics" msgstr "Allgemeine Objektsemantik" -#: src/SUMMARY.md:208 +#: src/SUMMARY.md:209 msgid "Contributing" msgstr "Mitwirken" -#: src/SUMMARY.md:210 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 +#: src/SUMMARY.md:211 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 #: src/testing/geisterhand.md:683 src/contributing/architecture.md:1 msgid "Architecture" msgstr "Architektur" -#: src/SUMMARY.md:211 src/contributing/crate-policy.md:1 +#: src/SUMMARY.md:212 src/contributing/crate-policy.md:1 msgid "Crate policy" msgstr "Crate-Richtlinie" -#: src/SUMMARY.md:212 src/contributing/building.md:1 +#: src/SUMMARY.md:213 src/contributing/building.md:1 msgid "Building from Source" msgstr "Aus dem Quellcode bauen" -#: src/SUMMARY.md:213 src/contributing/build-storage.md:1 +#: src/SUMMARY.md:214 src/contributing/build-storage.md:1 msgid "Linux agent build storage" msgstr "" -#: src/SUMMARY.md:214 src/contributing/releasing.md:1 +#: src/SUMMARY.md:215 src/contributing/releasing.md:1 msgid "Releasing Perry" msgstr "Perry veröffentlichen" @@ -1011,7 +1015,7 @@ msgstr "" "— kein Electron, keine WebView." #: src/introduction.md:81 src/getting-started/first-app.md:41 -#: src/threading/overview.md:323 src/threading/parallel-map.md:18 +#: src/threading/overview.md:308 src/threading/parallel-map.md:18 #: src/threading/parallel-filter.md:18 src/platforms/watchos.md:214 #: src/platforms/wasm.md:29 src/stdlib/overview.md:8 src/i18n/overview.md:78 #: src/widgets/overview.md:36 src/plugins/overview.md:7 @@ -1033,8 +1037,8 @@ msgstr "" #: src/introduction.md:96 src/getting-started/hello-world.md:155 #: src/getting-started/first-app.md:184 #: src/getting-started/project-config.md:327 -#: src/language/supported-features.md:631 src/language/type-system.md:149 -#: src/language/limitations.md:393 src/threading/overview.md:350 +#: src/language/supported-features.md:629 src/language/type-system.md:153 +#: src/language/limitations.md:250 src/threading/overview.md:392 #: src/ui/overview.md:239 src/ui/widgets.md:706 src/ui/layout.md:393 #: src/ui/styling.md:387 src/ui/state.md:226 src/ui/events.md:167 #: src/ui/canvas.md:93 src/ui/menus.md:119 src/ui/tray.md:106 @@ -1045,7 +1049,7 @@ msgstr "" #: src/platforms/watchos.md:316 src/platforms/android.md:97 #: src/platforms/wearos.md:208 src/platforms/windows.md:144 #: src/platforms/linux.md:84 src/platforms/web.md:21 src/platforms/wasm.md:191 -#: src/stdlib/overview.md:128 src/stdlib/fs.md:98 src/stdlib/http.md:408 +#: src/stdlib/overview.md:128 src/stdlib/fs.md:105 src/stdlib/http.md:408 #: src/stdlib/database.md:112 src/stdlib/crypto.md:87 #: src/stdlib/utilities.md:96 src/stdlib/other.md:333 src/i18n/overview.md:108 #: src/updater/overview.md:445 src/system/overview.md:77 @@ -1057,8 +1061,8 @@ msgstr "" #: src/plugins/overview.md:96 src/plugins/creating-plugins.md:162 #: src/plugins/hooks-and-events.md:144 src/plugins/native-extensions.md:324 #: src/plugins/appstore-review.md:199 src/cli/commands.md:437 -#: src/cli/flags.md:545 src/contributing/architecture.md:109 -#: src/contributing/building.md:201 +#: src/cli/flags.md:567 src/contributing/architecture.md:85 +#: src/contributing/building.md:233 msgid "Next Steps" msgstr "Nächste Schritte" @@ -1240,7 +1244,7 @@ msgstr "" #: src/system/background.md:78 src/system/other.md:18 src/system/other.md:35 #: src/widgets/data-fetching.md:142 src/widgets/platforms.md:14 #: src/plugins/native-extensions.md:298 src/testing/geisterhand.md:257 -#: src/cli/flags.md:96 src/cli/function-order.md:54 +#: src/cli/flags.md:118 src/cli/function-order.md:54 #: src/contributing/releasing.md:168 msgid "Platform" msgstr "Plattform" @@ -1265,7 +1269,7 @@ msgstr "macOS x64 (Intel)" msgid "`@perryts/perry-darwin-x64`" msgstr "`@perryts/perry-darwin-x64`" -#: src/getting-started/installation.md:72 src/cli/flags.md:126 +#: src/getting-started/installation.md:72 src/cli/flags.md:148 msgid "Linux x64 (glibc)" msgstr "Linux x64 (glibc)" @@ -1273,7 +1277,7 @@ msgstr "Linux x64 (glibc)" msgid "`@perryts/perry-linux-x64`" msgstr "`@perryts/perry-linux-x64`" -#: src/getting-started/installation.md:73 src/cli/flags.md:127 +#: src/getting-started/installation.md:73 src/cli/flags.md:149 msgid "Linux arm64 (glibc)" msgstr "Linux arm64 (glibc)" @@ -1297,7 +1301,7 @@ msgstr "Linux arm64 (musl / Alpine)" msgid "`@perryts/perry-linux-arm64-musl`" msgstr "`@perryts/perry-linux-arm64-musl`" -#: src/getting-started/installation.md:76 src/cli/flags.md:122 +#: src/getting-started/installation.md:76 src/cli/flags.md:144 msgid "Windows x64" msgstr "Windows x64" @@ -1534,7 +1538,7 @@ msgstr "" #: src/i18n/overview.md:94 src/updater/overview.md:271 #: src/system/preferences.md:38 src/system/keychain.md:29 #: src/system/notifications.md:124 src/system/audio.md:102 -#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:125 +#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:147 #: src/cli/app-updates.md:126 msgid "Linux" msgstr "Linux" @@ -2597,11 +2601,11 @@ msgstr "" #: src/getting-started/project-config.md:53 msgid "" -"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": " -"\"default\"` disables automatic Solid detection. A host setting overrides " -"per-file detection. The equivalent TOML setting is `[perry]` with `jsx = " -"{ runtime = \"@opentui/solid\" }`; TOML overrides the host's package " -"setting. React/Preact import sources keep their existing JSX behavior." +"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": \"default" +"\"` disables automatic Solid detection. A host setting overrides per-file " +"detection. The equivalent TOML setting is `[perry]` with `jsx = { runtime = " +"\"@opentui/solid\" }`; TOML overrides the host's package setting. React/" +"Preact import sources keep their existing JSX behavior." msgstr "" #: src/getting-started/project-config.md:59 @@ -2643,8 +2647,8 @@ msgid "" "Compiling third-party TypeScript into your binary is a privileged operation, " "so every entry in `compilePackages` must _also_ be matched by an entry in " "`perry.allow.compilePackages` in the same `package.json` — otherwise the " -"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/" -"*\"`), or the universal `\"*\"`:" +"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/*" +"\"`), or the universal `\"*\"`:" msgstr "" "**Zwei-Tasten-Opt-In.** Die Auflistung eines Pakets ist nicht allein " "ausreichend. Das Kompilieren von TypeScript von Drittanbietern in Ihre " @@ -3133,10 +3137,10 @@ msgstr "Feld" #: src/cli/commands.md:48 src/cli/commands.md:92 src/cli/commands.md:142 #: src/cli/commands.md:183 src/cli/commands.md:216 src/cli/commands.md:230 #: src/cli/commands.md:268 src/cli/commands.md:283 src/cli/commands.md:295 -#: src/cli/flags.md:80 src/cli/flags.md:135 src/cli/flags.md:143 -#: src/cli/flags.md:191 src/cli/flags.md:291 src/cli/flags.md:379 -#: src/cli/flags.md:399 src/cli/flags.md:412 src/cli/flags.md:419 -#: src/cli/flags.md:435 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 +#: src/cli/flags.md:102 src/cli/flags.md:157 src/cli/flags.md:165 +#: src/cli/flags.md:213 src/cli/flags.md:313 src/cli/flags.md:401 +#: src/cli/flags.md:421 src/cli/flags.md:434 src/cli/flags.md:441 +#: src/cli/flags.md:457 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 #: src/cli/perry-toml.md:156 src/cli/perry-toml.md:172 #: src/cli/perry-toml.md:182 src/cli/perry-toml.md:188 #: src/cli/perry-toml.md:198 src/cli/perry-toml.md:248 @@ -3340,7 +3344,7 @@ msgstr "" "Für nicht nativ unterstützte Pakete verwenden Sie `compilePackages` für " "reine TS/JS-Pakete oder den JavaScript-Runtime-Fallback für komplexe Pakete." -#: src/getting-started/project-config.md:289 src/contributing/building.md:177 +#: src/getting-started/project-config.md:289 src/contributing/building.md:209 msgid "Project Structure" msgstr "Projektstruktur" @@ -3535,10 +3539,11 @@ msgstr "Lokale Benachrichtigung" #: src/platforms/windows-7.md:25 src/container/overview.md:71 #: src/container/security.md:105 src/container/determinism.md:59 #: src/testing/geisterhand.md:87 src/testing/geisterhand.md:101 -#: src/cli/flags.md:562 src/cli/flags.md:668 src/cli/flags.md:669 +#: src/cli/flags.md:584 src/cli/flags.md:690 src/cli/flags.md:691 #: src/cli/dynamic-dispatch.md:123 src/cli/allow-js-runtime.md:67 #: src/cli/sandbox-buildrs.md:25 src/cli/sandbox-buildrs.md:53 -#: src/cli/perry-toml.md:431 src/internals/gc-rooting-invariant.md:285 +#: src/cli/perry-toml.md:431 src/internals/garbage-collector.md:463 +#: src/internals/gc-rooting-invariant.md:285 #: src/internals/codegen-mechanisms.md:56 #: src/internals/rfc-rooting-by-construction.md:354 #: src/contributing/releasing.md:202 @@ -3662,8 +3667,8 @@ msgid "" " // Arrow functions\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" msgstr "" @@ -3686,33 +3691,33 @@ msgstr "" " // Arrow-Funktionen\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" -#: src/language/supported-features.md:60 src/api/reference.md:223 -#: src/api/reference.md:257 src/api/reference.md:291 src/api/reference.md:349 -#: src/api/reference.md:373 src/api/reference.md:462 src/api/reference.md:509 -#: src/api/reference.md:530 src/api/reference.md:555 src/api/reference.md:830 -#: src/api/reference.md:913 src/api/reference.md:961 src/api/reference.md:981 -#: src/api/reference.md:1090 src/api/reference.md:1168 -#: src/api/reference.md:1207 src/api/reference.md:1256 -#: src/api/reference.md:1284 src/api/reference.md:1445 -#: src/api/reference.md:1653 src/api/reference.md:1676 -#: src/api/reference.md:1730 src/api/reference.md:1761 -#: src/api/reference.md:1815 src/api/reference.md:1859 -#: src/api/reference.md:1993 src/api/reference.md:2135 -#: src/api/reference.md:2371 src/api/reference.md:2860 -#: src/api/reference.md:2873 src/api/reference.md:2944 -#: src/api/reference.md:3006 src/api/reference.md:3115 -#: src/api/reference.md:3141 src/api/reference.md:3154 -#: src/api/reference.md:3165 src/api/reference.md:3292 -#: src/api/reference.md:3337 src/api/reference.md:3378 -#: src/api/reference.md:3398 src/api/reference.md:3427 -#: src/api/reference.md:3523 src/api/reference.md:3581 -#: src/api/reference.md:3617 src/api/reference.md:3635 -#: src/api/reference.md:3683 src/api/reference.md:3723 +#: src/language/supported-features.md:60 src/api/reference.md:224 +#: src/api/reference.md:258 src/api/reference.md:292 src/api/reference.md:351 +#: src/api/reference.md:375 src/api/reference.md:464 src/api/reference.md:511 +#: src/api/reference.md:532 src/api/reference.md:557 src/api/reference.md:832 +#: src/api/reference.md:915 src/api/reference.md:963 src/api/reference.md:983 +#: src/api/reference.md:1092 src/api/reference.md:1170 +#: src/api/reference.md:1209 src/api/reference.md:1258 +#: src/api/reference.md:1286 src/api/reference.md:1447 +#: src/api/reference.md:1655 src/api/reference.md:1678 +#: src/api/reference.md:1732 src/api/reference.md:1763 +#: src/api/reference.md:1817 src/api/reference.md:1861 +#: src/api/reference.md:1995 src/api/reference.md:2137 +#: src/api/reference.md:2376 src/api/reference.md:2869 +#: src/api/reference.md:2882 src/api/reference.md:2953 +#: src/api/reference.md:3015 src/api/reference.md:3124 +#: src/api/reference.md:3150 src/api/reference.md:3163 +#: src/api/reference.md:3174 src/api/reference.md:3301 +#: src/api/reference.md:3357 src/api/reference.md:3398 +#: src/api/reference.md:3418 src/api/reference.md:3447 +#: src/api/reference.md:3543 src/api/reference.md:3601 +#: src/api/reference.md:3637 src/api/reference.md:3655 +#: src/api/reference.md:3703 src/api/reference.md:3743 msgid "Classes" msgstr "Klassen" @@ -3879,7 +3884,7 @@ msgstr "" "Interfaces und Typ-Aliase werden zur Compile-Zeit gelöscht (wie `tsc`). Sie " "existieren nur zur Dokumentation und für Editor-Tooling." -#: src/language/supported-features.md:144 src/threading/overview.md:343 +#: src/language/supported-features.md:144 src/threading/overview.md:328 #: src/threading/parallel-map.md:58 msgid "Arrays" msgstr "Arrays" @@ -3924,11 +3929,11 @@ msgid "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" msgstr "" @@ -3970,15 +3975,15 @@ msgstr "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" -#: src/language/supported-features.md:188 src/threading/overview.md:344 +#: src/language/supported-features.md:188 src/threading/overview.md:329 #: src/threading/parallel-map.md:59 msgid "Objects" msgstr "Objekte" @@ -4067,8 +4072,8 @@ msgid "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" msgstr "" @@ -4096,8 +4101,8 @@ msgstr "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" @@ -4117,8 +4122,8 @@ msgid "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" msgstr "" @@ -4132,8 +4137,8 @@ msgstr "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" @@ -4188,7 +4193,7 @@ msgstr "" "```" #: src/language/supported-features.md:282 src/native-libraries/overview.md:190 -#: src/native-libraries/abi.md:76 src/threading/overview.md:345 +#: src/native-libraries/abi.md:76 src/threading/overview.md:330 msgid "Closures" msgstr "Closures" @@ -4572,7 +4577,7 @@ msgstr "" "```" #: src/language/supported-features.md:470 src/native-libraries/overview.md:192 -#: src/internals/memory-model.md:22 +#: src/internals/memory-model.md:24 msgid "BigInt" msgstr "BigInt" @@ -4702,19 +4707,19 @@ msgstr "" "}\n" "```" -#: src/language/supported-features.md:563 src/contributing/architecture.md:57 +#: src/language/supported-features.md:563 src/contributing/architecture.md:62 msgid "Garbage Collection" msgstr "Garbage Collection" #: src/language/supported-features.md:565 msgid "" -"Perry includes a mark-sweep garbage collector. It runs automatically when " -"memory pressure is detected (~8MB arena blocks), but you can also trigger it " -"manually:" +"Perry uses a tracing generational collector with automatic allocation-" +"pressure and safepoint scheduling. You can also request collection " +"explicitly:" msgstr "" -"Perry enthält einen Mark-Sweep-Garbage-Collector. Er läuft automatisch, wenn " -"Speicherdruck erkannt wird (~8 MB Arena-Blöcke), aber Sie können ihn auch " -"manuell auslösen:" +"Perry verwendet einen generationalen Tracing-Kollektor, dessen Sammlung " +"automatisch durch Allokationsdruck und Safepoints geplant wird. Sie können " +"auch explizit eine Sammlung anfordern:" #: src/language/supported-features.md:567 msgid "" @@ -4732,13 +4737,16 @@ msgstr "" #: src/language/supported-features.md:573 msgid "" -"The GC uses conservative stack scanning to find roots and supports arena-" -"allocated objects (arrays, objects) and malloc-allocated objects (strings, " -"closures, promises, BigInts, errors)." +"Copying minors, non-moving fallbacks and full sweeps cooperate with precise " +"generated roots and runtime root scanners. Conservative stack scanning is " +"diagnostic-only. See the [memory model](../internals/memory-model.md) for " +"allocation and rooting details." msgstr "" -"Der GC verwendet konservatives Stack-Scanning, um Roots zu finden, und " -"unterstützt arena-allokierte Objekte (Arrays, Objekte) und malloc-allokierte " -"Objekte (Strings, Closures, Promises, BigInts, Errors)." +"Kopierende Minor-Sammlungen, unbewegliche Rückfallpfade und vollständige " +"Sweeps arbeiten mit präzisen generierten Roots und Laufzeitscannern " +"zusammen. Konservatives Stackscanning dient nur der Diagnose. Details zu " +"Allokation und Rooting stehen im [Speichermodell](../internals/memory-" +"model.md)." #: src/language/supported-features.md:575 msgid "JSX/TSX" @@ -4825,20 +4833,19 @@ msgstr "" #: src/language/supported-features.md:615 msgid "" -"There are no guarantees for every dynamic JavaScript pattern (the " -"[Limitations](limitations.md) still apply — no `eval`, no general dynamic " -"`require()`), but most plain JavaScript projects compile and run." +"Dynamic evaluation and CommonJS have supported paths, with limits on " +"interpreter behavior and runtime module loading. See " +"[Limitations](limitations.md) for the current boundaries." msgstr "" -"Es gibt keine Garantie für jedes dynamische JavaScript-Muster (das " -"[Limitations](limitations.md) gilt immer noch — kein `eval`, kein " -"allgemeines dynamisches `require()`), aber die meisten einfachen JavaScript-" -"Projekte kompilieren und laufen." +"Dynamische Auswertung und CommonJS haben unterstützte Pfade, mit Grenzen für" +" Interpreterverhalten und Modulladen zur Laufzeit. Die aktuellen Grenzen " +"stehen unter [Einschränkungen](limitations.md)." -#: src/language/supported-features.md:619 +#: src/language/supported-features.md:617 msgid "Node.js Compatibility" msgstr "Node.js Compatibility" -#: src/language/supported-features.md:621 +#: src/language/supported-features.md:619 msgid "" "Perry implements a large, real (non-stub) slice of the Node.js standard " "library — `fs`, `http`/`https`/`http2`, `net`/`tls`, `dns`/`dgram`, " @@ -4861,23 +4868,25 @@ msgstr "" "Oberfläche und die verbleibenden Lücken werden in `docs/runtime-parity-gaps." "md` verfolgt." -#: src/language/supported-features.md:633 +#: src/language/supported-features.md:631 msgid "[Type System](type-system.md) — Type inference and checking" msgstr "[Typsystem](type-system.md) — Typinferenz und -prüfung" -#: src/language/supported-features.md:634 +#: src/language/supported-features.md:632 msgid "[Limitations](limitations.md) — What's not supported yet" msgstr "[Einschränkungen](limitations.md) — Was noch nicht unterstützt wird" #: src/language/type-system.md:3 msgid "" -"Perry erases types at compile time, similar to how `tsc` removes type " -"annotations when emitting JavaScript. However, Perry also performs type " -"inference to generate efficient native code." +"TypeScript annotations describe source-level intent; they do not validate " +"runtime values. Perry combines local evidence, program facts and runtime " +"guards to select native representations. Decorator metadata is an explicit " +"exception to annotation erasure." msgstr "" -"Perry löscht Typen zur Compile-Zeit, ähnlich wie `tsc` Typ-Annotationen beim " -"Emittieren von JavaScript entfernt. Perry führt jedoch zusätzlich " -"Typinferenz durch, um effizienten nativen Code zu generieren." +"TypeScript-Annotationen beschreiben die Absicht im Quellcode; sie validieren" +" keine Laufzeitwerte. Perry kombiniert lokale Nachweise, Programmfakten und " +"Laufzeitprüfungen zur Wahl nativer Darstellungen. Dekoratormetadaten sind " +"eine ausdrückliche Ausnahme vom Entfernen der Annotationen." #: src/language/type-system.md:5 msgid "Type Inference" @@ -5027,11 +5036,11 @@ msgstr "Utility-Typen" #: src/language/type-system.md:64 msgid "" -"Common TypeScript utility types are erased at compile time (they don't " -"affect code generation):" +"Utility types describe static relationships and do not introduce runtime " +"wrappers or validation:" msgstr "" -"Übliche TypeScript-Utility-Typen werden zur Compile-Zeit gelöscht (sie " -"beeinflussen die Code-Generierung nicht):" +"Utility-Typen beschreiben statische Beziehungen und erzeugen weder Laufzeit-" +"Wrapper noch Validierung:" #: src/language/type-system.md:75 msgid "" @@ -5090,11 +5099,13 @@ msgstr "" #: src/language/type-system.md:100 msgid "" -"At runtime, all values are NaN-boxed — the generic parameter doesn't affect " -"code generation." +"A generic parameter alone is not a runtime representation proof. Generic " +"value boundaries use NaN-boxing, while proven computations can use native " +"scalars and specialized storage." msgstr "" -"Zur Laufzeit sind alle Werte NaN-geboxt — der generische Parameter " -"beeinflusst die Code-Generierung nicht." +"Ein generischer Typparameter allein beweist keine Laufzeitdarstellung. " +"Generische Wertschnittstellen verwenden NaN-Boxing; nachgewiesene " +"Berechnungen können native Skalare und spezialisierten Speicher verwenden." #: src/language/type-system.md:102 msgid "Type Checking with `--type-check`" @@ -5110,12 +5121,17 @@ msgstr "" #: src/language/type-system.md:110 msgid "" -"This resolves cross-file types, interfaces, and generics via an IPC " -"protocol. It falls back gracefully if the type checker is not installed." +"This starts `tsgo`, loads the project tsconfig and requests diagnostics and " +"resolved types over IPC. Reported type errors stop compilation. Missing " +"tooling, project-load failures or unavailable diagnostics can leave " +"compilation running without checking; this option is not a guaranteed " +"standalone type-check gate." msgstr "" -"Dies löst dateiübergreifende Typen, Interfaces und Generics über ein IPC-" -"Protokoll auf. Es fällt elegant zurück, wenn der Typprüfer nicht installiert " -"ist." +"Dies startet `tsgo`, lädt die tsconfig des Projekts und fordert Diagnosen " +"sowie aufgelöste Typen über IPC an. Gemeldete Typfehler stoppen die " +"Kompilierung. Fehlende Werkzeuge, Ladefehler oder nicht verfügbare Diagnosen" +" können zur Fortsetzung ohne Prüfung führen; diese Option garantiert daher " +"keine eigenständige Typprüfschranke." #: src/language/type-system.md:112 msgid "" @@ -5132,10 +5148,13 @@ msgstr "Union- und Intersection-Typen" #: src/language/type-system.md:116 msgid "" -"Union types are recognized syntactically but don't affect code generation:" +"Unions and intersections do not create runtime tags of their own. " +"Specialization requires evidence about the actual value, such as a validated" +" guard:" msgstr "" -"Union-Typen werden syntaktisch erkannt, beeinflussen aber nicht die Code-" -"Generierung:" +"Union- und Intersection-Typen erzeugen keine eigenen Laufzeittags. " +"Spezialisierung erfordert Nachweise über den tatsächlichen Wert, etwa eine " +"validierte Prüfung:" #: src/language/type-system.md:118 msgid "" @@ -5207,13 +5226,31 @@ msgstr "" "Die `value is string`-Annotation wird gelöscht, aber die `typeof`-Prüfung " "funktioniert zur Laufzeit." +#: src/language/type-system.md:149 +msgid "Program facts and specialization" +msgstr "Programmfakten und Spezialisierung" + #: src/language/type-system.md:151 +msgid "" +"The default facts pass analyzes value flow across modules. It can prove " +"receiver sets, field layouts and call targets; unknown behavior retains " +"checks or the generic path. See [Program facts](../internals/program-" +"facts.md) and [Local binding type evidence](../internals/local-binding-type-" +"evidence.md)." +msgstr "" +"Der standardmäßige Faktenpass analysiert den Wertfluss zwischen Modulen. Er " +"kann Empfängermengen, Feldlayouts und Aufrufziele beweisen; bei unbekanntem " +"Verhalten bleiben Prüfungen oder der generische Pfad erhalten. Siehe " +"[Programmfakten](../internals/program-facts.md) und [Typnachweise lokaler " +"Bindungen](../internals/local-binding-type-evidence.md)." + +#: src/language/type-system.md:155 msgid "[Supported Features](supported-features.md) — Complete feature list" msgstr "" "[Unterstützte Funktionen](supported-features.md) — Vollständige " "Funktionsliste" -#: src/language/type-system.md:152 +#: src/language/type-system.md:156 msgid "[Limitations](limitations.md) — What's not supported" msgstr "[Einschränkungen](limitations.md) — Was nicht unterstützt wird" @@ -5350,7 +5387,7 @@ msgstr "" #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 #: src/widgets/creating-widgets.md:48 src/widgets/configuration.md:70 -#: src/testing/geisterhand.md:84 src/cli/flags.md:135 src/cli/perry-toml.md:117 +#: src/testing/geisterhand.md:84 src/cli/flags.md:157 src/cli/perry-toml.md:117 #: src/cli/perry-toml.md:128 src/cli/perry-toml.md:156 #: src/cli/perry-toml.md:172 src/cli/perry-toml.md:182 #: src/cli/perry-toml.md:188 src/cli/perry-toml.md:198 @@ -5360,8 +5397,8 @@ msgstr "" #: src/cli/perry-toml.md:334 src/cli/perry-toml.md:353 #: src/cli/perry-toml.md:365 src/cli/perry-toml.md:375 #: src/cli/perry-toml.md:385 src/cli/perry-toml.md:414 -#: src/cli/perry-toml.md:441 src/internals/memory-model.md:15 -#: src/contributing/architecture.md:48 +#: src/cli/perry-toml.md:441 src/internals/memory-model.md:17 +#: src/contributing/architecture.md:51 msgid "Type" msgstr "Typ" @@ -5458,7 +5495,7 @@ msgid "IEEE-754 binary32" msgstr "IEEE-754 binär32" #: src/language/native-values.md:75 src/native-libraries/manifest-v1.md:223 -#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:27 +#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:30 msgid "`f64`" msgstr "`f64`" @@ -5502,8 +5539,8 @@ msgstr "" #: src/language/native-values.md:90 msgid "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -5517,8 +5554,8 @@ msgid "" "```" msgstr "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -6124,150 +6161,42 @@ msgstr "" "`instanceof` verfügbar." #: src/language/limitations.md:29 -msgid "No eval() or Dynamic Code" -msgstr "Kein eval() oder dynamischer Code" +msgid "Dynamic code and module loading" +msgstr "Dynamischer Code und Modulladen" #: src/language/limitations.md:31 msgid "" -"Perry compiles to native code ahead of time. It cannot evaluate a code " -"string that is only known at runtime. A _constant_ code string is the " -"exception — `eval(\"1 + 1\")` and `new Function(\"a\", \"b\", \"return a + " -"b\")` are compiled to real native functions (#1679). Only a body built from " -"runtime data hits this limit:" -msgstr "" -"Perry kompiliert im Voraus zu nativem Code. Es kann keinen Code String " -"auswerten, der nur zur Laufzeit bekannt ist. Ein _constant_-Codestring ist " -"die Ausnahme — `eval(\"1 + 1\")` und `new Function(\"a\", \"b\", \"return a " -"+ b\")` werden zu echten nativen Funktionen kompiliert (#1679). Nur ein aus " -"Laufzeitdaten gebauter Körper trifft dieses Limit:" - -#: src/language/limitations.md:37 -msgid "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" - -#: src/language/limitations.md:45 -msgid "Default: deferred runtime error + compile-time notice (#5206)" -msgstr "" -"Voreinstellung: Latend Runtime Fehler + Compil-Time Ankündigung (#5206)" - -#: src/language/limitations.md:47 -msgid "" -"By default a runtime-unknown `eval(...)` / `new Function()` " -"site does **not** block the build. Perry compiles it to a value that throws " -"a descriptive `Error` _only if it is actually reached_ (an `eval(...)` " -"throws when evaluated; a `new Function(...)` returns a function that throws " -"when called), and prints a single end-of-compile notice listing every " -"degraded site:" -msgstr "" -"Standardmäßig ist eine runtime-unbekannte `eval(...)` / `new " -"Function()`-Site dies **nicht** Blockieren Sie den Bau. Perry " -"kompiliert es zu einem Wert, der eine beschreibende `Error` _nur dann wirft, " -"wenn es tatsächlich erreicht wird_ (ein `eval(...)` wirft, wenn ausgewertet; " -"ein `new Function(...)` gibt eine Funktion zurück, die beim Aufruf wirft), " -"und druckt eine einzelne End-of-Compile-Notiz, die jede degradierte Site " -"auflistet:" - -#: src/language/limitations.md:61 -msgid "" -"This lets a single such call in a cold path ship without aborting the whole " -"build, while still failing loudly (and catchably) if that path runs." -msgstr "" -"Dies lässt einen einzigen solchen Anruf in einem kalten Pfad Schiff, ohne " -"den gesamten Bau zu beenden, während immer noch laut (und eingängig) " -"scheitern, wenn dieser Pfad läuft." - -#: src/language/limitations.md:64 -msgid "Dynamic `import()` with a runtime-computed specifier (#5230)" -msgstr "Dynamisches `import()` mit einem Laufzeit-Computed Specifier (#5230)" - -#: src/language/limitations.md:66 -#, fuzzy -msgid "" -"For code modules, a dynamic `import(spec)` whose `spec` is only known at " -"runtime (a plugin loader building a path from a variable) is subject to the " -"**same defer/notice/strict policy** as `eval`. By default it compiles to a " -"rejected `Promise` carrying a descriptive `Error` (so `await import(spec)` " -"throws _only if reached_), is listed in the shared notice above under the " -"`import(...)` kind, and does **not** abort the build. This lets an app with " -"a plugin-loader path compile and run its core, with only the plugin-load " -"path throwing if exercised." -msgstr "" -"Ein dynamischer `import(spec)`, dessen `spec` nur zur Laufzeit bekannt ist " -"(ein Plugin Loader, der einen Pfad aus einer Variable baut) unterliegt dem " -"**gleiche defer/notice/strict-Richtlinie** als `eval`. . Standardmäßig " -"kompiliert es zu einem abgelehnten `Promise` mit einer beschreibenden " -"`Error` (so `await import(spec)` wirft _nur wenn erreicht_), ist in der oben " -"genannten gemeinsamen Mitteilung unter der Art `import(...)` aufgeführt, und " -"tut **nicht** Abbruch des Baus. Dadurch kann eine App mit einem Plugin-" -"loader-Pfad kompilieren und ihren Kern ausführen, wobei nur der Plugin-load-" -"Pfad werfen, wenn ausgeübt." - -#: src/language/limitations.md:74 -msgid "" -"If the runtime specifier names a supported Node builtin, Perry resolves its " -"native namespace. Otherwise, when it has no compiled target, the rejected " -"`Error` retains `code: \"ERR_MODULE_NOT_FOUND\"` for existing optional-" -"dependency handlers. Its message names the requested module, explains that " -"runtime-loaded JavaScript is unavailable in the native build, and suggests " -"the application's Bun/Node distribution or a statically resolvable import " -"followed by recompilation. Deferred sites also retain their source location. " -"Perry does not print a second runtime warning: the application's catch/" -"report path owns the diagnostic." -msgstr "" - -#: src/language/limitations.md:83 -msgid "" -"Resolvable specifiers are unaffected and still compile + load: string " -"literals (`import(\"./mod.js\")`), ternaries of resolvable arms, template " -"literals over `const` locals (``import(`./${KIND}.js`)``), finite string-" -"literal-union parameters, and directory globs." -msgstr "" -"Lösbare Spezifikatoren sind unbeeinflusst und kompilieren immer noch + Last: " -"String-Literative (`import(\"./mod.js\")`), Ternarien von resolvablen Armen, " -"Template-Literative über `const`-Locals (``import(`./${KIND}.js`)``), " -"endliche String-Literar-Union-Parameter und Verzeichnis-Globs." - -#: src/language/limitations.md:88 -msgid "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" - -#: src/language/limitations.md:98 +"Constant `eval` and `Function` bodies can compile ahead of time. Runtime-" +"generated bodies reach Perry’s linked interpreter; they are not " +"automatically deferred errors. Interpreter support does not imply full " +"engine parity or the ability to load arbitrary JavaScript packages from " +"disk." +msgstr "" +"Konstante `eval`- und `Function`-Rümpfe können vorab kompiliert werden. Zur " +"Laufzeit erzeugte Rümpfe erreichen Perrys eingebundenen Interpreter; sie " +"werden nicht automatisch zu verzögerten Fehlern. Interpreter-Unterstützung " +"bedeutet weder vollständige Engine-Parität noch das Laden beliebiger " +"JavaScript-Pakete vom Datenträger." + +#: src/language/limitations.md:33 +msgid "" +"Dynamic `import()` and `require()` can resolve supported builtins and " +"modules included in the compiled graph. Installing a package after " +"compilation does not add it to that graph. Unavailable code targets fail at " +"runtime; resolvable literals and supported finite-specifier forms can be " +"compiled into the executable." +msgstr "" +"Dynamisches `import()` und `require()` können unterstützte Builtins und " +"Module im kompilierten Graphen auflösen. Nachträglich installierte Pakete " +"ergänzen diesen Graphen nicht. Nicht verfügbare Codeziele scheitern zur " +"Laufzeit; auflösbare Literale und unterstützte endliche Spezifikatormengen " +"können in die ausführbare Datei kompiliert werden." + +#: src/language/limitations.md:35 msgid "Native OpenCode plugins (#10105)" msgstr "" -#: src/language/limitations.md:100 +#: src/language/limitations.md:37 msgid "" "The first native OpenCode deliverable follows **option A** from [\\#10105]" "(https://github.com/PerryTS/perry/issues/10105), tracked in [\\#10107]" @@ -6278,7 +6207,7 @@ msgid "" "providers reached through static imports remain supported." msgstr "" -#: src/language/limitations.md:108 +#: src/language/limitations.md:45 msgid "" "Use the Bun distribution when those extensions are required. Including " "extensions in a statically reachable import graph and recompiling is a build-" @@ -6289,7 +6218,7 @@ msgid "" "as TOML are a separate feature (#10104)." msgstr "" -#: src/language/limitations.md:115 +#: src/language/limitations.md:52 msgid "" "OpenCode v1.18.30's `PluginLoader.load` catches import rejections, and its " "caller reports a load failure without retrying that stage. The TUI plugin " @@ -6300,58 +6229,58 @@ msgid "" "remains part of the integration acceptance in #10107." msgstr "" -#: src/language/limitations.md:123 +#: src/language/limitations.md:60 msgid "" "Data files can be loaded at runtime using import attributes (#10104). The " "specifier must be an absolute filesystem path or a `file://` URL, and the " "result has a `default` export:" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 msgid "Import attribute `type`" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 #, fuzzy msgid "Default export" msgstr "Standardverhalten" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 #, fuzzy msgid "`\"toml\"`" msgstr "`perry.toml`" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 msgid "Parsed TOML table, using the same parser as `Bun.TOML.parse`" msgstr "" -#: src/language/limitations.md:130 src/native-libraries/manifest-v1.md:225 +#: src/language/limitations.md:67 src/native-libraries/manifest-v1.md:225 msgid "`\"json\"`" msgstr "`\"json\"`" -#: src/language/limitations.md:130 +#: src/language/limitations.md:67 msgid "Parsed JSON value" msgstr "" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 #, fuzzy msgid "`\"text\"`" msgstr "\"text\"" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 msgid "File contents as a string" msgstr "" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 #, fuzzy msgid "`\"file\"`" msgstr "`filename`" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 msgid "Filesystem path as a string" msgstr "" -#: src/language/limitations.md:134 +#: src/language/limitations.md:71 msgid "" "```typescript,no-test\n" "import { pathToFileURL } from \"node:url\";\n" @@ -6361,7 +6290,7 @@ msgid "" "```" msgstr "" -#: src/language/limitations.md:141 +#: src/language/limitations.md:78 msgid "" "TOML and JSON parse failures reject with `SyntaxError`. Missing files reject " "with an I/O error. These loaders do not load runtime code modules or resolve " @@ -6369,86 +6298,21 @@ msgid "" "runtime-computed specifiers at compile time as described below." msgstr "" -#: src/language/limitations.md:146 -msgid "Strict mode: refuse at compile time" -msgstr "Strenger Modus: zur Kompilierung Zeit ablehnen" - -#: src/language/limitations.md:148 -msgid "" -"To make every runtime-unknown site a hard compile-time error instead, opt " -"into strict-eval mode by any of:" -msgstr "" -"Um jede Runtime-Unbekannte Site stattdessen zu einem harten Compil-Time-" -"Fehler zu machen, wählen Sie in den strengen Auswertmodus durch einen der:" - -#: src/language/limitations.md:151 -msgid "the `--strict-eval` flag on `perry compile`," -msgstr "die `--strict-eval`-Flagge auf `perry compile`," - -#: src/language/limitations.md:152 +#: src/language/limitations.md:83 msgid "" -"`\"perry\": { \"eval\": \"error\" }` (or `\"perry\": { \"strict\": true }`) " -"in `package.json`, or" +"`--strict-eval` remains accepted but does not reject bodies sent to the " +"interpreter. `--strict-dynamic-import` still rejects unresolved asynchronous" +" import specifiers during module collection; synchronous `require()` takes " +"its runtime resolution path. `PERRY_ALLOW_EVAL=1` disables that strict " +"import check." msgstr "" -"`\"perry\": { \"eval\": \"error\" }` (oder `\"perry\": { \"strict\": true }" -"`) in `package.json` oder" - -#: src/language/limitations.md:154 -msgid "`[perry]` `eval = \"error\"` (or `strict = true`) in `perry.toml`." -msgstr "`[perry]` `eval = \"error\"` (oder `strict = true`) in `perry.toml`." +"`--strict-eval` wird weiter akzeptiert, lehnt aber keine an den Interpreter " +"geleiteten Rümpfe ab. `--strict-dynamic-import` lehnt weiterhin unaufgelöste" +" asynchrone Imports bei der Modulerfassung ab; synchrones `require()` " +"verwendet seine Laufzeitauflösung. `PERRY_ALLOW_EVAL=1` deaktiviert diese " +"strenge Importprüfung." -#: src/language/limitations.md:156 -msgid "" -"`perry.eval` accepts `\"defer\"` (the default) or `\"error\"`. Precedence is " -"package.json/perry.toml config → `--strict-eval` (opts in). The legacy " -"`PERRY_ALLOW_EVAL=1` environment variable still works: it forces non-strict " -"(defer) mode for a one-off build, overriding any strict flag/config." -msgstr "" -"`perry.eval` akzeptiert `\"defer\"` (der Standard) oder `\"error\"`. Vorrang " -"ist package.json/perry.toml config → `--strict-eval` (opts in). Die alte " -"`PERRY_ALLOW_EVAL=1` Umgebungsvariable funktioniert noch: Sie zwingt den " -"nicht-strikten (Defer) Modus für einen einmaligen Build und überschreibt " -"jeglichen strengen flag/config" - -#: src/language/limitations.md:161 -msgid "" -"The same strict controls apply to runtime-computed dynamic `import()` " -"(#5230). The broad `perry.strict = true` covers both eval and dynamic " -"import. For a knob scoped to dynamic imports only, use `--strict-dynamic-" -"import` or `\"perry\": { \"dynamicImport\": \"error\" }` (accepts " -"`\"defer\"` (default) or `\"error\"`); the dedicated knob overrides the " -"broad `perry.strict` for import sites. `PERRY_ALLOW_EVAL=1` is the shared " -"AOT escape hatch — it forces defer for both eval and dynamic import." -msgstr "" -"Die gleichen strengen Kontrollen gelten für die Laufzeit berechnet " -"dynamische `import()` (#5230). Der breite `perry.strict = true` deckt sowohl " -"Auswert als auch dynamischen Import ab. Für einen Knopf, der nur für " -"dynamische Importe ausgelegt ist, verwenden Sie `--strict-dynamic-import` " -"oder `\"perry\": { \"dynamicImport\": \"error\" }` (akzeptiert `\"defer\"` " -"(Standard) oder `\"error\"`); der dedizierte Knopf überschreibt den breiten " -"`perry.strict` für Importseiten. `PERRY_ALLOW_EVAL=1` ist die geteilte AOT-" -"Ausweichluke – es Kräfte verschieben sich sowohl für Evaluierung und " -"dynamischen Import." - -#: src/language/limitations.md:169 -msgid "" -"Test262 rows that only observe parsing or executing a code string remain " -"intentional AOT exclusions, not runtime dynamic-code work. This includes the " -"`language/white-space/comment-{multi,single}-{form-feed,horizontal-tab,nbsp," -"space,vertical-tab}.js` rows and the direct-eval reference row `language/" -"types/reference/8.7.2-1-s.js`; they map to the AOT eval tracker (#1677), " -"eval classifier diagnostics (#1678), and the limited literal `Function` " -"folding work (#1679)." -msgstr "" -"Test262-Zeilen, die nur das Parsen oder Ausführen eines Code-Strings " -"beobachten, sind bewusste AOT-Ausschlüsse und keine dynamischen Code-" -"Laufzeitarbeiten. Dazu gehören die Zeilen `language/white-space/comment-" -"{multi,single}-{form-feed,horizontal-tab,nbsp,space,vertical-tab}.js` sowie " -"die Direct-Eval-Referenzzeile `language/types/reference/8.7.2-1-s.js`; sie " -"sind dem AOT-Eval-Tracker (#1677), den Eval-Classifier-Diagnosen (#1678) und " -"der eingeschränkten literalen `Function`-Faltungsarbeit (#1679) zugeordnet." - -#: src/language/limitations.md:178 +#: src/language/limitations.md:87 msgid "" "Perry parses decorator syntax, supports compile-time-only transforms (see " "the bundled `@log` example), and has a reduced legacy TypeScript " @@ -6471,7 +6335,7 @@ msgstr "" "hasOwnMetadata`, `Reflect.getMetadataKeys`, `Reflect.getOwnMetadataKeys`, " "`Reflect.deleteMetadata` sowie `@Reflect.metadata(...)`." -#: src/language/limitations.md:190 +#: src/language/limitations.md:99 msgid "" "Accessor decorators, descriptor replacement, general `Reflect.metadata(...)` " "calls outside decorator syntax, `Symbol` metadata keys, and full Angular / " @@ -6484,11 +6348,11 @@ msgstr "" "unterstützt. Einzelheiten und eine ausgearbeitete Migrationshilfe finden " "sich unter [Decorators](decorators.md)." -#: src/language/limitations.md:196 +#: src/language/limitations.md:105 msgid "No Runtime Metadata Reflection" msgstr "Keine Laufzeit-Metadata-Reflection" -#: src/language/limitations.md:198 +#: src/language/limitations.md:107 msgid "" "Perry implements a small metadata subset for legacy decorators. General " "runtime reflection is not supported:" @@ -6496,7 +6360,7 @@ msgstr "" "Perry implementiert eine kleine Metadata-Teilmenge für Legacy-Decorators. " "Allgemeine Laufzeit-Reflection wird nicht unterstützt:" -#: src/language/limitations.md:202 +#: src/language/limitations.md:111 msgid "" "```text\n" "Reflect.getMetadata(\"design:type\", target, key);\n" @@ -6512,70 +6376,32 @@ msgstr "" "Reflect.metadata(\"design:type\", String)(target, key);\n" "```" -#: src/language/limitations.md:209 -msgid "No User-Space CommonJS require()" -msgstr "Kein Benutzer-Raum CommonJS require()" - -#: src/language/limitations.md:211 -msgid "Use static ESM imports in Perry source:" -msgstr "Verwende statische ESM-Imports in Perry-Quellcode:" - -#: src/language/limitations.md:214 -msgid "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" -msgstr "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" - -#: src/language/limitations.md:223 -msgid "" -"Perry has internal CommonJS compatibility paths for some npm package " -"wrappers, but user-written modules should use static `import` declarations." -msgstr "" -"Perry verfügt über interne CommonJS-Kompatibilitätspfade für einige npm-" -"Package-Wrapper, aber benutzerdefinierte Module sollten statische `import`-" -"Deklarationen verwenden." +#: src/language/limitations.md:118 +msgid "JavaScript and CommonJS" +msgstr "JavaScript und CommonJS" -#: src/language/limitations.md:226 +#: src/language/limitations.md:120 msgid "" -"**JavaScript source compiles too.** Perry accepts `.js`, `.cjs`, `.mjs`, and " -"`.jsx` files as compiler input — they are parsed as JavaScript and lowered " -"through the same native pipeline as TypeScript, so no type annotations are " -"required. The limitations on this page still apply (no `eval`, no general " -"dynamic `require()`, etc.), but plain JavaScript projects compile and run in " -"most cases." +"Perry accepts `.js`, `.cjs`, `.mjs` and `.jsx` through the native pipeline. " +"CommonJS `require()` is supported for compiled modules and supported native " +"modules. It does not turn the executable into a general filesystem " +"JavaScript loader." msgstr "" -"**JavaScript-Quelle kompiliert auch.** Perry akzeptiert `.js`, `.cjs`, `." -"mjs` und `.jsx`-Dateien als Compiler-Eingabe – sie werden als JavaScript " -"geparst und durch dieselbe native Pipeline wie TypeScript abgesenkt, so dass " -"keine Typ-Annotationen erforderlich sind. Die Einschränkungen auf dieser " -"Seite gelten immer noch (kein `eval`, keine allgemeine dynamische " -"`require()`, etc.), aber einfache JavaScript-Projekte kompilieren und laufen " -"in den meisten Fällen." +"Perry verarbeitet `.js`, `.cjs`, `.mjs` und `.jsx` durch die native " +"Pipeline. CommonJS-`require()` unterstützt kompilierte und unterstützte " +"native Module. Dadurch wird das Programm nicht zu einem allgemeinen " +"JavaScript-Dateisystemlader." -#: src/language/limitations.md:233 +#: src/language/limitations.md:122 msgid "Prototype Manipulation" msgstr "Prototyp-Manipulation" -#: src/language/limitations.md:235 +#: src/language/limitations.md:124 msgid "Dynamic prototype manipulation is supported for the common patterns:" msgstr "" "Dynamische Prototypmanipulation wird für die gängigen Muster unterstützt:" -#: src/language/limitations.md:237 +#: src/language/limitations.md:126 msgid "" "```typescript,no-test\n" "// Supported\n" @@ -6599,7 +6425,7 @@ msgstr "" "Object.getPrototypeOf(obj); // inspection\n" "```" -#: src/language/limitations.md:245 +#: src/language/limitations.md:134 msgid "" "Prototype-method assignment routes through a dedicated registry " "(`CLASS_PROTOTYPE_METHODS`) and `Object.setPrototypeOf` performs a real, " @@ -6610,11 +6436,11 @@ msgstr "" "zyklusgeprüfte Prototypmutation durch (ein echter Zyklus wirft immer noch " "`TypeError`)." -#: src/language/limitations.md:249 +#: src/language/limitations.md:138 msgid "Remaining known gaps:" msgstr "Verbleibende bekannte Lücken:" -#: src/language/limitations.md:251 +#: src/language/limitations.md:140 msgid "" "the assignment form `obj.__proto__ = x` on the dynamic write path stores a " "literal `\"__proto__\"` property instead of setting the prototype (use " @@ -6624,7 +6450,7 @@ msgstr "" "speichert eine buchstäbliche Eigenschaft `\"__proto__\"`, anstatt den " "Prototyp (Verwendung `Object.setPrototypeOf`);" -#: src/language/limitations.md:254 +#: src/language/limitations.md:143 msgid "" "an object-literal `{ __proto__: [] }` sets the prototype without throwing, " "but the result is not yet visible to `instanceof` (`… instanceof Array` " @@ -6634,82 +6460,31 @@ msgstr "" "aber das Ergebnis ist noch nicht sichtbar, `instanceof` (`… instanceof " "Array` berichtet `false`)." -#: src/language/limitations.md:258 -msgid "Weak References Retain Their Targets" -msgstr "Weak References behalten ihre Ziele" +#: src/language/limitations.md:147 +msgid "Weak references and finalization" +msgstr "Schwache Referenzen und Finalisierung" -#: src/language/limitations.md:260 +#: src/language/limitations.md:149 msgid "" -"`WeakMap`, `WeakSet`, `WeakRef`, and `FinalizationRegistry` are implemented " -"and their APIs behave as expected — `set` / `get` / `has` / `delete`, `add`, " -"`deref()`, and `register` / `unregister` all work and return the right " -"values. `WeakMap` and `WeakSet` use **reference** equality, so two distinct " -"objects never collide on the same slot." +"WeakMap, WeakSet, WeakRef and FinalizationRegistry participate in weak " +"processing. Weak targets are excluded from the strong trace, forwarded " +"addresses are repaired, dead targets can be cleared and cleanup callbacks " +"can be queued. Collection and callback timing are not deterministic. See [GC" +" weak processing](../internals/garbage-collector.md#barriers-and-weak-" +"references)." msgstr "" -"`WeakMap`, `WeakSet`, `WeakRef` und `FinalizationRegistry` sind " -"implementiert und ihre APIs verhalten sich wie erwartet — `set` / `get` / " -"`has` / `delete`, `add`, `deref()` sowie `register` / `unregister` " -"funktionieren alle und liefern die richtigen Werte. `WeakMap` und `WeakSet` " -"verwenden **Referenz**-Gleichheit, sodass zwei verschiedene Objekte nie " -"denselben Slot belegen." +"WeakMap, WeakSet, WeakRef und FinalizationRegistry nehmen an der " +"Verarbeitung schwacher Referenzen teil. Schwache Ziele werden nicht stark " +"verfolgt; verschobene Adressen werden korrigiert, tote Ziele können gelöscht" +" und Bereinigungs-Callbacks eingereiht werden. Sammlungs- und Callback-" +"Zeitpunkte sind nicht deterministisch. Siehe [schwache Referenzen im " +"GC](../internals/garbage-collector.md#barriers-and-weak-references)." -#: src/language/limitations.md:266 -msgid "" -"The one caveat is that Perry's garbage collector does not yet treat these " -"references as _weak_, so targets are **retained rather than collected**. The " -"current runtime stores `WeakRef` targets and `FinalizationRegistry` " -"registrations in ordinary object/array fields (`crates/perry-runtime/src/" -"weakref.rs`), and the adjacent GC root scanners do not have a weak-slot " -"clearing/finalizer queue hook yet. In practice:" -msgstr "" -"Der einzige Vorbehalt ist, dass Perrys Garbage Collector diese Referenzen " -"noch nicht als _schwach_ behandelt, sodass Ziele **behalten statt " -"eingesammelt** werden. Die aktuelle Runtime speichert `WeakRef`-Ziele und " -"`FinalizationRegistry`-Registrierungen in gewöhnlichen Objekt-/Array-Feldern " -"(`crates/perry-runtime/src/weakref.rs`), und die benachbarten GC-Root-" -"Scanner verfügen noch nicht über einen Weak-Slot-Clearing-/Finalizer-Queue-" -"Hook. In der Praxis bedeutet das:" - -#: src/language/limitations.md:273 -msgid "" -"`WeakRef.deref()` always returns the original target (it is never reported " -"as collected)." -msgstr "" -"`WeakRef.deref()` gibt immer das ursprüngliche Ziel zurück (es wird nie als " -"eingesammelt gemeldet)." - -#: src/language/limitations.md:275 -msgid "" -"`FinalizationRegistry` records registrations but never fires its cleanup " -"callback." -msgstr "" -"`FinalizationRegistry` zeichnet Registrierungen auf, löst aber niemals " -"seinen Cleanup-Callback aus." - -#: src/language/limitations.md:277 -msgid "" -"`WeakMap` / `WeakSet` keep their keys alive (they behave like a reference-" -"keyed `Map` / `Set`)." -msgstr "" -"`WeakMap` / `WeakSet` halten ihre Schlüssel am Leben (sie verhalten sich wie " -"eine referenzgeschlüsselte `Map` / `Set`)." - -#: src/language/limitations.md:280 -msgid "" -"This is safe for **correctness** — code that reads through these APIs gets " -"the right values. It only matters if you depend on collection _timing_ to " -"reclaim memory or to run finalizer side effects." -msgstr "" -"Dies ist **korrekt** im Sinne der Funktionalität — Code, der über diese APIs " -"liest, erhält die richtigen Werte. Es ist nur dann relevant, wenn man auf " -"den Einsammlungs-_Zeitpunkt_ angewiesen ist, um Speicher freizugeben oder " -"Finalizer-Nebeneffekte auszulösen." - -#: src/language/limitations.md:284 +#: src/language/limitations.md:151 msgid "Limited Proxy Trapping" msgstr "Eingeschränktes Proxy-Trapping" -#: src/language/limitations.md:286 +#: src/language/limitations.md:153 msgid "" "Proxy support is not a full engine-level trap layer for every possible " "dynamic object access. Prefer plain objects and explicit APIs unless a " @@ -6720,11 +6495,11 @@ msgstr "" "und explizite APIs, es sei denn, ein Package benötigt nur die von Perry " "unterstützte Proxy-Oberfläche." -#: src/language/limitations.md:290 +#: src/language/limitations.md:157 msgid "Threading Model" msgstr "Threading-Modell" -#: src/language/limitations.md:292 +#: src/language/limitations.md:159 msgid "" "Perry supports real multi-threading via `parallelMap` and `spawn` from " "`perry/thread`. See [Multi-Threading](../threading/overview.md)." @@ -6732,7 +6507,7 @@ msgstr "" "Perry unterstützt echtes Multi-Threading über `parallelMap` und `spawn` aus " "`perry/thread`. Siehe [Multi-Threading](../threading/overview.md)." -#: src/language/limitations.md:294 +#: src/language/limitations.md:161 msgid "" "Threads do not share mutable state by default — closures passed to thread " "primitives cannot capture mutable variables (enforced at compile time), and " @@ -6756,11 +6531,11 @@ msgstr "" "ein typisiertes-Array _view_, das direkt noch Deepcopies erfasst, also baut " "die Ansicht pro Agent aus dem geteilten SAB." -#: src/language/limitations.md:305 +#: src/language/limitations.md:172 msgid "WebAssembly (#6558)" msgstr "WebAssembly (#6558)" -#: src/language/limitations.md:307 +#: src/language/limitations.md:174 msgid "" "Perry ships **no WebAssembly engine** in the default build. The adopted " "policy is that **WASM-dependent features degrade gracefully**. Closed issue " @@ -6775,7 +6550,7 @@ msgstr "" "aktives Versprechen, eine vollständige Engine zum Standard-Binär " "hinzuzufügen." -#: src/language/limitations.md:313 +#: src/language/limitations.md:180 msgid "" "The `WebAssembly` global is spec-shaped — every standard member exists with " "the correct type and arity — and fails cleanly rather than crashing:" @@ -6784,7 +6559,7 @@ msgstr "" "mit dem richtigen Typ und der richtigen Seltenheit – und versagt sauber " "anstatt abstürzt:" -#: src/language/limitations.md:316 +#: src/language/limitations.md:183 msgid "" "`WebAssembly.compile` / `compileStreaming` / `instantiate` / " "`instantiateStreaming` return a Promise **rejected** with a `WebAssembly." @@ -6794,7 +6569,7 @@ msgstr "" "`instantiateStreaming` geben ein Versprechen zurück **abgelehnt** mit einem " "`WebAssembly.CompileError`, dessen Nachricht auf #6558 zeigt." -#: src/language/limitations.md:319 +#: src/language/limitations.md:186 msgid "" "`WebAssembly.validate(bytes)` returns `false` — the honest answer for \"can " "I run this module here\"." @@ -6802,7 +6577,7 @@ msgstr "" "`WebAssembly.validate(bytes)` gibt `false` zurück — die ehrliche Antwort für " "\"kann ich dieses Modul hier ausführen\"." -#: src/language/limitations.md:321 +#: src/language/limitations.md:188 msgid "" "`new WebAssembly.Module(...)` throws `CompileError` synchronously; " "`Instance` throws `LinkError`; `Table` / `Global` throw `RuntimeError`." @@ -6810,7 +6585,7 @@ msgstr "" "`new WebAssembly.Module(...)` wirft `CompileError` synchron; `Instance` " "wirft `LinkError`; `Table` / `Global` wirft `RuntimeError`." -#: src/language/limitations.md:323 +#: src/language/limitations.md:190 msgid "" "`new WebAssembly.Memory({ initial })` genuinely works (a real zero-filled " "`ArrayBuffer` backs it, and `grow()` is supported), so feature-detection " @@ -6820,7 +6595,7 @@ msgstr "" "gefüllter `ArrayBuffer` unterstützt ihn, und `grow()` wird unterstützt), so " "dass Feature-Detektionscode, der eine Seite zuweist, erfolgreich ist." -#: src/language/limitations.md:326 +#: src/language/limitations.md:193 msgid "" "`CompileError` / `LinkError` / `RuntimeError` are real error constructors " "(`instanceof Error` and `instanceof WebAssembly.CompileError` both work)." @@ -6829,7 +6604,7 @@ msgstr "" "(`instanceof Error` und `instanceof WebAssembly.CompileError` funktionieren " "beide)." -#: src/language/limitations.md:329 +#: src/language/limitations.md:196 msgid "" "In practice this means lazy wasm consumers — wasm-bindgen loaders like " "`@silvia-odwyer/photon-node`, `@jsquash/webp` decoders, undici's llhttp " @@ -6841,7 +6616,7 @@ msgstr "" "— treffen ihre eigenen catch/fallback Pfade und die App läuft mit dieser " "Funktion abgebaut." -#: src/language/limitations.md:334 +#: src/language/limitations.md:201 msgid "" "When the wasmi host is linked, `new WebAssembly.Module(bytes)` and the " "synchronous `new WebAssembly.Instance(module, imports)` constructor execute " @@ -6864,11 +6639,11 @@ msgstr "" "Referenz-bewertete Funktionssignaturen und Streaming bleiben außerhalb " "dieser Untermenge." -#: src/language/limitations.md:344 +#: src/language/limitations.md:211 msgid "Two spellings, two paths:" msgstr "Zwei Schreibweisen, zwei Pfade:" -#: src/language/limitations.md:346 +#: src/language/limitations.md:213 msgid "" "**Namespace access** (`const WA = globalThis.WebAssembly; WA.compile(...)`, " "which is also what minified bundles evaluate through a namespace value) uses " @@ -6878,7 +6653,7 @@ msgstr "" "das ist auch, was minified Bundles durch einen Namensraumwert auswerten) " "nutzt die anmutige Oberfläche oben und braucht nichts zur Linkzeit." -#: src/language/limitations.md:349 +#: src/language/limitations.md:216 #, fuzzy msgid "" "**Literal static calls** (`WebAssembly.instantiate(bytes)` written against " @@ -6896,15 +6671,15 @@ msgstr "" "p perry-wasm-host`); ohne diesen wird der Bau mit dieser Anweisung " "fehlgeschlagen." -#: src/language/limitations.md:356 +#: src/language/limitations.md:223 msgid "npm Package Compatibility" msgstr "npm-Paket-Kompatibilität" -#: src/language/limitations.md:358 +#: src/language/limitations.md:225 msgid "Not all npm packages work with Perry:" msgstr "Nicht alle npm-Pakete funktionieren mit Perry:" -#: src/language/limitations.md:360 +#: src/language/limitations.md:227 msgid "" "**Natively supported**: ~50 popular packages (fastify, mysql2, redis, etc.) " "— these are compiled natively. See [Standard Library](../stdlib/overview.md)." @@ -6913,7 +6688,7 @@ msgstr "" "diese werden nativ kompiliert. Siehe [Standardbibliothek](../stdlib/overview." "md)." -#: src/language/limitations.md:361 +#: src/language/limitations.md:228 msgid "" "**`compilePackages`**: Pure TS/JS packages can be compiled natively via " "[configuration](../getting-started/project-config.md)." @@ -6921,52 +6696,32 @@ msgstr "" "**`compilePackages`**: Reine TS/JS-Pakete können über [Konfiguration](../" "getting-started/project-config.md) nativ kompiliert werden." -#: src/language/limitations.md:362 +#: src/language/limitations.md:229 msgid "" -"**Not supported**: Packages requiring native addons (`.node` files), " -"`eval()`, dynamic `require()`, or Node.js internals." +"Native addons require the supported [Node-API host](../internals/node-api-" +"host.md), explicit authorization and a matching target binary. V8/NAN/Node " +"C++ addons are not interchangeable with Node-API addons. Dynamic loading " +"remains constrained by the compiled graph." msgstr "" -"**Nicht unterstützt**: Pakete, die native Add-ons (`.node`-Dateien), " -"`eval()`, dynamisches `require()` oder Node.js-Interna benötigen." +"Native Addons erfordern den unterstützten [Node-API-Host](../internals/node-" +"api-host.md), ausdrückliche Autorisierung und eine passende Zielbinärdatei. " +"V8/NAN/Node-C++-Addons sind nicht mit Node-API-Addons austauschbar. " +"Dynamisches Laden bleibt auf den kompilierten Graphen beschränkt." -#: src/language/limitations.md:364 +#: src/language/limitations.md:231 msgid "Workarounds" msgstr "Umgehungslösungen" -#: src/language/limitations.md:366 -msgid "Dynamic Behavior" -msgstr "Dynamisches Verhalten" - -#: src/language/limitations.md:368 -msgid "" -"For cases where you need dynamic behavior, use the JavaScript runtime " -"fallback:" -msgstr "" -"Für Fälle, in denen Sie dynamisches Verhalten benötigen, nutzen Sie den " -"JavaScript-Runtime-Fallback:" - -#: src/language/limitations.md:371 -msgid "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" -msgstr "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" - -#: src/language/limitations.md:376 +#: src/language/limitations.md:233 msgid "Type Narrowing" msgstr "Typeinengung" -#: src/language/limitations.md:378 +#: src/language/limitations.md:235 msgid "Since there's no runtime type checking, use explicit checks:" msgstr "" "Da es keine Laufzeit-Typprüfung gibt, verwenden Sie explizite Prüfungen:" -#: src/language/limitations.md:380 +#: src/language/limitations.md:237 msgid "" "```typescript\n" "function processValue(value: string | number) {\n" @@ -6994,11 +6749,11 @@ msgstr "" "}\n" "```" -#: src/language/limitations.md:395 +#: src/language/limitations.md:252 msgid "[Supported Features](supported-features.md) — What does work" msgstr "[Unterstützte Funktionen](supported-features.md) — Was funktioniert" -#: src/language/limitations.md:396 +#: src/language/limitations.md:253 msgid "[Type System](type-system.md) — How types are handled" msgstr "[Typsystem](type-system.md) — Wie Typen behandelt werden" @@ -7153,16 +6908,16 @@ msgstr "" #: src/packages/porting.md:35 msgid "" -"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a " -"`\"gypfile\"` package marker are compiled against Node's native addon ABI. " +"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a `" +"\"gypfile\"` package marker are compiled against Node's native addon ABI. " "The JavaScript entry point is usually just loader glue around a shared " "library that expects Node-API/N-API, NAN, V8, libuv, or Node internals. " "Perry does not host that ABI as part of `compilePackages`. Common examples " "in this category include `node-pty`, `sharp`, `better-sqlite3`, and " "`sqlite3`." msgstr "" -"Pakete, die `.node`-Dateien, `binding.gyp`, `prebuilds/` oder einen " -"`\"gypfile\"`-Paketmarker versenden, werden mit dem nativen Addon ABI von " +"Pakete, die `.node`-Dateien, `binding.gyp`, `prebuilds/` oder einen `" +"\"gypfile\"`-Paketmarker versenden, werden mit dem nativen Addon ABI von " "Node kompiliert. Der JavaScript-Eingangspunkt ist normalerweise nur " "Laderkleber um eine gemeinsame Bibliothek herum, die Node-API/N-API, NAN, " "V8, libuv oder Node Internals erwartet. Perry hostet diese ABI nicht als " @@ -8063,7 +7818,7 @@ msgid "Documented at" msgstr "Dokumentiert unter" #: src/native-libraries/overview.md:186 src/native-libraries/abi.md:82 -#: src/threading/overview.md:342 src/threading/parallel-map.md:57 +#: src/threading/overview.md:327 src/threading/parallel-map.md:57 msgid "Strings" msgstr "Strings" @@ -8543,14 +8298,14 @@ msgstr "" msgid "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" msgstr "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" #: src/native-libraries/overview.md:351 @@ -8875,7 +8630,7 @@ msgstr "" "Erzeugen Sie diese Tabelle mit `python3 scripts/binding_governance.py --" "table`." -#: src/native-libraries/governance.md:99 src/contributing/architecture.md:24 +#: src/native-libraries/governance.md:99 src/contributing/architecture.md:25 msgid "Crate" msgstr "Crate" @@ -8900,7 +8655,7 @@ msgstr "Gegenwärtiger Stand" msgid "`perry-ext-ads`" msgstr "`perry-ext-ads`" -#: src/native-libraries/governance.md:101 src/api/reference.md:2172 +#: src/native-libraries/governance.md:101 src/api/reference.md:2174 msgid "`perry/ads`" msgstr "`perry/ads`" @@ -8920,7 +8675,7 @@ msgstr "Gebündelt; Abschiebung anhängig" msgid "`perry-ext-argon2`" msgstr "`perry-ext-argon2`" -#: src/native-libraries/governance.md:102 src/api/reference.md:214 +#: src/native-libraries/governance.md:102 src/api/reference.md:215 msgid "`argon2`" msgstr "`argon2`" @@ -8960,7 +8715,7 @@ msgstr "Gebündelt; Migration anhängig" msgid "`perry-ext-bcrypt`" msgstr "`perry-ext-bcrypt`" -#: src/native-libraries/governance.md:103 src/api/reference.md:322 +#: src/native-libraries/governance.md:103 src/api/reference.md:323 msgid "`bcrypt`" msgstr "`bcrypt`" @@ -8968,7 +8723,7 @@ msgstr "`bcrypt`" msgid "`perry-ext-better-sqlite3`" msgstr "`perry-ext-better-sqlite3`" -#: src/native-libraries/governance.md:104 src/api/reference.md:329 +#: src/native-libraries/governance.md:104 src/api/reference.md:330 msgid "`better-sqlite3`" msgstr "`better-sqlite3`" @@ -8976,7 +8731,7 @@ msgstr "`better-sqlite3`" msgid "`perry-ext-cheerio`" msgstr "`perry-ext-cheerio`" -#: src/native-libraries/governance.md:105 src/api/reference.md:489 +#: src/native-libraries/governance.md:105 src/api/reference.md:491 msgid "`cheerio`" msgstr "`cheerio`" @@ -9000,7 +8755,7 @@ msgstr "Kompilieren der vorgelagerten Paketquelle" msgid "`perry-ext-ethers`" msgstr "`perry-ext-ethers`" -#: src/native-libraries/governance.md:106 src/api/reference.md:1194 +#: src/native-libraries/governance.md:106 src/api/reference.md:1196 msgid "`ethers`" msgstr "`ethers`" @@ -9037,7 +8792,7 @@ msgstr "zusammengeschlossen, behalten" msgid "`perry-ext-net`" msgstr "`perry-ext-net`" -#: src/native-libraries/governance.md:108 src/api/reference.md:1857 +#: src/native-libraries/governance.md:108 src/api/reference.md:1859 msgid "`net`" msgstr "`net`" @@ -9045,7 +8800,7 @@ msgstr "`net`" msgid "`perry-ext-nodemailer`" msgstr "`perry-ext-nodemailer`" -#: src/native-libraries/governance.md:109 src/api/reference.md:2015 +#: src/native-libraries/governance.md:109 src/api/reference.md:2017 msgid "`nodemailer`" msgstr "`nodemailer`" @@ -9069,7 +8824,7 @@ msgstr "" msgid "`perry-ext-pdf`" msgstr "`perry-ext-pdf`" -#: src/native-libraries/governance.md:111 src/api/reference.md:192 +#: src/native-libraries/governance.md:111 src/api/reference.md:193 msgid "`@perryts/pdf`" msgstr "`@perryts/pdf`" @@ -9077,7 +8832,7 @@ msgstr "`@perryts/pdf`" msgid "`perry-ext-sharp`" msgstr "`perry-ext-sharp`" -#: src/native-libraries/governance.md:112 src/api/reference.md:2914 +#: src/native-libraries/governance.md:112 src/api/reference.md:2923 msgid "`sharp`" msgstr "`sharp`" @@ -9085,7 +8840,7 @@ msgstr "`sharp`" msgid "`perry-ext-typescript`" msgstr "`perry-ext-typescript`" -#: src/native-libraries/governance.md:113 src/api/reference.md:3363 +#: src/native-libraries/governance.md:113 src/api/reference.md:3383 msgid "`typescript`" msgstr "`typescript`" @@ -9093,7 +8848,7 @@ msgstr "`typescript`" msgid "`perry-ext-undici`" msgstr "`perry-ext-undici`" -#: src/native-libraries/governance.md:114 src/api/reference.md:3376 +#: src/native-libraries/governance.md:114 src/api/reference.md:3396 msgid "`undici`" msgstr "`undici`" @@ -9101,7 +8856,7 @@ msgstr "`undici`" msgid "`perry-ext-ws`" msgstr "`perry-ext-ws`" -#: src/native-libraries/governance.md:115 src/api/reference.md:3681 +#: src/native-libraries/governance.md:115 src/api/reference.md:3701 msgid "`ws`" msgstr "`ws`" @@ -9109,7 +8864,7 @@ msgstr "`ws`" msgid "`perry-ext-zlib`" msgstr "`perry-ext-zlib`" -#: src/native-libraries/governance.md:116 src/api/reference.md:3721 +#: src/native-libraries/governance.md:116 src/api/reference.md:3741 msgid "`zlib`" msgstr "`zlib`" @@ -9339,7 +9094,7 @@ msgstr "\"js_pdf_parse\"" #: src/native-libraries/manifest-v1.md:155 #: src/native-libraries/manifest-v1.md:161 #: src/native-libraries/manifest-v1.md:192 -#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:596 +#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:618 msgid "\"kind\"" msgstr "\"gütig\"" @@ -9431,20 +9186,20 @@ msgstr "" #: src/native-libraries/authoring-guide.md:150 msgid "" "Manifest descriptors are native ABI descriptors, not TypeScript surface " -"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and " -"`\"void\"` still work. Use the explicit vocabulary when you need native " -"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, " -"`\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " -"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": " -"\"jsvalue\" }`." +"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and `" +"\"void\"` still work. Use the explicit vocabulary when you need native " +"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, `" +"\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " +"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": \"jsvalue" +"\" }`." msgstr "" "Manifest-Deskriptoren sind native ABI-Deskriptoren, keine TypeScript-" -"Oberflächentypen. Vorhandene Zeichenketten wie `\"string\"`, `\"number\"`, " -"`\"i64\"` und `\"void\"` funktionieren weiterhin. Verwenden Sie das " -"explizite Vokabular, wenn Sie native Präzision oder Metadaten benötigen: " -"`\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, `\"buffer_len\"`, `{ \"kind\": " -"\"buffer+len\" }`, `{ \"kind\": \"handle\", \"type\": \"MyThing\" }` und " -"`{ \"kind\": \"promise\", \"result\": \"jsvalue\" }`." +"Oberflächentypen. Vorhandene Zeichenketten wie `\"string\"`, `\"number\"`, `" +"\"i64\"` und `\"void\"` funktionieren weiterhin. Verwenden Sie das explizite " +"Vokabular, wenn Sie native Präzision oder Metadaten benötigen: `\"f32\"`, `" +"\"u32\"`, `\"u64\"`, `\"usize\"`, `\"buffer_len\"`, `{ \"kind\": \"buffer+len" +"\" }`, `{ \"kind\": \"handle\", \"type\": \"MyThing\" }` und `{ \"kind\": " +"\"promise\", \"result\": \"jsvalue\" }`." #: src/native-libraries/authoring-guide.md:158 msgid "" @@ -10848,7 +10603,7 @@ msgstr "Erforderlich" #: src/tui/widgets.md:55 src/tui/hooks.md:169 src/platforms/tvos.md:50 #: src/platforms/tvos.md:149 src/platforms/watchos.md:137 #: src/platforms/wearos.md:23 src/container/containers.md:133 -#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:96 +#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:118 #: src/cli/cache-dir.md:42 src/internals/node-api-host.md:588 #: src/internals/node-api-host.md:624 src/internals/node-api-host.md:638 #: src/internals/node-api-host.md:649 @@ -10914,17 +10669,18 @@ msgstr "string" #: src/testing/http2-conformance.md:424 src/testing/http2-conformance.md:425 #: src/testing/http2-conformance.md:426 src/testing/http2-conformance.md:427 #: src/testing/http2-conformance.md:428 src/testing/http2-conformance.md:429 -#: src/testing/ci-tiers.md:39 src/testing/ci-tiers.md:40 -#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:42 -#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:45 -#: src/testing/ci-tiers.md:46 src/testing/ci-tiers.md:48 -#: src/testing/ci-tiers.md:49 src/testing/ci-tiers.md:50 -#: src/testing/ci-tiers.md:51 src/testing/ci-tiers.md:52 +#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:45 src/testing/ci-tiers.md:46 +#: src/testing/ci-tiers.md:47 src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:50 src/testing/ci-tiers.md:52 #: src/testing/ci-tiers.md:53 src/testing/ci-tiers.md:54 #: src/testing/ci-tiers.md:55 src/testing/ci-tiers.md:56 #: src/testing/ci-tiers.md:57 src/testing/ci-tiers.md:58 #: src/testing/ci-tiers.md:59 src/testing/ci-tiers.md:60 #: src/testing/ci-tiers.md:61 src/testing/ci-tiers.md:62 +#: src/testing/ci-tiers.md:63 src/testing/ci-tiers.md:64 +#: src/testing/ci-tiers.md:65 src/testing/ci-tiers.md:66 +#: src/testing/ci-tiers.md:67 src/testing/ci-tiers.md:68 #: src/internals/rfc-rooting-by-construction.md:277 #: src/internals/rfc-rooting-by-construction.md:278 #: src/internals/rfc-rooting-by-construction.md:279 @@ -10996,8 +10752,8 @@ msgid "" "Unlike handles and promises, `pod` has no string-only spelling; use object " "form so the field order and scalar ABI types are explicit. `json` is " "parameter-only: it serializes its argument with `JSON.stringify` at the " -"callsite and passes the result in a `string`\\-shaped slot (see \"Param " -"types\")." +"callsite and passes the result in a `string`\\-shaped slot (see \"Param types" +"\")." msgstr "" "`number` ist ein Kompatibilitätsalias für `f64`; `js_value` und `boolean` " "sind Kompatibilitätsalias für `jsvalue` und `bool`. Bare `handle` ist das " @@ -11162,7 +10918,7 @@ msgstr "boolean" #: src/testing/http2-conformance.md:289 src/cli/perry-toml.md:178 #: src/cli/perry-toml.md:257 src/cli/perry-toml.md:312 #: src/cli/perry-toml.md:369 src/cli/updates.md:38 -#: src/internals/memory-model.md:19 +#: src/internals/memory-model.md:21 msgid "`false`" msgstr "`false`" @@ -11671,7 +11427,7 @@ msgstr "`\"void\"`" msgid "`-> ()`" msgstr "`-> ()`" -#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:17 +#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:19 msgid "`undefined`" msgstr "`undefined`" @@ -11689,8 +11445,8 @@ msgstr "" #: src/native-libraries/manifest-v1.md:276 msgid "" "Note on `\"string\"` vs. `\"i64_str\"`: both produce a string on the " -"TypeScript side, but they differ in how Rust returns the pointer. Use " -"`\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " +"TypeScript side, but they differ in how Rust returns the pointer. Use `" +"\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " "u8` (or `*const StringHeader`); use `\"i64_str\"` when it's `-> i64` and the " "value happens to be a `StringHeader` address (closes [\\#222](https://github." "com/PerryTS/perry/issues/222))." @@ -11705,15 +11461,15 @@ msgstr "" #: src/native-libraries/manifest-v1.md:283 msgid "" -"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, " -"`\"json\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " +"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, `\"json" +"\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " "descriptors: `\"buffer+len\"` expands one JavaScript argument into two " "native ABI slots, while `pod` lowers one object-shaped argument to a pointer " "to verifier-backed C-layout storage." msgstr "" "`\"void\"` gilt nur als Rückgabedeskriptor. `\"buffer+len\"`, `\"json\"` und " -"`{ \"kind\": \"pod\", ... }` sind nur als Parameterdeskriptoren gültig: " -"`\"buffer+len\"` erweitert ein JavaScript-Argument in zwei native ABI-Slots, " +"`{ \"kind\": \"pod\", ... }` sind nur als Parameterdeskriptoren gültig: `" +"\"buffer+len\"` erweitert ein JavaScript-Argument in zwei native ABI-Slots, " "während `pod` ein objektförmiges Argument auf einen Zeiger auf einen " "prüfergestützten C-Layout-Speicher senkt." @@ -12096,7 +11852,7 @@ msgid "`d3d12`" msgstr "`d3d12`" #: src/native-libraries/manifest-v1.md:350 src/testing/geisterhand.md:780 -#: src/cli/flags.md:121 +#: src/cli/flags.md:143 msgid "`windows`" msgstr "`windows`" @@ -12977,13 +12733,11 @@ msgstr "" #: src/threading/overview.md:3 msgid "" -"Perry gives you real OS threads with a one-line API. No worker setup, no " -"message ports, no structured clone overhead. Just `parallelMap`, " -"`parallelFilter`, and `spawn`." +"Use `parallelMap`, `parallelFilter` and `spawn` to run native work on OS " +"threads." msgstr "" -"Perry bietet Ihnen echte OS-Threads mit einer Einzeilen-API. Keine Worker-" -"Einrichtung, keine Message Ports, kein Structured-Clone-Overhead. Nur " -"`parallelMap`, `parallelFilter` und `spawn`." +"Mit `parallelMap`, `parallelFilter` und `spawn` führen Sie native Arbeit auf" +" OS-Threads aus." #: src/threading/overview.md:5 msgid "" @@ -13045,128 +12799,29 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:32 +#: src/threading/overview.md:32 src/threading/overview.md:300 msgid "" -"This is something **no JavaScript runtime can do**. V8, Bun, and Deno are " -"all locked to one thread per isolate. Perry compiles to native code — there " -"are no isolates, no GIL, no structural limitations. Your code runs on real " -"OS threads with the full power of every CPU core." +"Perry workers run native code on OS threads, each with its own arena and " +"collector. Ordinary captures and results are serialized between heaps; this " +"has a cost proportional to the transferred data. SharedArrayBuffer and " +"supported ownership transfers are explicit exceptions, not permission to " +"share arbitrary heap pointers." msgstr "" -"Dies ist etwas, das **keine JavaScript-Laufzeit kann**. V8, Bun und Deno " -"sind alle auf einen Thread pro Isolate beschränkt. Perry kompiliert zu " -"nativem Code — es gibt keine Isolate, keinen GIL, keine strukturellen " -"Einschränkungen. Ihr Code läuft auf echten OS-Threads mit der vollen " -"Leistung jedes CPU-Kerns." +"Perry-Worker führen nativen Code auf OS-Threads aus, jeweils mit eigener " +"Arena und eigenem Kollektor. Gewöhnliche Captures und Ergebnisse werden " +"zwischen Heaps serialisiert; die Kosten wachsen mit der Datenmenge. " +"SharedArrayBuffer und unterstützte Besitztransfers sind ausdrückliche " +"Ausnahmen, keine Erlaubnis zum Teilen beliebiger Heapzeiger." #: src/threading/overview.md:34 -msgid "Why This Matters" -msgstr "Warum das wichtig ist" - -#: src/threading/overview.md:36 -msgid "" -"JavaScript's single-threaded model is its biggest performance bottleneck. " -"Here's how runtimes try to work around it:" -msgstr "" -"JavaScripts Single-Thread-Modell ist sein größter Performance-Engpass. So " -"versuchen Laufzeiten, es zu umgehen:" - -#: src/threading/overview.md:38 -msgid "Runtime" -msgstr "Laufzeit" - -#: src/threading/overview.md:38 -msgid "\"Multi-threading\"" -msgstr "\"Multi-Threading\"" - -#: src/threading/overview.md:38 -msgid "Reality" -msgstr "Realität" - -#: src/threading/overview.md:40 -msgid "**Node.js**" -msgstr "**Node.js**" - -#: src/threading/overview.md:40 src/api/reference.md:3633 -msgid "`worker_threads`" -msgstr "`worker_threads`" - -#: src/threading/overview.md:40 -msgid "" -"Separate V8 isolates. Data copied via structured clone. ~2MB RAM per worker. " -"Complex API." -msgstr "" -"Separate V8-Isolate. Daten werden via Structured Clone kopiert. ~2 MB RAM " -"pro Worker. Komplexe API." - -#: src/threading/overview.md:41 -msgid "**Deno**" -msgstr "**Deno**" - -#: src/threading/overview.md:41 src/threading/overview.md:42 -#: src/api/reference.md:532 src/api/reference.md:3640 -msgid "`Worker`" -msgstr "`Worker`" - -#: src/threading/overview.md:41 -msgid "Same as Node — isolated heaps, message passing only." -msgstr "Genauso wie Node — isolierte Heaps, nur Nachrichtenaustausch." - -#: src/threading/overview.md:42 -msgid "**Bun**" -msgstr "**Bun**" - -#: src/threading/overview.md:42 -msgid "Same architecture. Faster structured clone, still isolated." -msgstr "Gleiche Architektur. Schnellerer Structured Clone, weiterhin isoliert." - -#: src/threading/overview.md:43 -msgid "**Perry**" -msgstr "**Perry**" - -#: src/threading/overview.md:43 -msgid "`parallelMap` / `spawn`" -msgstr "`parallelMap` / `spawn`" - -#: src/threading/overview.md:43 -msgid "" -"Real OS threads. Lightweight (8MB stack). One-line API. Compile-time safety." -msgstr "" -"Echte OS-Threads. Leichtgewichtig (8 MB Stack). Einzeilen-API. Compile-Zeit-" -"Sicherheit." - -#: src/threading/overview.md:45 -msgid "" -"The fundamental problem: V8 uses a garbage-collected heap that **cannot be " -"shared** between threads. Every \"worker\" is an entirely separate " -"JavaScript engine instance with its own heap, its own GC, and its own copy " -"of your data." -msgstr "" -"Das fundamentale Problem: V8 verwendet einen Garbage-Collected-Heap, der " -"**nicht zwischen Threads geteilt werden kann**. Jeder \"Worker\" ist eine " -"vollständig separate JavaScript-Engine-Instanz mit eigenem Heap, eigenem GC " -"und eigener Kopie Ihrer Daten." - -#: src/threading/overview.md:47 -msgid "" -"Perry doesn't have this limitation. It compiles TypeScript to native machine " -"code. Values are transferred between threads using zero-cost copies for " -"numbers and efficient serialization for objects — no separate engine " -"instances, no multi-megabyte overhead per thread." -msgstr "" -"Perry hat diese Einschränkung nicht. Es kompiliert TypeScript zu nativem " -"Maschinencode. Werte werden zwischen Threads mit kostenlosen Kopien für " -"Zahlen und effizienter Serialisierung für Objekte übertragen — keine " -"separaten Engine-Instanzen, kein Multi-Megabyte-Overhead pro Thread." - -#: src/threading/overview.md:49 msgid "Three Primitives" msgstr "Drei Primitive" -#: src/threading/overview.md:51 +#: src/threading/overview.md:36 msgid "`parallelMap` — Data-Parallel Processing" msgstr "`parallelMap` — Datenparallele Verarbeitung" -#: src/threading/overview.md:53 +#: src/threading/overview.md:38 msgid "" "Split an array across all CPU cores. Each element is processed " "independently. Results are collected in order." @@ -13174,7 +12829,7 @@ msgstr "" "Teilen Sie ein Array über alle CPU-Kerne hinweg. Jedes Element wird " "unabhängig verarbeitet. Ergebnisse werden in der Reihenfolge gesammelt." -#: src/threading/overview.md:55 +#: src/threading/overview.md:40 msgid "" "```typescript\n" "async function overviewParallelMap(): Promise {\n" @@ -13208,31 +12863,31 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:71 src/plugins/creating-plugins.md:37 +#: src/threading/overview.md:56 src/plugins/creating-plugins.md:37 msgid "Perry automatically:" msgstr "Perry erledigt automatisch:" -#: src/threading/overview.md:72 +#: src/threading/overview.md:57 msgid "Detects the number of CPU cores" msgstr "Erkennt die Anzahl der CPU-Kerne" -#: src/threading/overview.md:73 +#: src/threading/overview.md:58 msgid "Splits the array into chunks (one per core)" msgstr "Teilt das Array in Chunks auf (einen pro Kern)" -#: src/threading/overview.md:74 +#: src/threading/overview.md:59 msgid "Spawns OS threads to process each chunk" msgstr "Startet OS-Threads, um jeden Chunk zu verarbeiten" -#: src/threading/overview.md:75 +#: src/threading/overview.md:60 msgid "Collects results in the original order" msgstr "Sammelt Ergebnisse in der ursprünglichen Reihenfolge" -#: src/threading/overview.md:76 +#: src/threading/overview.md:61 msgid "Returns a new array" msgstr "Gibt ein neues Array zurück" -#: src/threading/overview.md:78 +#: src/threading/overview.md:63 msgid "" "For small arrays, Perry skips threading entirely and processes inline — no " "overhead for trivial cases." @@ -13240,18 +12895,18 @@ msgstr "" "Für kleine Arrays überspringt Perry das Threading vollständig und " "verarbeitet inline — kein Overhead für triviale Fälle." -#: src/threading/overview.md:80 +#: src/threading/overview.md:65 msgid "`parallelFilter` — Data-Parallel Filtering" msgstr "`parallelFilter` — Datenparalleles Filtern" -#: src/threading/overview.md:82 +#: src/threading/overview.md:67 msgid "" "Filter a large array across all CPU cores. Like `.filter()` but parallel:" msgstr "" "Filtert ein großes Array über alle CPU-Kerne hinweg. Wie `.filter()`, aber " "parallel:" -#: src/threading/overview.md:84 +#: src/threading/overview.md:69 msgid "" "```typescript\n" "async function overviewParallelFilter(): Promise {\n" @@ -13291,7 +12946,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:102 +#: src/threading/overview.md:87 msgid "" "Same rules as `parallelMap`: closures cannot capture mutable variables " "(compile-time enforced), and values are deep-copied between threads." @@ -13300,11 +12955,11 @@ msgstr "" "Variablen erfassen (zur Compile-Zeit erzwungen), und Werte werden zwischen " "Threads deep-kopiert." -#: src/threading/overview.md:104 +#: src/threading/overview.md:89 msgid "`spawn` — Background Threads" msgstr "`spawn` — Hintergrund-Threads" -#: src/threading/overview.md:106 +#: src/threading/overview.md:91 msgid "" "Run any computation in the background and get a Promise back. The main " "thread continues immediately." @@ -13312,7 +12967,7 @@ msgstr "" "Führen Sie eine beliebige Berechnung im Hintergrund aus und erhalten Sie ein " "Promise zurück. Der Haupt-Thread wird sofort fortgesetzt." -#: src/threading/overview.md:108 +#: src/threading/overview.md:93 msgid "" "```typescript\n" "async function overviewSpawnBg(): Promise {\n" @@ -13354,7 +13009,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:128 +#: src/threading/overview.md:113 msgid "" "`spawn` returns a standard Promise. You can `await` it, pass it to `Promise." "all`, or chain `.then()` — it works exactly like any other async operation." @@ -13363,15 +13018,15 @@ msgstr "" "`Promise.all` übergeben oder `.then()` verketten — es funktioniert genau wie " "jede andere Async-Operation." -#: src/threading/overview.md:130 +#: src/threading/overview.md:115 msgid "Practical Examples" msgstr "Praktische Beispiele" -#: src/threading/overview.md:132 +#: src/threading/overview.md:117 msgid "Parallel Image Processing" msgstr "Parallele Bildverarbeitung" -#: src/threading/overview.md:134 +#: src/threading/overview.md:119 msgid "" "```typescript\n" "async function overviewImage(): Promise {\n" @@ -13415,11 +13070,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:154 +#: src/threading/overview.md:139 msgid "Parallel Cryptographic Hashing" msgstr "Paralleles kryptographisches Hashing" -#: src/threading/overview.md:156 +#: src/threading/overview.md:141 msgid "" "```typescript\n" "async function overviewCrypto(): Promise {\n" @@ -13457,11 +13112,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:174 +#: src/threading/overview.md:159 msgid "Multiple Independent Computations" msgstr "Mehrere unabhängige Berechnungen" -#: src/threading/overview.md:176 +#: src/threading/overview.md:161 msgid "" "```typescript\n" "async function overviewMultiple(): Promise {\n" @@ -13523,11 +13178,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:205 +#: src/threading/overview.md:190 msgid "Keeping UI Responsive" msgstr "UI reaktionsfähig halten" -#: src/threading/overview.md:207 +#: src/threading/overview.md:192 msgid "" "```typescript\n" "const responsiveButton = Button(\"Start Analysis\", async () => {\n" @@ -13565,11 +13220,11 @@ msgstr "" "const responsiveText = Text(`Status: ${status.value}`)\n" "```" -#: src/threading/overview.md:225 +#: src/threading/overview.md:210 msgid "Captured Variables" msgstr "Erfasste Variablen" -#: src/threading/overview.md:227 +#: src/threading/overview.md:212 msgid "" "Closures can capture outer variables. Captured values are automatically deep-" "copied to each worker thread:" @@ -13577,7 +13232,7 @@ msgstr "" "Closures können äußere Variablen erfassen. Erfasste Werte werden automatisch " "zu jedem Worker-Thread deep-kopiert:" -#: src/threading/overview.md:229 +#: src/threading/overview.md:214 msgid "" "```typescript\n" "async function overviewCaptured(): Promise {\n" @@ -13611,7 +13266,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:245 +#: src/threading/overview.md:230 msgid "" "Numbers and booleans are zero-cost copies (just 64-bit values). Strings, " "arrays, and objects are deep-copied automatically." @@ -13619,23 +13274,25 @@ msgstr "" "Zahlen und Booleans sind kostenlose Kopien (nur 64-Bit-Werte). Strings, " "Arrays und Objekte werden automatisch deep-kopiert." -#: src/threading/overview.md:247 +#: src/threading/overview.md:232 msgid "Safety" msgstr "Sicherheit" -#: src/threading/overview.md:249 +#: src/threading/overview.md:234 msgid "" -"Perry enforces thread safety **at compile time**. You don't need to think " -"about race conditions, mutexes, or data corruption." +"Compile-time admission checks restrict worker closures and module-global " +"access. Shared memory still requires synchronization, and native " +"integrations must respect thread ownership." msgstr "" -"Perry erzwingt Thread-Sicherheit **zur Compile-Zeit**. Sie müssen nicht über " -"Race Conditions, Mutexes oder Datenkorruption nachdenken." +"Zulassungsprüfungen beim Kompilieren beschränken Worker-Closures und " +"Modulglobalzugriffe. Gemeinsamer Speicher erfordert weiterhin " +"Synchronisierung; native Integrationen müssen den Threadbesitz respektieren." -#: src/threading/overview.md:251 +#: src/threading/overview.md:236 msgid "No Shared Mutable State" msgstr "Kein geteilter veränderlicher State" -#: src/threading/overview.md:253 +#: src/threading/overview.md:238 msgid "" "Closures passed to `parallelMap` and `spawn` **cannot capture mutable " "variables**. The compiler rejects this:" @@ -13643,7 +13300,7 @@ msgstr "" "An `parallelMap` und `spawn` übergebene Closures **können keine " "veränderlichen Variablen erfassen**. Der Compiler weist dies zurück:" -#: src/threading/overview.md:255 +#: src/threading/overview.md:240 msgid "" "```text\n" "// Reject example — Perry rejects this at compile time:\n" @@ -13671,7 +13328,7 @@ msgstr "" "});\n" "```" -#: src/threading/overview.md:268 +#: src/threading/overview.md:253 msgid "" "This eliminates data races by design. If you need to aggregate results, use " "the return values:" @@ -13679,7 +13336,7 @@ msgstr "" "Dies eliminiert Data Races by Design. Wenn Sie Ergebnisse aggregieren " "müssen, verwenden Sie die Rückgabewerte:" -#: src/threading/overview.md:270 +#: src/threading/overview.md:255 msgid "" "```typescript\n" "async function overviewReduceInstead(): Promise {\n" @@ -13705,7 +13362,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:282 +#: src/threading/overview.md:267 msgid "" "The one explicit shared-state escape hatch is `SharedArrayBuffer`: a SAB " "captured into a `spawn` / `parallelMap` closure aliases the same physical " @@ -13724,11 +13381,11 @@ msgstr "" "typisierte-Array-Ansicht über ihn pro-agent anstatt die Ansicht direkt zu " "erfassen." -#: src/threading/overview.md:289 +#: src/threading/overview.md:274 msgid "Workers Are Synchronous" msgstr "Arbeiter sind synchron" -#: src/threading/overview.md:291 +#: src/threading/overview.md:276 msgid "" "The closure passed to `parallelMap`, `parallelFilter`, or `spawn` cannot be " "`async`, contain `await` (directly or in a nested closure), or call another " @@ -13747,11 +13404,11 @@ msgstr "" "Das Async-Teil auf dem Hauptgewinde und `await` das `spawn` Ergebnis dort - " "das oben gezeigte Standardmuster bleibt unberührt." -#: src/threading/overview.md:299 +#: src/threading/overview.md:284 msgid "Module-Scope Objects Stay Home" msgstr "Modul-Scope-Objekte bleiben zu Hause" -#: src/threading/overview.md:301 +#: src/threading/overview.md:286 msgid "" "Worker closures may read module-scope **primitives** (numbers, strings, " "booleans), but not module-scope bindings that hold heap objects — object and " @@ -13778,23 +13435,11 @@ msgstr "" "Code – immer gut, um von einem Arbeiter zu rufen). `SharedArrayBuffer` Modul " "Globals sind ausgenommen: Cross-Thread-Sharing ist ihr Zweck." -#: src/threading/overview.md:313 +#: src/threading/overview.md:298 msgid "Independent Thread Arenas" msgstr "Unabhängige Thread-Arenas" -#: src/threading/overview.md:315 -msgid "" -"Each worker thread has its own memory arena. Objects created on one thread " -"can never be accessed from another thread. Values cross thread boundaries " -"only through deep-copy serialization, which Perry handles automatically and " -"invisibly." -msgstr "" -"Jeder Worker-Thread hat seine eigene Speicher-Arena. Auf einem Thread " -"erstellte Objekte können nie von einem anderen Thread aus zugegriffen " -"werden. Werte überschreiten Thread-Grenzen nur durch Deep-Copy-" -"Serialisierung, die Perry automatisch und unsichtbar handhabt." - -#: src/threading/overview.md:317 +#: src/threading/overview.md:302 msgid "" "File-system descriptors are also thread-affine. Numeric fds from `fs." "openSync` are just copied numbers in another thread, where the fd registry " @@ -13810,43 +13455,41 @@ msgstr "" "Handles mit `fd === -1`. Übergeben Sie Dateipfade an `spawn`/`parallelMap` " "und öffnen Sie Dateien innerhalb des Workers neu, wenn er Datei-I/O benötigt." -#: src/threading/overview.md:325 +#: src/threading/overview.md:310 msgid "Perry's threading model is built on three pillars:" msgstr "Perrys Threading-Modell basiert auf drei Säulen:" -#: src/threading/overview.md:327 -msgid "**1. Native Code, Not Interpreted**" -msgstr "**1. Nativer Code, nicht interpretiert**" +#: src/threading/overview.md:312 +msgid "**1. Native Code**" +msgstr "**1. Nativer Code**" -#: src/threading/overview.md:329 +#: src/threading/overview.md:314 msgid "" -"Perry compiles TypeScript to native machine code via LLVM. There's no " -"interpreter, no VM, no isolate. A function pointer is just a function " -"pointer — it's valid on any thread." +"Perry compiles TypeScript to native machine code via LLVM. Native worker " +"execution still requires the ownership and transfer rules described above." msgstr "" -"Perry kompiliert TypeScript via LLVM zu nativem Maschinencode. Es gibt " -"keinen Interpreter, keine VM, kein Isolate. Ein Funktionszeiger ist einfach " -"ein Funktionszeiger — er ist auf jedem Thread gültig." +"Perry kompiliert TypeScript über LLVM zu nativem Maschinencode. Auch native " +"Worker müssen die oben beschriebenen Besitz- und Transferregeln einhalten." -#: src/threading/overview.md:331 +#: src/threading/overview.md:316 msgid "**2. Thread-Local Memory**" msgstr "**2. Thread-lokaler Speicher**" -#: src/threading/overview.md:333 +#: src/threading/overview.md:318 msgid "" -"Each thread gets its own memory arena (bump allocator) and garbage " -"collector. No synchronization overhead during computation. When a thread " -"finishes, its arena is freed automatically." +"Each worker has its own arena and collector. This avoids a shared JavaScript" +" heap; explicit shared memory and runtime coordination still require " +"synchronization." msgstr "" -"Jeder Thread erhält seine eigene Speicher-Arena (Bump-Allokator) und Garbage " -"Collector. Kein Synchronisations-Overhead während der Berechnung. Wenn ein " -"Thread endet, wird seine Arena automatisch freigegeben." +"Jeder Worker besitzt seine eigene Arena und seinen eigenen Kollektor. Das " +"vermeidet einen gemeinsamen JavaScript-Heap; explizit geteilter Speicher und" +" Laufzeitkoordination benötigen weiterhin Synchronisierung." -#: src/threading/overview.md:335 +#: src/threading/overview.md:320 msgid "**3. Serialized Transfer**" msgstr "**3. Serialisierter Transfer**" -#: src/threading/overview.md:337 +#: src/threading/overview.md:322 msgid "" "Values crossing thread boundaries are serialized to a thread-safe " "intermediate format and deserialized on the target thread. The cost depends " @@ -13856,47 +13499,47 @@ msgstr "" "Zwischenformat serialisiert und auf dem Ziel-Thread deserialisiert. Die " "Kosten hängen vom Werttyp ab:" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Value Type" msgstr "Werttyp" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Transfer Cost" msgstr "Transferkosten" -#: src/threading/overview.md:341 +#: src/threading/overview.md:326 msgid "Numbers, booleans, null, undefined" msgstr "Zahlen, Booleans, null, undefined" -#: src/threading/overview.md:341 src/threading/parallel-map.md:55 +#: src/threading/overview.md:326 src/threading/parallel-map.md:55 msgid "Zero-cost (64-bit copy)" msgstr "Kostenlos (64-Bit-Kopie)" -#: src/threading/overview.md:342 +#: src/threading/overview.md:327 msgid "O(n) byte copy" msgstr "O(n) Byte-Kopie" -#: src/threading/overview.md:343 +#: src/threading/overview.md:328 msgid "O(n) deep copy of elements" msgstr "O(n) Deep-Copy der Elemente" -#: src/threading/overview.md:344 +#: src/threading/overview.md:329 msgid "O(n) deep copy of fields" msgstr "O(n) Deep-Copy der Felder" -#: src/threading/overview.md:345 +#: src/threading/overview.md:330 msgid "Pointer + captured values" msgstr "Zeiger + erfasste Werte" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "`fs` numeric fds / `FileHandle`" msgstr "`fs` numerische fds / `FileHandle`" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "Thread-affine; reopen by path" msgstr "Thread-affin; per Pfad neu öffnen" -#: src/threading/overview.md:348 +#: src/threading/overview.md:333 msgid "" "For numeric workloads — the most common parallelizable tasks — the threading " "overhead is negligible." @@ -13904,20 +13547,88 @@ msgstr "" "Für numerische Workloads — die häufigsten parallelisierbaren Aufgaben — ist " "der Threading-Overhead vernachlässigbar." -#: src/threading/overview.md:352 +#: src/threading/overview.md:335 +#, fuzzy +msgid "Node.js Worker Constructor Compatibility" +msgstr "Node.js Compatibility" + +#: src/threading/overview.md:337 +msgid "" +"For `node:worker_threads`, a truthy `Worker` `name` must be a string; " +"invalid names throw `TypeError` with `ERR_INVALID_ARG_TYPE` before " +"transferring data. Falsy names use the default. `transferList` is validated " +"even when `workerData` is undefined, and listed ArrayBuffers are detached " +"after a successful clone whether or not the data references them." +msgstr "" + +#: src/threading/overview.md:343 +msgid "" +"`setEnvironmentData` retains the caller's value until it is replaced or " +"deleted by setting `undefined`. A new worker receives a creation-time clone " +"of that map alongside `workerData`; shared references and cycles are " +"preserved within this snapshot, while later parent changes remain local. " +"Captured namespace methods and CommonJS imports use the same storage as " +"named imports. If cloning either data source fails, construction throws " +"before transferring any buffers. Clone and transfer marks are private, " +"thread-local identity metadata, not object properties. Primitive marks are " +"ignored. Clone marks reject ordinary objects and Errors in both global " +"`structuredClone` and worker messages; builtin clone kinds such as arrays, " +"Maps, Sets and buffers retain their own serialization behavior. Marked " +"transfer buffers are rejected without detachment. The marker table does not " +"keep its targets alive. Inside a Node-compatible Worker, process-wide " +"mutations such as `abort`, `chdir`, credential setters, IPC send/disconnect " +"and setting `umask` are rejected before OS effects. IPC members remain " +"absent unless enabled by the Worker startup environment. Disabled process " +"callables expose `disabled === true`; reading `umask` remains allowed. " +"Worker title writes remain local to the existing title value, and mutable " +"CommonJS/default reads observe the updated title. Named ESM title imports " +"remain a known parity limitation. Worker eval source must be a primitive " +"string. A URL with truthy `eval` throws before reading workerData or " +"detaching buffers; ordinary file URLs use the same registered entry table as " +"dynamically selected files. Literal string eval remains compiled ahead of " +"time; dynamically generated source is still unsupported. Worker `argv` " +"values are converted in constructor order and installed in the child's " +"existing process-argv authority, after execPath and the Worker entry name. " +"MessagePort and BroadcastChannel property reads return stored values without " +"invoking handlers; method calls retain their receiver through ordinary " +"property lookup. Message methods accept transfer iterables directly or " +"through `options.transfer`. The complete sequence is consumed before " +"transfer entries are validated; iterator errors retain their identity, and " +"rejection leaves ArrayBuffers attached. BroadcastChannel ignores its extra " +"argument. Worker constructor transfer admission remains separate; " +"MessagePort endpoint movement and direct-message routing/timeout parity are " +"not provided by this normalization. Main-thread `postMessageToThread` " +"rejects missing or stopped destinations before cloning the payload or " +"detaching transfer buffers. Worker-origin messages retain Node's forwarding " +"order: cloning precedes destination rejection. Admission does not guarantee " +"rollback if a destination exits concurrently after it was admitted. Core-" +"module messaging constructor exports retain the original thread-realm " +"MessageChannel, MessagePort and BroadcastChannel objects even after global " +"reassignment. Explicit CommonJS export overrides and named-export " +"synchronization remain separate; vm/eval global population does not replace " +"these thread-core values. MessagePort core methods (`postMessage`, `start`, " +"`close`, `ref`, `unref`, `hasRef`) operate on their actual receiver, " +"including calls borrowed from another port or its prototype. A private ID " +"supplies the brand without observable getters or proxy traps; closing a port " +"preserves its brand without retaining a closed runtime port in the delivery " +"table. Listener methods and realm movement remain separate parity work. " +"Other worker API parity gaps remain." +msgstr "" + +#: src/threading/overview.md:394 msgid "" "[parallelMap Reference](parallel-map.md) — detailed API and performance tips" msgstr "" "[parallelMap-Referenz](parallel-map.md) — detaillierte API- und Performance-" "Tipps" -#: src/threading/overview.md:353 +#: src/threading/overview.md:395 msgid "" "[parallelFilter Reference](parallel-filter.md) — parallel array filtering" msgstr "" "[parallelFilter-Referenz](parallel-filter.md) — paralleles Array-Filtern" -#: src/threading/overview.md:354 +#: src/threading/overview.md:396 msgid "" "[spawn Reference](spawn.md) — background threads and Promise integration" msgstr "" @@ -14666,8 +14377,8 @@ msgid "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" msgstr "" @@ -14691,8 +14402,8 @@ msgstr "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" @@ -15205,8 +14916,8 @@ msgid "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" msgstr "" @@ -15223,8 +14934,8 @@ msgstr "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" @@ -15462,7 +15173,7 @@ msgstr "" msgid "Property" msgstr "Eigenschaft" -#: src/ui/overview.md:79 src/api/reference.md:2799 +#: src/ui/overview.md:79 src/api/reference.md:2804 msgid "`title`" msgstr "`title`" @@ -15990,7 +15701,7 @@ msgstr "NSMenu" #: src/cli/perry-toml.md:389 src/cli/perry-toml.md:418 #: src/cli/app-updates.md:56 src/cli/app-updates.md:57 #: src/cli/app-updates.md:58 src/cli/app-updates.md:60 -#: src/internals/memory-model.md:325 src/internals/memory-model.md:326 +#: src/internals/memory-model.md:309 src/internals/memory-model.md:310 #: src/contributing/building.md:55 msgid "—" msgstr "—" @@ -16589,8 +16300,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -16615,8 +16326,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -18029,8 +17740,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18065,8 +17776,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18222,7 +17933,7 @@ msgstr "Name" #: src/container/overview.md:112 src/container/containers.md:46 #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 -#: src/internals/memory-model.md:168 src/internals/memory-model.md:185 +#: src/internals/memory-model.md:153 src/internals/memory-model.md:170 #: src/internals/weakmap-owned-index-validation.md:78 #: src/internals/weakmap-owned-index-validation.md:100 msgid "Effect" @@ -18386,7 +18097,7 @@ msgstr "Alle Kinder erhalten gleiche Größe" #: src/ui/styling-matrix.md:97 src/tui/widgets.md:60 #: src/platforms/windows-7.md:26 src/container/overview.md:72 #: src/container/determinism.md:60 src/testing/geisterhand.md:88 -#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:190 +#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:197 #: src/cli/perry-toml.md:430 src/internals/gc-rooting-invariant.md:358 #: src/internals/gc-rooting-invariant.md:359 #: src/internals/rfc-rooting-by-construction.md:355 @@ -18475,8 +18186,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: web, wasm\n" "\n" -"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/" -"ui\";\n" +"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/ui" +"\";\n" "\n" "const column = VStack(12, [Text(\"Responsive content\")]);\n" "setPadding(column, 32);\n" @@ -23446,17 +23157,15 @@ msgstr "" #: src/ui/multi-window.md:118 msgid "" -"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `\"menu" +"\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground\"`, `" +"\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground\"`, `" +"\"underWindowBackground\"`, `\"underPageBackground\"`" msgstr "" -"**macOS-Materialien:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**macOS-Materialien:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `" +"\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground" +"\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground" +"\"`, `\"underWindowBackground\"`, `\"underPageBackground\"`" #: src/ui/multi-window.md:125 msgid "`NSVisualEffectView` with the specified material" @@ -24446,13 +24155,13 @@ msgid "" "\n" "const auth = WebView({\n" " url: \"https://accounts.google.com/o/oauth2/auth?client_id=..." -"&redirect_uri=https://myapp.com/oauth/" -"callback&response_type=code&scope=email\",\n" +"&redirect_uri=https://myapp.com/oauth/callback&response_type=code&scope=email" +"\",\n" " // Hard host-level allowlist — blocked at the native delegate\n" " // without round-tripping into TS. Exact match or subdomain match\n" " // (so \"google.com\" allows \"accounts.google.com\").\n" -" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp." -"com\"],\n" +" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp.com" +"\"],\n" " onShouldNavigate: (url) => {\n" " if (url.startsWith(\"https://myapp.com/oauth/callback?\")) {\n" " const code = new URL(url).searchParams.get(\"code\") ?? \"\"\n" @@ -24475,13 +24184,13 @@ msgstr "" "\n" "const auth = WebView({\n" " url: \"https://accounts.google.com/o/oauth2/auth?client_id=..." -"&redirect_uri=https://myapp.com/oauth/" -"callback&response_type=code&scope=email\",\n" +"&redirect_uri=https://myapp.com/oauth/callback&response_type=code&scope=email" +"\",\n" " // Hard host-level allowlist — blocked at the native delegate\n" " // without round-tripping into TS. Exact match or subdomain match\n" " // (so \"google.com\" allows \"accounts.google.com\").\n" -" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp." -"com\"],\n" +" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp.com" +"\"],\n" " onShouldNavigate: (url) => {\n" " if (url.startsWith(\"https://myapp.com/oauth/callback?\")) {\n" " const code = new URL(url).searchParams.get(\"code\") ?? \"\"\n" @@ -24812,7 +24521,7 @@ msgstr "" msgid "Options" msgstr "Optionen" -#: src/ui/webview.md:186 src/api/reference.md:3396 src/cli/perry-toml.md:443 +#: src/ui/webview.md:186 src/api/reference.md:3416 src/cli/perry-toml.md:443 #: src/cli/app-updates.md:57 src/cli/app-updates.md:80 msgid "`url`" msgstr "`url`" @@ -24867,7 +24576,7 @@ msgstr "`ephemeral`" #: src/platforms/watchos-app-store.md:103 src/testing/http2-conformance.md:286 #: src/testing/http2-conformance.md:287 src/testing/http2-conformance.md:288 #: src/testing/http2-conformance.md:289 src/cli/app-updates.md:67 -#: src/internals/memory-model.md:20 +#: src/internals/memory-model.md:22 msgid "`true`" msgstr "`true`" @@ -25223,8 +24932,8 @@ msgstr "" #: src/tui/overview.md:18 msgid "" -"The smallest interactive `perry/tui` program — a counter that increments on " -"`+`, decrements on `-`, and quits on `q`:" +"The smallest interactive `perry/tui` program — a counter that increments on `" +"+`, decrements on `-`, and quits on `q`:" msgstr "" "Das kleinste interaktive `perry/tui`-Programm — ein Zähler, der bei `+` " "erhöht, bei `-` verringert und bei `q` beendet wird:" @@ -26383,14 +26092,14 @@ msgstr "" #: src/tui/hooks.md:161 msgid "" "The `s` argument is the raw byte chunk as a string. ANSI escape sequences " -"like arrow keys arrive as a single chunk (`\\x1b[A`, `\\x1b[B`, `\\x1b[C`, " -"`\\x1b[D`); printable characters as one byte; control codes (Ctrl+C, Tab, " +"like arrow keys arrive as a single chunk (`\\x1b[A`, `\\x1b[B`, `\\x1b[C`, `" +"\\x1b[D`); printable characters as one byte; control codes (Ctrl+C, Tab, " "Enter, Backspace) as their literal byte." msgstr "" "Das Argument `s` ist der rohe Byte-Chunk als String. ANSI-Escape-Sequenzen " -"wie Pfeiltasten kommen als einzelner Chunk an (`\\x1b[A`, `\\x1b[B`, " -"`\\x1b[C`, `\\x1b[D`); druckbare Zeichen als ein Byte; Steuerzeichen " -"(Ctrl+C, Tab, Enter, Backspace) als ihr wörtliches Byte." +"wie Pfeiltasten kommen als einzelner Chunk an (`\\x1b[A`, `\\x1b[B`, `" +"\\x1b[C`, `\\x1b[D`); druckbare Zeichen als ein Byte; Steuerzeichen (Ctrl+C, " +"Tab, Enter, Backspace) als ihr wörtliches Byte." #: src/tui/hooks.md:163 msgid "" @@ -26423,7 +26132,7 @@ msgstr "Äquivalenz mit ink" msgid "ink" msgstr "ink" -#: src/tui/hooks.md:169 src/api/reference.md:2452 +#: src/tui/hooks.md:169 src/api/reference.md:2457 msgid "`perry/tui`" msgstr "`perry/tui`" @@ -26967,10 +26676,10 @@ msgid "" "Ctrl+C (or whatever exit key the program defines), and restores everything " "cleanly on exit. Your scrollback is untouched." msgstr "" -"Das Binary öffnet den Alternativbildschirm, übernimmt das Terminal, bis " -"Ctrl+C gedrückt wird (oder welche Exit-Taste auch immer das Programm " -"definiert), und stellt beim Beenden alles sauber wieder her. Der Scrollback-" -"Puffer bleibt unberührt." +"Das Binary öffnet den Alternativbildschirm, übernimmt das Terminal, bis Ctrl" +"+C gedrückt wird (oder welche Exit-Taste auch immer das Programm definiert), " +"und stellt beim Beenden alles sauber wieder her. Der Scrollback-Puffer " +"bleibt unberührt." #: src/tui/examples.md:205 msgid "" @@ -28918,7 +28627,7 @@ msgid "Build environment variables" msgstr "Umgebungsvariablen erstellen" #: src/platforms/watchos.md:82 src/container/overview.md:112 -#: src/cli/flags.md:399 src/cli/flags.md:435 src/cli/perry-toml.md:518 +#: src/cli/flags.md:421 src/cli/flags.md:457 src/cli/perry-toml.md:518 #: src/cli/perry-toml.md:530 src/cli/perry-toml.md:540 msgid "Variable" msgstr "Variable" @@ -31171,13 +30880,13 @@ msgstr "" #: src/platforms/windows.md:19 msgid "" -"`perry setup windows` downloads ~700 MB (unpacks to ~1.5 GB) at " -"`%LOCALAPPDATA%\\perry\\windows-sdk` after prompting you to accept the " +"`perry setup windows` downloads ~700 MB (unpacks to ~1.5 GB) at `" +"%LOCALAPPDATA%\\perry\\windows-sdk` after prompting you to accept the " "Microsoft redistributable license. Pass `--accept-license` to skip the " "prompt in CI. Partial downloads resume safely on re-run." msgstr "" -"`perry setup windows` lädt ~700 MB herunter (entpackt auf ~1,5 GB) nach " -"`%LOCALAPPDATA%\\perry\\windows-sdk`, nachdem Sie aufgefordert werden, die " +"`perry setup windows` lädt ~700 MB herunter (entpackt auf ~1,5 GB) nach `" +"%LOCALAPPDATA%\\perry\\windows-sdk`, nachdem Sie aufgefordert werden, die " "Microsoft-Redistributable-Lizenz zu akzeptieren. Übergeben Sie `--accept-" "license`, um den Prompt in CI zu überspringen. Teilweise Downloads werden " "bei einem erneuten Lauf sicher fortgesetzt." @@ -31386,15 +31095,15 @@ msgstr "`LNK1181: cannot open input file 'user32.lib'`" #: src/platforms/windows.md:115 msgid "" "The linker couldn't find the Windows SDK libraries. Perry probes the " -"registry (`KitsRoot10`) and the standard `Windows " -"Kits\\10\\Lib\\\\um\\x64` install paths; when the probe fails it prints " -"a warning listing the paths it tried. Fixes, in order of preference:" +"registry (`KitsRoot10`) and the standard `Windows Kits\\10\\Lib\\\\um" +"\\x64` install paths; when the probe fails it prints a warning listing the " +"paths it tried. Fixes, in order of preference:" msgstr "" "Der Linker konnte die Windows SDK-Bibliotheken nicht finden. Perry ertastert " -"die Registry (`KitsRoot10`) und die Standard-`Windows " -"Kits\\10\\Lib\\\\um\\x64`-Installationspfade; wenn die Sonde " -"fehlschlägt, gibt sie eine Warnung aus, die die von ihr ausprobierten Pfade " -"auflistet. Befestigt in der Reihenfolge der Präferenz:" +"die Registry (`KitsRoot10`) und die Standard-`Windows Kits\\10\\Lib\\" +"\\um\\x64`-Installationspfade; wenn die Sonde fehlschlägt, gibt sie eine " +"Warnung aus, die die von ihr ausprobierten Pfade auflistet. Befestigt in der " +"Reihenfolge der Präferenz:" #: src/platforms/windows.md:117 msgid "" @@ -33297,7 +33006,7 @@ msgstr "" msgid "[File System](fs.md)" msgstr "[Dateisystem](fs.md)" -#: src/stdlib/overview.md:131 src/stdlib/fs.md:100 +#: src/stdlib/overview.md:131 src/stdlib/fs.md:107 msgid "[HTTP & Networking](http.md)" msgstr "[HTTP & Netzwerk](http.md)" @@ -33524,7 +33233,17 @@ msgstr "" "Übergeben Sie Dateipfade über Thread-Grenzen hinweg und öffnen Sie Dateien " "innerhalb des Workers neu." -#: src/stdlib/fs.md:101 src/stdlib/http.md:411 src/stdlib/database.md:115 +#: src/stdlib/fs.md:98 +msgid "" +"`perry/thread` deep-copies function-local captures. In descriptor-isolation " +"tests, create the descriptor and worker closure inside an ordinary function " +"so the test reaches that capture path instead of the compiler's module-" +"global heap guard. The FS parity fixtures use this structure without " +"changing their `EBADF` checks or expected output: this is fixture-structure " +"alignment, not a runtime or compiler descriptor fix." +msgstr "" + +#: src/stdlib/fs.md:108 src/stdlib/http.md:411 src/stdlib/database.md:115 #: src/stdlib/crypto.md:90 src/stdlib/utilities.md:99 src/stdlib/other.md:335 msgid "[Overview](overview.md) — All stdlib modules" msgstr "[Überblick](overview.md) — Alle stdlib-Module" @@ -34120,8 +33839,8 @@ msgid "" " const data = await response.json()\n" "\n" " // POST request\n" -" const result = await fetch(\"https://jsonplaceholder.typicode.com/" -"posts\", {\n" +" const result = await fetch(\"https://jsonplaceholder.typicode.com/posts" +"\", {\n" " method: \"POST\",\n" " headers: { \"Content-Type\": \"application/json\" },\n" " body: JSON.stringify({ title: \"hello\", body: \"world\", userId: " @@ -34140,8 +33859,8 @@ msgstr "" " const data = await response.json()\n" "\n" " // POST request\n" -" const result = await fetch(\"https://jsonplaceholder.typicode.com/" -"posts\", {\n" +" const result = await fetch(\"https://jsonplaceholder.typicode.com/posts" +"\", {\n" " method: \"POST\",\n" " headers: { \"Content-Type\": \"application/json\" },\n" " body: JSON.stringify({ title: \"hello\", body: \"world\", userId: " @@ -34252,11 +33971,11 @@ msgstr "" #: src/stdlib/http.md:333 msgid "" "For manual routing, create `new WebSocketServer({ noServer: true })` and " -"call `wss.handleUpgrade(req, socket, head, (ws, req) => wss." -"emit(\"connection\", ws, req))` from the HTTP server's `upgrade` listener. " -"The callback runs once; it controls whether the connection event is emitted. " -"The native HTTP transport performs the handshake before dispatching " -"`upgrade`, as described above." +"call `wss.handleUpgrade(req, socket, head, (ws, req) => wss.emit(\"connection" +"\", ws, req))` from the HTTP server's `upgrade` listener. The callback runs " +"once; it controls whether the connection event is emitted. The native HTTP " +"transport performs the handshake before dispatching `upgrade`, as described " +"above." msgstr "" #: src/stdlib/http.md:339 @@ -34655,8 +34374,8 @@ msgid "" " const db = client.db(\"mydb\")\n" " const users = db.collection(\"users\")\n" "\n" -" await users.insertOne({ name: \"Perry\", email: \"perry@example." -"com\" })\n" +" await users.insertOne({ name: \"Perry\", email: \"perry@example.com" +"\" })\n" " const user = await users.findOne({ name: \"Perry\" })\n" " console.log(user)\n" "\n" @@ -34674,8 +34393,8 @@ msgstr "" " const db = client.db(\"mydb\")\n" " const users = db.collection(\"users\")\n" "\n" -" await users.insertOne({ name: \"Perry\", email: \"perry@example." -"com\" })\n" +" await users.insertOne({ name: \"Perry\", email: \"perry@example.com" +"\" })\n" " const user = await users.findOne({ name: \"Perry\" })\n" " console.log(user)\n" "\n" @@ -34830,8 +34549,8 @@ msgid "" "\n" "function cryptoExample(): void {\n" " // Hash\n" -" const hash = crypto.createHash(\"sha256\").update(\"data\")." -"digest(\"hex\")\n" +" const hash = crypto.createHash(\"sha256\").update(\"data\").digest(\"hex" +"\")\n" "\n" " // HMAC\n" " const hmac = crypto.createHmac(\"sha256\", \"secret\").update(\"data\")." @@ -34840,8 +34559,8 @@ msgid "" " // Random bytes\n" " const bytes = crypto.randomBytes(32)\n" "\n" -" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} " -"bytes_len=${bytes.length}`)\n" +" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} bytes_len=" +"${bytes.length}`)\n" "}\n" "```" msgstr "" @@ -34850,8 +34569,8 @@ msgstr "" "\n" "function cryptoExample(): void {\n" " // Hash\n" -" const hash = crypto.createHash(\"sha256\").update(\"data\")." -"digest(\"hex\")\n" +" const hash = crypto.createHash(\"sha256\").update(\"data\").digest(\"hex" +"\")\n" "\n" " // HMAC\n" " const hmac = crypto.createHmac(\"sha256\", \"secret\").update(\"data\")." @@ -34860,8 +34579,8 @@ msgstr "" " // Random bytes\n" " const bytes = crypto.randomBytes(32)\n" "\n" -" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} " -"bytes_len=${bytes.length}`)\n" +" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} bytes_len=" +"${bytes.length}`)\n" "}\n" "```" @@ -35120,9 +34839,9 @@ msgstr "" #: src/stdlib/utilities.md:13 #, fuzzy msgid "" -"Use named imports: the default-import receiver form (`import _ from " -"\"lodash\"; _.chunk(...)`) is not routed to these native signatures because " -"it would pass the module object as an extra receiver argument." +"Use named imports: the default-import receiver form (`import _ from \"lodash" +"\"; _.chunk(...)`) is not routed to these native signatures because it would " +"pass the module object as an extra receiver argument." msgstr "" "Benutze benannte Importe: Das Standard-Import-Empfänger-Formular (`import _ " "from \"lodash\"; _.chunk(...)`) wird nicht auf diese nativen Signaturen " @@ -36097,7 +35816,7 @@ msgstr "" #: src/api/reference.md:5 #, fuzzy -msgid "Total: 2794 entries across 112 modules." +msgid "Total: 2802 entries across 113 modules." msgstr "Insgesamt: 3019 Einträge in 138 Modulen." #: src/api/reference.md:9 @@ -36505,4085 +36224,4102 @@ msgid "[`tls`](#tls)" msgstr "[`tls`](#tls)" #: src/api/reference.md:109 +#, fuzzy +msgid "[`trace_events`](#trace_events)" +msgstr "[`events`](#events)" + +#: src/api/reference.md:110 msgid "[`tty`](#tty)" msgstr "[`tty`](#tty)" -#: src/api/reference.md:110 +#: src/api/reference.md:111 msgid "[`typescript`](#typescript)" msgstr "[`typescript`](#typescript)" -#: src/api/reference.md:111 +#: src/api/reference.md:112 msgid "[`undici`](#undici)" msgstr "[`undici`](#undici)" -#: src/api/reference.md:112 +#: src/api/reference.md:113 msgid "[`url`](#url)" msgstr "[`url`](#url)" -#: src/api/reference.md:113 +#: src/api/reference.md:114 msgid "[`util`](#util)" msgstr "[`util`](#util)" -#: src/api/reference.md:114 +#: src/api/reference.md:115 msgid "[`util/types`](#utiltypes)" msgstr "[`util/types`](#utiltypes)" -#: src/api/reference.md:115 +#: src/api/reference.md:116 msgid "[`v8`](#v8)" msgstr "[`v8`](#v8)" -#: src/api/reference.md:116 +#: src/api/reference.md:117 msgid "[`vm`](#vm)" msgstr "[`vm`](#vm)" -#: src/api/reference.md:117 +#: src/api/reference.md:118 msgid "[`wasi`](#wasi)" msgstr "[`wasi`](#wasi)" -#: src/api/reference.md:118 +#: src/api/reference.md:119 msgid "[`worker_threads`](#worker_threads)" msgstr "[`worker_threads`](#worker_threads)" -#: src/api/reference.md:119 +#: src/api/reference.md:120 msgid "[`ws`](#ws)" msgstr "[`ws`](#ws)" -#: src/api/reference.md:120 +#: src/api/reference.md:121 msgid "[`zlib`](#zlib)" msgstr "[`zlib`](#zlib)" -#: src/api/reference.md:124 +#: src/api/reference.md:125 msgid "`@lydell/node-pty`" msgstr "`@lydell/node-pty`" -#: src/api/reference.md:126 src/api/reference.md:136 src/api/reference.md:146 -#: src/api/reference.md:152 src/api/reference.md:158 src/api/reference.md:164 -#: src/api/reference.md:170 src/api/reference.md:176 src/api/reference.md:182 -#: src/api/reference.md:188 src/api/reference.md:194 src/api/reference.md:204 -#: src/api/reference.md:216 src/api/reference.md:228 src/api/reference.md:262 -#: src/api/reference.md:296 src/api/reference.md:324 src/api/reference.md:331 -#: src/api/reference.md:355 src/api/reference.md:377 src/api/reference.md:426 -#: src/api/reference.md:436 src/api/reference.md:456 src/api/reference.md:467 -#: src/api/reference.md:491 src/api/reference.md:513 src/api/reference.md:534 -#: src/api/reference.md:559 src/api/reference.md:840 src/api/reference.md:917 -#: src/api/reference.md:966 src/api/reference.md:985 src/api/reference.md:1094 -#: src/api/reference.md:1172 src/api/reference.md:1196 -#: src/api/reference.md:1212 src/api/reference.md:1264 -#: src/api/reference.md:1270 src/api/reference.md:1295 -#: src/api/reference.md:1400 src/api/reference.md:1459 -#: src/api/reference.md:1658 src/api/reference.md:1682 -#: src/api/reference.md:1734 src/api/reference.md:1765 -#: src/api/reference.md:1781 src/api/reference.md:1820 -#: src/api/reference.md:1867 src/api/reference.md:2001 -#: src/api/reference.md:2007 src/api/reference.md:2017 -#: src/api/reference.md:2025 src/api/reference.md:2057 -#: src/api/reference.md:2083 src/api/reference.md:2109 -#: src/api/reference.md:2145 src/api/reference.md:2163 -#: src/api/reference.md:2174 src/api/reference.md:2186 -#: src/api/reference.md:2215 src/api/reference.md:2223 -#: src/api/reference.md:2238 src/api/reference.md:2252 -#: src/api/reference.md:2280 src/api/reference.md:2294 -#: src/api/reference.md:2302 src/api/reference.md:2315 -#: src/api/reference.md:2327 src/api/reference.md:2347 -#: src/api/reference.md:2375 src/api/reference.md:2392 -#: src/api/reference.md:2446 src/api/reference.md:2454 -#: src/api/reference.md:2519 src/api/reference.md:2648 -#: src/api/reference.md:2672 src/api/reference.md:2678 -#: src/api/reference.md:2692 src/api/reference.md:2711 -#: src/api/reference.md:2805 src/api/reference.md:2820 -#: src/api/reference.md:2836 src/api/reference.md:2865 -#: src/api/reference.md:2878 src/api/reference.md:2902 -#: src/api/reference.md:2916 src/api/reference.md:2951 -#: src/api/reference.md:3015 src/api/reference.md:3091 -#: src/api/reference.md:3106 src/api/reference.md:3158 -#: src/api/reference.md:3172 src/api/reference.md:3213 -#: src/api/reference.md:3251 src/api/reference.md:3265 -#: src/api/reference.md:3280 src/api/reference.md:3296 -#: src/api/reference.md:3342 src/api/reference.md:3365 -#: src/api/reference.md:3383 src/api/reference.md:3405 -#: src/api/reference.md:3434 src/api/reference.md:3475 -#: src/api/reference.md:3531 src/api/reference.md:3585 -#: src/api/reference.md:3621 src/api/reference.md:3642 -#: src/api/reference.md:3689 src/api/reference.md:3746 +#: src/api/reference.md:127 src/api/reference.md:137 src/api/reference.md:147 +#: src/api/reference.md:153 src/api/reference.md:159 src/api/reference.md:165 +#: src/api/reference.md:171 src/api/reference.md:177 src/api/reference.md:183 +#: src/api/reference.md:189 src/api/reference.md:195 src/api/reference.md:205 +#: src/api/reference.md:217 src/api/reference.md:229 src/api/reference.md:263 +#: src/api/reference.md:297 src/api/reference.md:325 src/api/reference.md:332 +#: src/api/reference.md:357 src/api/reference.md:379 src/api/reference.md:428 +#: src/api/reference.md:438 src/api/reference.md:458 src/api/reference.md:469 +#: src/api/reference.md:493 src/api/reference.md:515 src/api/reference.md:536 +#: src/api/reference.md:561 src/api/reference.md:842 src/api/reference.md:919 +#: src/api/reference.md:968 src/api/reference.md:987 src/api/reference.md:1096 +#: src/api/reference.md:1174 src/api/reference.md:1198 +#: src/api/reference.md:1214 src/api/reference.md:1266 +#: src/api/reference.md:1272 src/api/reference.md:1297 +#: src/api/reference.md:1402 src/api/reference.md:1461 +#: src/api/reference.md:1660 src/api/reference.md:1684 +#: src/api/reference.md:1736 src/api/reference.md:1767 +#: src/api/reference.md:1783 src/api/reference.md:1822 +#: src/api/reference.md:1869 src/api/reference.md:2003 +#: src/api/reference.md:2009 src/api/reference.md:2019 +#: src/api/reference.md:2027 src/api/reference.md:2059 +#: src/api/reference.md:2085 src/api/reference.md:2111 +#: src/api/reference.md:2147 src/api/reference.md:2165 +#: src/api/reference.md:2176 src/api/reference.md:2188 +#: src/api/reference.md:2217 src/api/reference.md:2225 +#: src/api/reference.md:2243 src/api/reference.md:2257 +#: src/api/reference.md:2285 src/api/reference.md:2299 +#: src/api/reference.md:2307 src/api/reference.md:2320 +#: src/api/reference.md:2332 src/api/reference.md:2352 +#: src/api/reference.md:2380 src/api/reference.md:2397 +#: src/api/reference.md:2451 src/api/reference.md:2459 +#: src/api/reference.md:2524 src/api/reference.md:2653 +#: src/api/reference.md:2677 src/api/reference.md:2683 +#: src/api/reference.md:2697 src/api/reference.md:2716 +#: src/api/reference.md:2810 src/api/reference.md:2825 +#: src/api/reference.md:2841 src/api/reference.md:2874 +#: src/api/reference.md:2887 src/api/reference.md:2911 +#: src/api/reference.md:2925 src/api/reference.md:2960 +#: src/api/reference.md:3024 src/api/reference.md:3100 +#: src/api/reference.md:3115 src/api/reference.md:3167 +#: src/api/reference.md:3181 src/api/reference.md:3222 +#: src/api/reference.md:3260 src/api/reference.md:3274 +#: src/api/reference.md:3289 src/api/reference.md:3305 +#: src/api/reference.md:3346 src/api/reference.md:3362 +#: src/api/reference.md:3385 src/api/reference.md:3403 +#: src/api/reference.md:3425 src/api/reference.md:3454 +#: src/api/reference.md:3495 src/api/reference.md:3551 +#: src/api/reference.md:3605 src/api/reference.md:3641 +#: src/api/reference.md:3662 src/api/reference.md:3709 +#: src/api/reference.md:3766 msgid "Methods" msgstr "Methoden" -#: src/api/reference.md:128 src/api/reference.md:399 src/api/reference.md:428 -#: src/api/reference.md:521 src/api/reference.md:2009 src/api/reference.md:2450 +#: src/api/reference.md:129 src/api/reference.md:401 src/api/reference.md:430 +#: src/api/reference.md:523 src/api/reference.md:2011 src/api/reference.md:2455 msgid "`spawn` — module" msgstr "`spawn` — Modul" -#: src/api/reference.md:130 src/api/reference.md:251 src/api/reference.md:285 -#: src/api/reference.md:317 src/api/reference.md:364 src/api/reference.md:411 -#: src/api/reference.md:430 src/api/reference.md:448 src/api/reference.md:484 -#: src/api/reference.md:524 src/api/reference.md:541 src/api/reference.md:588 -#: src/api/reference.md:904 src/api/reference.md:955 src/api/reference.md:975 -#: src/api/reference.md:1029 src/api/reference.md:1138 -#: src/api/reference.md:1188 src/api/reference.md:1245 -#: src/api/reference.md:1278 src/api/reference.md:1393 -#: src/api/reference.md:1438 src/api/reference.md:1643 -#: src/api/reference.md:1668 src/api/reference.md:1724 -#: src/api/reference.md:1753 src/api/reference.md:1775 -#: src/api/reference.md:1846 src/api/reference.md:2011 -#: src/api/reference.md:2048 src/api/reference.md:2073 -#: src/api/reference.md:2099 src/api/reference.md:2125 -#: src/api/reference.md:2155 src/api/reference.md:2168 -#: src/api/reference.md:2365 src/api/reference.md:2685 -#: src/api/reference.md:2769 src/api/reference.md:2812 -#: src/api/reference.md:2830 src/api/reference.md:2893 -#: src/api/reference.md:2910 src/api/reference.md:3000 -#: src/api/reference.md:3084 src/api/reference.md:3100 -#: src/api/reference.md:3135 src/api/reference.md:3206 -#: src/api/reference.md:3243 src/api/reference.md:3259 -#: src/api/reference.md:3274 src/api/reference.md:3286 -#: src/api/reference.md:3325 src/api/reference.md:3370 -#: src/api/reference.md:3421 src/api/reference.md:3468 -#: src/api/reference.md:3574 src/api/reference.md:3610 -#: src/api/reference.md:3629 src/api/reference.md:3669 -#: src/api/reference.md:3714 src/api/reference.md:3783 +#: src/api/reference.md:131 src/api/reference.md:252 src/api/reference.md:286 +#: src/api/reference.md:318 src/api/reference.md:366 src/api/reference.md:413 +#: src/api/reference.md:432 src/api/reference.md:450 src/api/reference.md:486 +#: src/api/reference.md:526 src/api/reference.md:543 src/api/reference.md:590 +#: src/api/reference.md:906 src/api/reference.md:957 src/api/reference.md:977 +#: src/api/reference.md:1031 src/api/reference.md:1140 +#: src/api/reference.md:1190 src/api/reference.md:1247 +#: src/api/reference.md:1280 src/api/reference.md:1395 +#: src/api/reference.md:1440 src/api/reference.md:1645 +#: src/api/reference.md:1670 src/api/reference.md:1726 +#: src/api/reference.md:1755 src/api/reference.md:1777 +#: src/api/reference.md:1848 src/api/reference.md:2013 +#: src/api/reference.md:2050 src/api/reference.md:2075 +#: src/api/reference.md:2101 src/api/reference.md:2127 +#: src/api/reference.md:2157 src/api/reference.md:2170 +#: src/api/reference.md:2370 src/api/reference.md:2690 +#: src/api/reference.md:2774 src/api/reference.md:2817 +#: src/api/reference.md:2835 src/api/reference.md:2863 +#: src/api/reference.md:2902 src/api/reference.md:2919 +#: src/api/reference.md:3009 src/api/reference.md:3093 +#: src/api/reference.md:3109 src/api/reference.md:3144 +#: src/api/reference.md:3215 src/api/reference.md:3252 +#: src/api/reference.md:3268 src/api/reference.md:3283 +#: src/api/reference.md:3295 src/api/reference.md:3334 +#: src/api/reference.md:3351 src/api/reference.md:3390 +#: src/api/reference.md:3441 src/api/reference.md:3488 +#: src/api/reference.md:3594 src/api/reference.md:3630 +#: src/api/reference.md:3649 src/api/reference.md:3689 +#: src/api/reference.md:3734 src/api/reference.md:3803 msgid "Properties" msgstr "Eigenschaften" -#: src/api/reference.md:132 src/api/reference.md:320 src/api/reference.md:432 -#: src/api/reference.md:526 src/api/reference.md:545 src/api/reference.md:825 -#: src/api/reference.md:957 src/api/reference.md:977 src/api/reference.md:1085 -#: src/api/reference.md:1164 src/api/reference.md:1249 -#: src/api/reference.md:1441 src/api/reference.md:1671 -#: src/api/reference.md:1757 src/api/reference.md:1777 -#: src/api/reference.md:1854 src/api/reference.md:2013 -#: src/api/reference.md:2052 src/api/reference.md:2075 -#: src/api/reference.md:2101 src/api/reference.md:2127 -#: src/api/reference.md:2158 src/api/reference.md:2814 -#: src/api/reference.md:2832 src/api/reference.md:2898 -#: src/api/reference.md:2912 src/api/reference.md:3102 -#: src/api/reference.md:3137 src/api/reference.md:3208 -#: src/api/reference.md:3261 src/api/reference.md:3423 -#: src/api/reference.md:3470 src/api/reference.md:3613 +#: src/api/reference.md:133 src/api/reference.md:321 src/api/reference.md:434 +#: src/api/reference.md:528 src/api/reference.md:547 src/api/reference.md:827 +#: src/api/reference.md:959 src/api/reference.md:979 src/api/reference.md:1087 +#: src/api/reference.md:1166 src/api/reference.md:1251 +#: src/api/reference.md:1443 src/api/reference.md:1673 +#: src/api/reference.md:1759 src/api/reference.md:1779 +#: src/api/reference.md:1856 src/api/reference.md:2015 +#: src/api/reference.md:2054 src/api/reference.md:2077 +#: src/api/reference.md:2103 src/api/reference.md:2129 +#: src/api/reference.md:2160 src/api/reference.md:2819 +#: src/api/reference.md:2837 src/api/reference.md:2907 +#: src/api/reference.md:2921 src/api/reference.md:3111 +#: src/api/reference.md:3146 src/api/reference.md:3217 +#: src/api/reference.md:3270 src/api/reference.md:3353 +#: src/api/reference.md:3443 src/api/reference.md:3490 +#: src/api/reference.md:3633 msgid "`default`" msgstr "`default`" -#: src/api/reference.md:134 +#: src/api/reference.md:135 msgid "`@parcel/watcher`" msgstr "`@parcel/watcher`" -#: src/api/reference.md:138 +#: src/api/reference.md:139 msgid "`__nativeEventCount` — module" msgstr "`__nativeEventCount` — Modul" -#: src/api/reference.md:139 +#: src/api/reference.md:140 msgid "`getEventsSince` — module" msgstr "`getEventsSince` — Modul" -#: src/api/reference.md:140 src/api/reference.md:148 src/api/reference.md:154 -#: src/api/reference.md:160 src/api/reference.md:166 src/api/reference.md:172 -#: src/api/reference.md:178 src/api/reference.md:184 src/api/reference.md:190 -#: src/api/reference.md:971 +#: src/api/reference.md:141 src/api/reference.md:149 src/api/reference.md:155 +#: src/api/reference.md:161 src/api/reference.md:167 src/api/reference.md:173 +#: src/api/reference.md:179 src/api/reference.md:185 src/api/reference.md:191 +#: src/api/reference.md:973 msgid "`subscribe` — module" msgstr "`subscribe` — Modul" -#: src/api/reference.md:141 src/api/reference.md:973 +#: src/api/reference.md:142 src/api/reference.md:975 msgid "`unsubscribe` — module" msgstr "`unsubscribe` — Modul" -#: src/api/reference.md:142 +#: src/api/reference.md:143 msgid "`writeSnapshot` — module" msgstr "`writeSnapshot` — Modul" -#: src/api/reference.md:144 +#: src/api/reference.md:145 msgid "`@parcel/watcher-darwin-arm64`" msgstr "`@parcel/watcher-darwin-arm64`" -#: src/api/reference.md:150 +#: src/api/reference.md:151 msgid "`@parcel/watcher-darwin-x64`" msgstr "`@parcel/watcher-darwin-x64`" -#: src/api/reference.md:156 +#: src/api/reference.md:157 msgid "`@parcel/watcher-linux-arm64-glibc`" msgstr "`@parcel/watcher-linux-arm64-glibc`" -#: src/api/reference.md:162 +#: src/api/reference.md:163 msgid "`@parcel/watcher-linux-arm64-musl`" msgstr "`@parcel/watcher-linux-arm64-musl`" -#: src/api/reference.md:168 +#: src/api/reference.md:169 msgid "`@parcel/watcher-linux-x64-glibc`" msgstr "`@parcel/watcher-linux-x64-glibc`" -#: src/api/reference.md:174 +#: src/api/reference.md:175 msgid "`@parcel/watcher-linux-x64-musl`" msgstr "`@parcel/watcher-linux-x64-musl`" -#: src/api/reference.md:180 +#: src/api/reference.md:181 msgid "`@parcel/watcher-win32-arm64`" msgstr "`@parcel/watcher-win32-arm64`" -#: src/api/reference.md:186 +#: src/api/reference.md:187 msgid "`@parcel/watcher-win32-x64`" msgstr "`@parcel/watcher-win32-x64`" -#: src/api/reference.md:196 +#: src/api/reference.md:197 msgid "`createPdf` — module" msgstr "`createPdf` — Modul" -#: src/api/reference.md:197 +#: src/api/reference.md:198 msgid "`pdfAddLine` — module" msgstr "`pdfAddLine` — Modul" -#: src/api/reference.md:198 +#: src/api/reference.md:199 msgid "`pdfAddText` — module" msgstr "`pdfAddText` — Modul" -#: src/api/reference.md:199 +#: src/api/reference.md:200 msgid "`pdfNewPage` — module" msgstr "`pdfNewPage` — Modul" -#: src/api/reference.md:200 +#: src/api/reference.md:201 msgid "`pdfSave` — module" msgstr "`pdfSave` — Modul" -#: src/api/reference.md:202 +#: src/api/reference.md:203 msgid "`__disposable__`" msgstr "`__disposable__`" -#: src/api/reference.md:206 +#: src/api/reference.md:207 msgid "`adopt` — instance" msgstr "`adopt` — Instanz" -#: src/api/reference.md:207 +#: src/api/reference.md:208 msgid "`defer` — instance" msgstr "`defer` — Instanz" -#: src/api/reference.md:208 +#: src/api/reference.md:209 msgid "`dispose` — instance" msgstr "`dispose` — Instanz" -#: src/api/reference.md:209 +#: src/api/reference.md:210 msgid "`disposeAsync` — instance" msgstr "`disposeAsync` — Instanz" -#: src/api/reference.md:210 +#: src/api/reference.md:211 msgid "`disposed` — instance" msgstr "`disposed` — Instanz" -#: src/api/reference.md:211 +#: src/api/reference.md:212 msgid "`move` — instance" msgstr "`move` — Instanz" -#: src/api/reference.md:212 +#: src/api/reference.md:213 msgid "`use` — instance" msgstr "`use` — Instanz" -#: src/api/reference.md:218 src/api/reference.md:327 src/api/reference.md:390 -#: src/api/reference.md:881 +#: src/api/reference.md:219 src/api/reference.md:328 src/api/reference.md:392 +#: src/api/reference.md:883 msgid "`hash` — module" msgstr "`hash` — Modul" -#: src/api/reference.md:219 src/api/reference.md:902 +#: src/api/reference.md:220 src/api/reference.md:904 msgid "`verify` — module" msgstr "`verify` — Modul" -#: src/api/reference.md:221 src/api/reference.md:3245 +#: src/api/reference.md:222 src/api/reference.md:3254 msgid "`assert`" msgstr "`assert`" -#: src/api/reference.md:225 src/api/reference.md:259 +#: src/api/reference.md:226 src/api/reference.md:260 msgid "`Assert`" msgstr "`Assert`" -#: src/api/reference.md:226 src/api/reference.md:260 +#: src/api/reference.md:227 src/api/reference.md:261 msgid "`AssertionError`" msgstr "`AssertionError`" -#: src/api/reference.md:230 src/api/reference.md:264 +#: src/api/reference.md:231 src/api/reference.md:265 msgid "`deepEqual` — module" msgstr "`deepEqual` — Modul" -#: src/api/reference.md:231 src/api/reference.md:265 +#: src/api/reference.md:232 src/api/reference.md:266 msgid "`deepStrictEqual` — module" msgstr "`deepStrictEqual` — Modul" -#: src/api/reference.md:232 src/api/reference.md:266 src/api/reference.md:336 -#: src/api/reference.md:1266 src/api/reference.md:2003 -#: src/api/reference.md:2922 src/api/reference.md:3026 -#: src/api/reference.md:3219 +#: src/api/reference.md:233 src/api/reference.md:267 src/api/reference.md:337 +#: src/api/reference.md:1268 src/api/reference.md:2005 +#: src/api/reference.md:2931 src/api/reference.md:3035 +#: src/api/reference.md:3228 msgid "`default` — module" msgstr "`default` — Modul" -#: src/api/reference.md:233 src/api/reference.md:267 +#: src/api/reference.md:234 src/api/reference.md:268 msgid "`doesNotMatch` — module" msgstr "`doesNotMatch` — Modul" -#: src/api/reference.md:234 src/api/reference.md:268 +#: src/api/reference.md:235 src/api/reference.md:269 msgid "`doesNotReject` — module" msgstr "`doesNotReject` — Modul" -#: src/api/reference.md:235 src/api/reference.md:269 +#: src/api/reference.md:236 src/api/reference.md:270 msgid "`doesNotThrow` — module" msgstr "`doesNotThrow` — Modul" -#: src/api/reference.md:236 src/api/reference.md:270 +#: src/api/reference.md:237 src/api/reference.md:271 msgid "`equal` — module" msgstr "`equal` — Modul" -#: src/api/reference.md:237 src/api/reference.md:271 +#: src/api/reference.md:238 src/api/reference.md:272 msgid "`fail` — module" msgstr "`fail` — Modul" -#: src/api/reference.md:238 src/api/reference.md:272 +#: src/api/reference.md:239 src/api/reference.md:273 msgid "`ifError` — module" msgstr "`ifError` — Modul" -#: src/api/reference.md:239 src/api/reference.md:273 +#: src/api/reference.md:240 src/api/reference.md:274 msgid "`match` — module" msgstr "`match` — Modul" -#: src/api/reference.md:240 src/api/reference.md:274 +#: src/api/reference.md:241 src/api/reference.md:275 msgid "`notDeepEqual` — module" msgstr "`notDeepEqual` — Modul" -#: src/api/reference.md:241 src/api/reference.md:275 +#: src/api/reference.md:242 src/api/reference.md:276 msgid "`notDeepStrictEqual` — module" msgstr "`notDeepStrictEqual` — Modul" -#: src/api/reference.md:242 src/api/reference.md:276 +#: src/api/reference.md:243 src/api/reference.md:277 msgid "`notEqual` — module" msgstr "`notEqual` — Modul" -#: src/api/reference.md:243 src/api/reference.md:277 +#: src/api/reference.md:244 src/api/reference.md:278 msgid "`notStrictEqual` — module" msgstr "`notStrictEqual` — Modul" -#: src/api/reference.md:244 src/api/reference.md:278 +#: src/api/reference.md:245 src/api/reference.md:279 msgid "`ok` — module" msgstr "`ok` — Modul" -#: src/api/reference.md:245 src/api/reference.md:279 +#: src/api/reference.md:246 src/api/reference.md:280 msgid "`partialDeepStrictEqual` — module" msgstr "`partialDeepStrictEqual` — Modul" -#: src/api/reference.md:246 src/api/reference.md:280 +#: src/api/reference.md:247 src/api/reference.md:281 msgid "`rejects` — module" msgstr "`rejects` — Modul" -#: src/api/reference.md:247 src/api/reference.md:281 +#: src/api/reference.md:248 src/api/reference.md:282 msgid "`strict` — module" msgstr "`strict` — Modul" -#: src/api/reference.md:248 src/api/reference.md:282 +#: src/api/reference.md:249 src/api/reference.md:283 msgid "`strictEqual` — module" msgstr "`strictEqual` — Modul" -#: src/api/reference.md:249 src/api/reference.md:283 +#: src/api/reference.md:250 src/api/reference.md:284 msgid "`throws` — module" msgstr "`throws` — Modul" -#: src/api/reference.md:253 src/api/reference.md:287 +#: src/api/reference.md:254 src/api/reference.md:288 msgid "`strict`" msgstr "`strict`" -#: src/api/reference.md:255 +#: src/api/reference.md:256 msgid "`assert/strict`" msgstr "`assert/strict`" -#: src/api/reference.md:289 +#: src/api/reference.md:290 msgid "`async_hooks`" msgstr "`async_hooks`" -#: src/api/reference.md:293 +#: src/api/reference.md:294 msgid "`AsyncLocalStorage`" msgstr "`AsyncLocalStorage`" -#: src/api/reference.md:294 +#: src/api/reference.md:295 msgid "`AsyncResource`" msgstr "`AsyncResource`" -#: src/api/reference.md:298 +#: src/api/reference.md:299 msgid "`asyncId` — instance _(class: `AsyncResource`)_" msgstr "`asyncId` — Instanz _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:299 +#: src/api/reference.md:300 msgid "`bind` — module _(class: `AsyncLocalStorage`)_" msgstr "`bind` — Modul _(Klasse: `AsyncLocalStorage`)_" -#: src/api/reference.md:300 +#: src/api/reference.md:301 msgid "`bind` — module _(class: `AsyncResource`)_" msgstr "`bind` — Modul _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:301 +#: src/api/reference.md:302 msgid "`bind` — instance _(class: `AsyncResource`)_" msgstr "`bind` — Instanz _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:302 +#: src/api/reference.md:303 msgid "`createHook` — module" msgstr "`createHook` — Modul" -#: src/api/reference.md:303 +#: src/api/reference.md:304 msgid "`disable` — instance" msgstr "`disable` — Instanz" -#: src/api/reference.md:304 +#: src/api/reference.md:305 msgid "`emitDestroy` — instance _(class: `AsyncResource`)_" msgstr "`emitDestroy` — Instanz _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:305 +#: src/api/reference.md:306 msgid "`enable` — instance _(class: `AsyncHook`)_" msgstr "`enable` — Instanz _(Klasse: `AsyncHook`)_" -#: src/api/reference.md:306 +#: src/api/reference.md:307 msgid "`enterWith` — instance" msgstr "`enterWith` — Instanz" -#: src/api/reference.md:307 +#: src/api/reference.md:308 msgid "`executionAsyncId` — module" msgstr "`executionAsyncId` — Modul" -#: src/api/reference.md:308 +#: src/api/reference.md:309 msgid "`executionAsyncResource` — module" msgstr "`executionAsyncResource` — Modul" -#: src/api/reference.md:309 src/api/reference.md:1182 +#: src/api/reference.md:310 src/api/reference.md:1184 msgid "`exit` — instance" msgstr "`exit` — Instanz" -#: src/api/reference.md:310 +#: src/api/reference.md:311 msgid "`getStore` — instance" msgstr "`getStore` — Instanz" -#: src/api/reference.md:311 src/api/reference.md:344 src/api/reference.md:1186 -#: src/api/reference.md:2990 +#: src/api/reference.md:312 src/api/reference.md:345 src/api/reference.md:1188 +#: src/api/reference.md:2999 msgid "`run` — instance" msgstr "`run` — Instanz" -#: src/api/reference.md:312 +#: src/api/reference.md:313 msgid "`runInAsyncScope` — instance _(class: `AsyncResource`)_" msgstr "`runInAsyncScope` — Instanz _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:313 +#: src/api/reference.md:314 msgid "`snapshot` — module _(class: `AsyncLocalStorage`)_" msgstr "`snapshot` — Modul _(Klasse: `AsyncLocalStorage`)_" -#: src/api/reference.md:314 +#: src/api/reference.md:315 msgid "`triggerAsyncId` — module" msgstr "`triggerAsyncId` — Modul" -#: src/api/reference.md:315 +#: src/api/reference.md:316 msgid "`triggerAsyncId` — instance _(class: `AsyncResource`)_" msgstr "`triggerAsyncId` — Instanz _(Klasse: `AsyncResource`)_" -#: src/api/reference.md:319 +#: src/api/reference.md:320 msgid "`asyncWrapProviders`" msgstr "`asyncWrapProviders`" -#: src/api/reference.md:326 +#: src/api/reference.md:327 msgid "`compare` — module" msgstr "`compare` — Modul" -#: src/api/reference.md:333 src/api/reference.md:2960 +#: src/api/reference.md:334 src/api/reference.md:2969 msgid "`all` — instance" msgstr "`all` — Instanz" -#: src/api/reference.md:334 src/api/reference.md:2840 src/api/reference.md:2867 -#: src/api/reference.md:2966 src/api/reference.md:3696 +#: src/api/reference.md:335 src/api/reference.md:2845 src/api/reference.md:2876 +#: src/api/reference.md:2975 src/api/reference.md:3716 msgid "`close` — instance" msgstr "`close` — Instanz" -#: src/api/reference.md:335 src/api/reference.md:2967 +#: src/api/reference.md:336 src/api/reference.md:2976 msgid "`columns` — instance" msgstr "`columns` — Instanz" -#: src/api/reference.md:337 src/api/reference.md:2974 +#: src/api/reference.md:338 src/api/reference.md:2983 msgid "`exec` — instance" msgstr "`exec` — Instanz" -#: src/api/reference.md:338 src/api/reference.md:2978 +#: src/api/reference.md:339 src/api/reference.md:2987 msgid "`get` — instance" msgstr "`get` — Instanz" -#: src/api/reference.md:339 src/api/reference.md:2982 +#: src/api/reference.md:340 src/api/reference.md:2991 msgid "`iterate` — instance" msgstr "`iterate` — Instanz" -#: src/api/reference.md:340 +#: src/api/reference.md:341 msgid "`pluck` — instance" msgstr "`pluck` — Instanz" -#: src/api/reference.md:341 +#: src/api/reference.md:342 msgid "`pragma` — instance" msgstr "`pragma` — Instanz" -#: src/api/reference.md:342 src/api/reference.md:2988 +#: src/api/reference.md:343 src/api/reference.md:2997 msgid "`prepare` — instance" msgstr "`prepare` — Instanz" -#: src/api/reference.md:343 +#: src/api/reference.md:344 msgid "`raw` — instance" msgstr "`raw` — Instanz" -#: src/api/reference.md:345 +#: src/api/reference.md:346 +#, fuzzy +msgid "`safeIntegers` — instance" +msgstr "`enter` — Instanz" + +#: src/api/reference.md:347 msgid "`transaction` — instance" msgstr "`transaction` — Instanz" -#: src/api/reference.md:347 +#: src/api/reference.md:349 msgid "`buffer`" msgstr "`buffer`" -#: src/api/reference.md:351 src/api/reference.md:1258 src/api/reference.md:1995 +#: src/api/reference.md:353 src/api/reference.md:1260 src/api/reference.md:1997 msgid "`Blob`" msgstr "`Blob`" -#: src/api/reference.md:352 +#: src/api/reference.md:354 msgid "`Buffer`" msgstr "`Buffer`" -#: src/api/reference.md:353 +#: src/api/reference.md:355 msgid "`File`" msgstr "`File`" -#: src/api/reference.md:357 +#: src/api/reference.md:359 msgid "`atob` — module" msgstr "`atob` — Modul" -#: src/api/reference.md:358 +#: src/api/reference.md:360 msgid "`btoa` — module" msgstr "`btoa` — Modul" -#: src/api/reference.md:359 +#: src/api/reference.md:361 msgid "`isAscii` — module" msgstr "`isAscii` — Modul" -#: src/api/reference.md:360 +#: src/api/reference.md:362 msgid "`isUtf8` — module" msgstr "`isUtf8` — Modul" -#: src/api/reference.md:361 +#: src/api/reference.md:363 msgid "`resolveObjectURL` — module" msgstr "`resolveObjectURL` — Modul" -#: src/api/reference.md:362 +#: src/api/reference.md:364 msgid "`transcode` — module" msgstr "`transcode` — Modul" -#: src/api/reference.md:366 +#: src/api/reference.md:368 msgid "`INSPECT_MAX_BYTES`" msgstr "`INSPECT_MAX_BYTES`" -#: src/api/reference.md:367 src/api/reference.md:586 src/api/reference.md:907 -#: src/api/reference.md:1395 src/api/reference.md:1440 -#: src/api/reference.md:1670 src/api/reference.md:1853 -#: src/api/reference.md:2051 src/api/reference.md:2157 -#: src/api/reference.md:3002 src/api/reference.md:3612 -#: src/api/reference.md:3786 +#: src/api/reference.md:369 src/api/reference.md:588 src/api/reference.md:909 +#: src/api/reference.md:1397 src/api/reference.md:1442 +#: src/api/reference.md:1672 src/api/reference.md:1855 +#: src/api/reference.md:2053 src/api/reference.md:2159 +#: src/api/reference.md:3011 src/api/reference.md:3632 +#: src/api/reference.md:3806 msgid "`constants`" msgstr "`constants`" -#: src/api/reference.md:368 +#: src/api/reference.md:370 msgid "`kMaxLength`" msgstr "`kMaxLength`" -#: src/api/reference.md:369 +#: src/api/reference.md:371 msgid "`kStringMaxLength`" msgstr "`kStringMaxLength`" -#: src/api/reference.md:371 +#: src/api/reference.md:373 msgid "`bun`" msgstr "`bun`" -#: src/api/reference.md:375 +#: src/api/reference.md:377 #, fuzzy msgid "`Transpiler`" msgstr "Transfer" -#: src/api/reference.md:379 +#: src/api/reference.md:381 msgid "`Glob` — module" msgstr "`Glob` — Modul" -#: src/api/reference.md:380 +#: src/api/reference.md:382 #, fuzzy msgid "`SQL` — module" msgstr "`t` — Modul" -#: src/api/reference.md:381 +#: src/api/reference.md:383 #, fuzzy msgid "`Terminal` — module" msgstr "`onTerminate` — Modul" -#: src/api/reference.md:382 +#: src/api/reference.md:384 #, fuzzy msgid "`Transpiler` — module" msgstr "`range` — Modul" -#: src/api/reference.md:383 +#: src/api/reference.md:385 #, fuzzy msgid "`build` — module" msgstr "`bind` — Modul" -#: src/api/reference.md:384 src/api/reference.md:1660 src/api/reference.md:1894 -#: src/api/reference.md:3305 +#: src/api/reference.md:386 src/api/reference.md:1662 src/api/reference.md:1896 +#: src/api/reference.md:3314 msgid "`connect` — module" msgstr "`connect` — Modul" -#: src/api/reference.md:385 +#: src/api/reference.md:387 #, fuzzy msgid "`deepEquals` — module" msgstr "`deepEqual` — Modul" -#: src/api/reference.md:386 +#: src/api/reference.md:388 msgid "`file` — module" msgstr "`file` — Modul" -#: src/api/reference.md:387 src/api/reference.md:3411 +#: src/api/reference.md:389 src/api/reference.md:3431 msgid "`fileURLToPath` — module" msgstr "`fileURLToPath` — Modul" -#: src/api/reference.md:388 +#: src/api/reference.md:390 #, fuzzy msgid "`gc` — module" msgstr "`cp` — Modul" -#: src/api/reference.md:389 +#: src/api/reference.md:391 #, fuzzy msgid "`generateHeapSnapshot` — module" msgstr "`getHeapSnapshot` — Modul" -#: src/api/reference.md:391 +#: src/api/reference.md:393 #, fuzzy msgid "`listen` — module" msgstr "`list` — Modul" -#: src/api/reference.md:392 src/api/reference.md:3415 +#: src/api/reference.md:394 src/api/reference.md:3435 msgid "`pathToFileURL` — module" msgstr "`pathToFileURL` — Modul" -#: src/api/reference.md:393 +#: src/api/reference.md:395 msgid "" "`plugin` — module ⚠ **stub** — setup runs synchronously; runtime loader " "hooks and clearAll are inert (#10100)" msgstr "" -#: src/api/reference.md:394 +#: src/api/reference.md:396 #, fuzzy msgid "`scan` — instance _(class: `Transpiler`)_" msgstr "`cancel` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:395 +#: src/api/reference.md:397 #, fuzzy msgid "`scanImports` — instance _(class: `Transpiler`)_" msgstr "`start` — Instanz _(Klasse: `GCProfiler`)_" -#: src/api/reference.md:396 +#: src/api/reference.md:398 #, fuzzy msgid "`serve` — module" msgstr "`Server` — Modul" -#: src/api/reference.md:397 +#: src/api/reference.md:399 #, fuzzy msgid "`sleep` — module" msgstr "`seek` — Modul" -#: src/api/reference.md:398 +#: src/api/reference.md:400 #, fuzzy msgid "`sleepSync` — module" msgstr "`execSync` — Modul" -#: src/api/reference.md:400 +#: src/api/reference.md:402 msgid "`stringWidth` — module" msgstr "`stringWidth` — Modul" -#: src/api/reference.md:401 +#: src/api/reference.md:403 #, fuzzy msgid "`stripANSI` — module" msgstr "`strict` — Modul" -#: src/api/reference.md:402 +#: src/api/reference.md:404 #, fuzzy msgid "`transform` — instance _(class: `Transpiler`)_" msgstr "`start` — Instanz _(Klasse: `GCProfiler`)_" -#: src/api/reference.md:403 +#: src/api/reference.md:405 #, fuzzy msgid "`transformSync` — instance _(class: `Transpiler`)_" msgstr "`transaction` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:404 +#: src/api/reference.md:406 msgid "`unsupported` — module" msgstr "`unsupported` — Modul" -#: src/api/reference.md:405 +#: src/api/reference.md:407 #, fuzzy msgid "`which` — module" msgstr "`watch` — Modul" -#: src/api/reference.md:406 +#: src/api/reference.md:408 #, fuzzy msgid "`wrapAnsi` — module" msgstr "`warn` — Modul" -#: src/api/reference.md:407 src/api/reference.md:1386 +#: src/api/reference.md:409 src/api/reference.md:1388 msgid "`write` — module" msgstr "`write` — Modul" -#: src/api/reference.md:408 src/api/reference.md:3780 +#: src/api/reference.md:410 src/api/reference.md:3800 msgid "`zstdDecompress` — module" msgstr "`zstdDecompress` — Modul" -#: src/api/reference.md:409 src/api/reference.md:3781 +#: src/api/reference.md:411 src/api/reference.md:3801 msgid "`zstdDecompressSync` — module" msgstr "`zstdDecompressSync` — Modul" -#: src/api/reference.md:413 +#: src/api/reference.md:415 #, fuzzy msgid "`JSONL`" msgstr "JSON" -#: src/api/reference.md:414 +#: src/api/reference.md:416 msgid "`TOML`" msgstr "" -#: src/api/reference.md:415 +#: src/api/reference.md:417 msgid "`YAML`" msgstr "" -#: src/api/reference.md:416 +#: src/api/reference.md:418 msgid "`ant`" msgstr "" -#: src/api/reference.md:417 src/api/reference.md:2170 +#: src/api/reference.md:419 src/api/reference.md:2172 msgid "`isStandaloneExecutable`" msgstr "`isStandaloneExecutable`" -#: src/api/reference.md:418 +#: src/api/reference.md:420 #, fuzzy msgid "`semver`" msgstr "`server`" -#: src/api/reference.md:419 src/api/reference.md:2796 +#: src/api/reference.md:421 src/api/reference.md:2801 msgid "`stderr`" msgstr "`stderr`" -#: src/api/reference.md:420 src/api/reference.md:2797 +#: src/api/reference.md:422 src/api/reference.md:2802 msgid "`stdin`" msgstr "`stdin`" -#: src/api/reference.md:421 src/api/reference.md:2798 +#: src/api/reference.md:423 src/api/reference.md:2803 msgid "`stdout`" msgstr "`stdout`" -#: src/api/reference.md:422 src/api/reference.md:2800 src/api/reference.md:2816 +#: src/api/reference.md:424 src/api/reference.md:2805 src/api/reference.md:2821 #: src/updater/overview.md:109 src/cli/perry-toml.md:120 msgid "`version`" msgstr "`version`" -#: src/api/reference.md:424 +#: src/api/reference.md:426 #, fuzzy msgid "`bun-pty`" msgstr "`node-pty`" -#: src/api/reference.md:434 +#: src/api/reference.md:436 msgid "`bun:ffi`" msgstr "`bun:ffi`" -#: src/api/reference.md:438 +#: src/api/reference.md:440 msgid "`CFunction` — module" msgstr "`CFunction` — Modul" -#: src/api/reference.md:439 +#: src/api/reference.md:441 msgid "`CString` — module" msgstr "`CString` — Modul" -#: src/api/reference.md:440 +#: src/api/reference.md:442 msgid "`JSCallback` — module" msgstr "`JSCallback` — Modul" -#: src/api/reference.md:441 src/api/reference.md:1272 src/api/reference.md:2732 +#: src/api/reference.md:443 src/api/reference.md:1274 src/api/reference.md:2737 msgid "`dlopen` — module" msgstr "`dlopen` — Modul" -#: src/api/reference.md:442 +#: src/api/reference.md:444 msgid "`linkSymbols` — module" msgstr "`linkSymbols` — Modul" -#: src/api/reference.md:443 +#: src/api/reference.md:445 msgid "`ptr` — module" msgstr "`ptr` — Modul" -#: src/api/reference.md:444 src/api/reference.md:1274 +#: src/api/reference.md:446 src/api/reference.md:1276 msgid "`toArrayBuffer` — module" msgstr "`toArrayBuffer` — Modul" -#: src/api/reference.md:445 src/api/reference.md:1275 +#: src/api/reference.md:447 src/api/reference.md:1277 msgid "`toBuffer` — module" msgstr "`toBuffer` — Modul" -#: src/api/reference.md:446 +#: src/api/reference.md:448 msgid "`viewSource` — module" msgstr "`viewSource` — Modul" -#: src/api/reference.md:450 +#: src/api/reference.md:452 msgid "`FFIType`" msgstr "`FFIType`" -#: src/api/reference.md:451 +#: src/api/reference.md:453 msgid "`read`" msgstr "`read`" -#: src/api/reference.md:452 src/api/reference.md:1280 +#: src/api/reference.md:454 src/api/reference.md:1282 msgid "`suffix`" msgstr "`suffix`" -#: src/api/reference.md:454 +#: src/api/reference.md:456 #, fuzzy msgid "`bun:jsc`" msgstr "`bun`" -#: src/api/reference.md:458 +#: src/api/reference.md:460 #, fuzzy msgid "`heapStats` — module" msgstr "`State` — Modul" -#: src/api/reference.md:460 +#: src/api/reference.md:462 msgid "`bun:sqlite`" msgstr "`bun:sqlite`" -#: src/api/reference.md:464 +#: src/api/reference.md:466 msgid "`Database`" msgstr "`Database`" -#: src/api/reference.md:465 +#: src/api/reference.md:467 msgid "`Statement`" msgstr "`Statement`" -#: src/api/reference.md:469 +#: src/api/reference.md:471 msgid "`Database` — module" msgstr "`Database` — Modul" -#: src/api/reference.md:470 +#: src/api/reference.md:472 msgid "`all` — instance _(class: `Statement`)_" msgstr "`all` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:471 +#: src/api/reference.md:473 msgid "`close` — instance _(class: `Database`)_" msgstr "`close` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:472 +#: src/api/reference.md:474 msgid "`finalize` — instance _(class: `Statement`)_" msgstr "`finalize` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:473 +#: src/api/reference.md:475 msgid "`get` — instance _(class: `Statement`)_" msgstr "`get` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:474 +#: src/api/reference.md:476 msgid "`loadExtension` — instance _(class: `Database`)_" msgstr "`loadExtension` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:475 +#: src/api/reference.md:477 msgid "`prepare` — instance _(class: `Database`)_" msgstr "`prepare` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:476 +#: src/api/reference.md:478 msgid "`query` — instance _(class: `Database`)_" msgstr "`query` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:477 +#: src/api/reference.md:479 msgid "`run` — instance _(class: `Database`)_" msgstr "`run` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:478 +#: src/api/reference.md:480 msgid "`run` — instance _(class: `Statement`)_" msgstr "`run` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:479 +#: src/api/reference.md:481 msgid "`safeIntegers` — instance _(class: `Statement`)_" msgstr "`safeIntegers` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:480 +#: src/api/reference.md:482 msgid "`serialize` — instance _(class: `Database`)_" msgstr "`serialize` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:481 +#: src/api/reference.md:483 msgid "`transaction` — instance _(class: `Database`)_" msgstr "`transaction` — Instanz _(Klasse: `Database`)_" -#: src/api/reference.md:482 +#: src/api/reference.md:484 msgid "`values` — instance _(class: `Statement`)_" msgstr "`values` — Instanz _(Klasse: `Statement`)_" -#: src/api/reference.md:486 +#: src/api/reference.md:488 msgid "`filename`" msgstr "`filename`" -#: src/api/reference.md:487 +#: src/api/reference.md:489 msgid "`inTransaction`" msgstr "`inTransaction`" -#: src/api/reference.md:493 +#: src/api/reference.md:495 msgid "`attr` — instance" msgstr "`attr` — Instanz" -#: src/api/reference.md:494 +#: src/api/reference.md:496 msgid "`children` — instance" msgstr "`children` — Instanz" -#: src/api/reference.md:495 +#: src/api/reference.md:497 msgid "`eq` — instance" msgstr "`eq` — Instanz" -#: src/api/reference.md:496 +#: src/api/reference.md:498 msgid "`find` — instance" msgstr "`find` — Instanz" -#: src/api/reference.md:497 +#: src/api/reference.md:499 msgid "`first` — instance" msgstr "`first` — Instanz" -#: src/api/reference.md:498 +#: src/api/reference.md:500 msgid "`hasClass` — instance" msgstr "`hasClass` — Instanz" -#: src/api/reference.md:499 +#: src/api/reference.md:501 msgid "`html` — instance" msgstr "`html` — Instanz" -#: src/api/reference.md:500 +#: src/api/reference.md:502 msgid "`last` — instance" msgstr "`last` — Instanz" -#: src/api/reference.md:501 +#: src/api/reference.md:503 msgid "`length` — instance" msgstr "`length` — Instanz" -#: src/api/reference.md:502 +#: src/api/reference.md:504 msgid "`load` — module" msgstr "`load` — Modul" -#: src/api/reference.md:503 +#: src/api/reference.md:505 msgid "`parent` — instance" msgstr "`parent` — Instanz" -#: src/api/reference.md:504 +#: src/api/reference.md:506 msgid "`select` — instance" msgstr "`select` — Instanz" -#: src/api/reference.md:505 +#: src/api/reference.md:507 msgid "`text` — instance" msgstr "`text` — Instanz" -#: src/api/reference.md:507 +#: src/api/reference.md:509 msgid "`child_process`" msgstr "`child_process`" -#: src/api/reference.md:511 +#: src/api/reference.md:513 msgid "`ChildProcess`" msgstr "`ChildProcess`" -#: src/api/reference.md:515 +#: src/api/reference.md:517 msgid "`_forkChild` — module" msgstr "`_forkChild` — Modul" -#: src/api/reference.md:516 src/api/reference.md:2242 src/api/reference.md:2259 -#: src/api/reference.md:2284 +#: src/api/reference.md:518 src/api/reference.md:2247 src/api/reference.md:2264 +#: src/api/reference.md:2289 msgid "`exec` — module" msgstr "`exec` — Modul" -#: src/api/reference.md:517 +#: src/api/reference.md:519 msgid "`execFile` — module" msgstr "`execFile` — Modul" -#: src/api/reference.md:518 +#: src/api/reference.md:520 msgid "`execFileSync` — module" msgstr "`execFileSync` — Modul" -#: src/api/reference.md:519 +#: src/api/reference.md:521 msgid "`execSync` — module" msgstr "`execSync` — Modul" -#: src/api/reference.md:520 src/api/reference.md:537 +#: src/api/reference.md:522 src/api/reference.md:539 msgid "`fork` — module" msgstr "`fork` — Modul" -#: src/api/reference.md:522 +#: src/api/reference.md:524 msgid "`spawnSync` — module" msgstr "`spawnSync` — Modul" -#: src/api/reference.md:528 +#: src/api/reference.md:530 msgid "`cluster`" msgstr "`cluster`" -#: src/api/reference.md:536 +#: src/api/reference.md:534 src/api/reference.md:3660 +msgid "`Worker`" +msgstr "`Worker`" + +#: src/api/reference.md:538 msgid "`disconnect` — module" msgstr "`disconnect` — Modul" -#: src/api/reference.md:538 +#: src/api/reference.md:540 msgid "`setupMaster` — module" msgstr "`setupMaster` — Modul" -#: src/api/reference.md:539 +#: src/api/reference.md:541 msgid "`setupPrimary` — module" msgstr "`setupPrimary` — Modul" -#: src/api/reference.md:543 +#: src/api/reference.md:545 msgid "`SCHED_NONE`" msgstr "`SCHED_NONE`" -#: src/api/reference.md:544 +#: src/api/reference.md:546 msgid "`SCHED_RR`" msgstr "`SCHED_RR`" -#: src/api/reference.md:546 +#: src/api/reference.md:548 msgid "`isMaster`" msgstr "`isMaster`" -#: src/api/reference.md:547 +#: src/api/reference.md:549 msgid "`isPrimary`" msgstr "`isPrimary`" -#: src/api/reference.md:548 +#: src/api/reference.md:550 msgid "`isWorker`" msgstr "`isWorker`" -#: src/api/reference.md:549 +#: src/api/reference.md:551 msgid "`schedulingPolicy`" msgstr "`schedulingPolicy`" -#: src/api/reference.md:550 +#: src/api/reference.md:552 msgid "`settings`" msgstr "`settings`" -#: src/api/reference.md:551 +#: src/api/reference.md:553 msgid "`workers`" msgstr "`workers`" -#: src/api/reference.md:553 src/api/reference.md:1756 +#: src/api/reference.md:555 src/api/reference.md:1758 msgid "`console`" msgstr "`console`" -#: src/api/reference.md:557 +#: src/api/reference.md:559 msgid "`Console`" msgstr "`Console`" -#: src/api/reference.md:561 +#: src/api/reference.md:563 msgid "`assert` — module" msgstr "`assert` — Modul" -#: src/api/reference.md:562 +#: src/api/reference.md:564 msgid "`clear` — module" msgstr "`clear` — Modul" -#: src/api/reference.md:563 +#: src/api/reference.md:565 msgid "`context` — module" msgstr "`context` — Modul" -#: src/api/reference.md:564 +#: src/api/reference.md:566 msgid "`count` — module" msgstr "`count` — Modul" -#: src/api/reference.md:565 +#: src/api/reference.md:567 msgid "`countReset` — module" msgstr "`countReset` — Modul" -#: src/api/reference.md:566 +#: src/api/reference.md:568 msgid "`createTask` — module" msgstr "`createTask` — Modul" -#: src/api/reference.md:567 src/api/reference.md:3182 src/api/reference.md:3444 +#: src/api/reference.md:569 src/api/reference.md:3191 src/api/reference.md:3464 msgid "`debug` — module" msgstr "`debug` — Modul" -#: src/api/reference.md:568 +#: src/api/reference.md:570 msgid "`dir` — module" msgstr "`dir` — Modul" -#: src/api/reference.md:569 +#: src/api/reference.md:571 msgid "`dirxml` — module" msgstr "`dirxml` — Modul" -#: src/api/reference.md:570 +#: src/api/reference.md:572 msgid "`error` — module" msgstr "`error` — Modul" -#: src/api/reference.md:571 +#: src/api/reference.md:573 msgid "`group` — module" msgstr "`group` — Modul" -#: src/api/reference.md:572 +#: src/api/reference.md:574 msgid "`groupCollapsed` — module" msgstr "`groupCollapsed` — Modul" -#: src/api/reference.md:573 +#: src/api/reference.md:575 msgid "`groupEnd` — module" msgstr "`groupEnd` — Modul" -#: src/api/reference.md:574 +#: src/api/reference.md:576 msgid "`info` — module" msgstr "`info` — Modul" -#: src/api/reference.md:575 +#: src/api/reference.md:577 msgid "`log` — module" msgstr "`log` — Modul" -#: src/api/reference.md:576 +#: src/api/reference.md:578 msgid "`profile` — module" msgstr "`profile` — Modul" -#: src/api/reference.md:577 +#: src/api/reference.md:579 msgid "`profileEnd` — module" msgstr "`profileEnd` — Modul" -#: src/api/reference.md:578 +#: src/api/reference.md:580 msgid "`table` — module" msgstr "`table` — Modul" -#: src/api/reference.md:579 +#: src/api/reference.md:581 msgid "`time` — module" msgstr "`time` — Modul" -#: src/api/reference.md:580 +#: src/api/reference.md:582 msgid "`timeEnd` — module" msgstr "`timeEnd` — Modul" -#: src/api/reference.md:581 +#: src/api/reference.md:583 msgid "`timeLog` — module" msgstr "`timeLog` — Modul" -#: src/api/reference.md:582 +#: src/api/reference.md:584 msgid "`timeStamp` — module" msgstr "`timeStamp` — Modul" -#: src/api/reference.md:583 +#: src/api/reference.md:585 msgid "`trace` — module" msgstr "`trace` — Modul" -#: src/api/reference.md:584 +#: src/api/reference.md:586 msgid "`warn` — module" msgstr "`warn` — Modul" -#: src/api/reference.md:590 +#: src/api/reference.md:592 msgid "`COPYFILE_EXCL`" msgstr "`COPYFILE_EXCL`" -#: src/api/reference.md:591 +#: src/api/reference.md:593 msgid "`COPYFILE_FICLONE`" msgstr "`COPYFILE_FICLONE`" -#: src/api/reference.md:592 +#: src/api/reference.md:594 msgid "`COPYFILE_FICLONE_FORCE`" msgstr "`COPYFILE_FICLONE_FORCE`" -#: src/api/reference.md:593 +#: src/api/reference.md:595 msgid "`DH_CHECK_P_NOT_PRIME`" msgstr "`DH_CHECK_P_NOT_PRIME`" -#: src/api/reference.md:594 +#: src/api/reference.md:596 msgid "`DH_CHECK_P_NOT_SAFE_PRIME`" msgstr "`DH_CHECK_P_NOT_SAFE_PRIME`" -#: src/api/reference.md:595 +#: src/api/reference.md:597 msgid "`DH_NOT_SUITABLE_GENERATOR`" msgstr "`DH_NOT_SUITABLE_GENERATOR`" -#: src/api/reference.md:596 +#: src/api/reference.md:598 msgid "`DH_UNABLE_TO_CHECK_GENERATOR`" msgstr "`DH_UNABLE_TO_CHECK_GENERATOR`" -#: src/api/reference.md:597 +#: src/api/reference.md:599 msgid "`E2BIG`" msgstr "`E2BIG`" -#: src/api/reference.md:598 +#: src/api/reference.md:600 msgid "`EACCES`" msgstr "`EACCES`" -#: src/api/reference.md:599 +#: src/api/reference.md:601 msgid "`EADDRINUSE`" msgstr "`EADDRINUSE`" -#: src/api/reference.md:600 +#: src/api/reference.md:602 msgid "`EADDRNOTAVAIL`" msgstr "`EADDRNOTAVAIL`" -#: src/api/reference.md:601 +#: src/api/reference.md:603 msgid "`EAFNOSUPPORT`" msgstr "`EAFNOSUPPORT`" -#: src/api/reference.md:602 +#: src/api/reference.md:604 msgid "`EAGAIN`" msgstr "`EAGAIN`" -#: src/api/reference.md:603 +#: src/api/reference.md:605 msgid "`EALREADY`" msgstr "`EALREADY`" -#: src/api/reference.md:604 +#: src/api/reference.md:606 msgid "`EBADF`" msgstr "`EBADF`" -#: src/api/reference.md:605 +#: src/api/reference.md:607 msgid "`EBADMSG`" msgstr "`EBADMSG`" -#: src/api/reference.md:606 +#: src/api/reference.md:608 msgid "`EBUSY`" msgstr "`EBUSY`" -#: src/api/reference.md:607 +#: src/api/reference.md:609 msgid "`ECANCELED`" msgstr "`ECANCELED`" -#: src/api/reference.md:608 +#: src/api/reference.md:610 msgid "`ECHILD`" msgstr "`ECHILD`" -#: src/api/reference.md:609 +#: src/api/reference.md:611 msgid "`ECONNABORTED`" msgstr "`ECONNABORTED`" -#: src/api/reference.md:610 +#: src/api/reference.md:612 msgid "`ECONNREFUSED`" msgstr "`ECONNREFUSED`" -#: src/api/reference.md:611 +#: src/api/reference.md:613 msgid "`ECONNRESET`" msgstr "`ECONNRESET`" -#: src/api/reference.md:612 +#: src/api/reference.md:614 msgid "`EDEADLK`" msgstr "`EDEADLK`" -#: src/api/reference.md:613 +#: src/api/reference.md:615 msgid "`EDESTADDRREQ`" msgstr "`EDESTADDRREQ`" -#: src/api/reference.md:614 +#: src/api/reference.md:616 msgid "`EDOM`" msgstr "`EDOM`" -#: src/api/reference.md:615 +#: src/api/reference.md:617 msgid "`EDQUOT`" msgstr "`EDQUOT`" -#: src/api/reference.md:616 +#: src/api/reference.md:618 msgid "`EEXIST`" msgstr "`EEXIST`" -#: src/api/reference.md:617 +#: src/api/reference.md:619 msgid "`EFAULT`" msgstr "`EFAULT`" -#: src/api/reference.md:618 +#: src/api/reference.md:620 msgid "`EFBIG`" msgstr "`EFBIG`" -#: src/api/reference.md:619 +#: src/api/reference.md:621 msgid "`EHOSTUNREACH`" msgstr "`EHOSTUNREACH`" -#: src/api/reference.md:620 +#: src/api/reference.md:622 msgid "`EIDRM`" msgstr "`EIDRM`" -#: src/api/reference.md:621 +#: src/api/reference.md:623 msgid "`EILSEQ`" msgstr "`EILSEQ`" -#: src/api/reference.md:622 +#: src/api/reference.md:624 msgid "`EINPROGRESS`" msgstr "`EINPROGRESS`" -#: src/api/reference.md:623 +#: src/api/reference.md:625 msgid "`EINTR`" msgstr "`EINTR`" -#: src/api/reference.md:624 +#: src/api/reference.md:626 msgid "`EINVAL`" msgstr "`EINVAL`" -#: src/api/reference.md:625 +#: src/api/reference.md:627 msgid "`EIO`" msgstr "`EIO`" -#: src/api/reference.md:626 +#: src/api/reference.md:628 msgid "`EISCONN`" msgstr "`EISCONN`" -#: src/api/reference.md:627 +#: src/api/reference.md:629 msgid "`EISDIR`" msgstr "`EISDIR`" -#: src/api/reference.md:628 +#: src/api/reference.md:630 msgid "`ELOOP`" msgstr "`ELOOP`" -#: src/api/reference.md:629 +#: src/api/reference.md:631 msgid "`EMFILE`" msgstr "`EMFILE`" -#: src/api/reference.md:630 +#: src/api/reference.md:632 msgid "`EMLINK`" msgstr "`EMLINK`" -#: src/api/reference.md:631 +#: src/api/reference.md:633 msgid "`EMSGSIZE`" msgstr "`EMSGSIZE`" -#: src/api/reference.md:632 +#: src/api/reference.md:634 msgid "`EMULTIHOP`" msgstr "`EMULTIHOP`" -#: src/api/reference.md:633 +#: src/api/reference.md:635 msgid "`ENAMETOOLONG`" msgstr "`ENAMETOOLONG`" -#: src/api/reference.md:634 +#: src/api/reference.md:636 msgid "`ENETDOWN`" msgstr "`ENETDOWN`" -#: src/api/reference.md:635 +#: src/api/reference.md:637 msgid "`ENETRESET`" msgstr "`ENETRESET`" -#: src/api/reference.md:636 +#: src/api/reference.md:638 msgid "`ENETUNREACH`" msgstr "`ENETUNREACH`" -#: src/api/reference.md:637 +#: src/api/reference.md:639 msgid "`ENFILE`" msgstr "`ENFILE`" -#: src/api/reference.md:638 +#: src/api/reference.md:640 msgid "`ENGINE_METHOD_ALL`" msgstr "`ENGINE_METHOD_ALL`" -#: src/api/reference.md:639 +#: src/api/reference.md:641 msgid "`ENGINE_METHOD_CIPHERS`" msgstr "`ENGINE_METHOD_CIPHERS`" -#: src/api/reference.md:640 +#: src/api/reference.md:642 msgid "`ENGINE_METHOD_DH`" msgstr "`ENGINE_METHOD_DH`" -#: src/api/reference.md:641 +#: src/api/reference.md:643 msgid "`ENGINE_METHOD_DIGESTS`" msgstr "`ENGINE_METHOD_DIGESTS`" -#: src/api/reference.md:642 +#: src/api/reference.md:644 msgid "`ENGINE_METHOD_DSA`" msgstr "`ENGINE_METHOD_DSA`" -#: src/api/reference.md:643 +#: src/api/reference.md:645 msgid "`ENGINE_METHOD_EC`" msgstr "`ENGINE_METHOD_EC`" -#: src/api/reference.md:644 +#: src/api/reference.md:646 msgid "`ENGINE_METHOD_NONE`" msgstr "`ENGINE_METHOD_NONE`" -#: src/api/reference.md:645 +#: src/api/reference.md:647 msgid "`ENGINE_METHOD_PKEY_ASN1_METHS`" msgstr "`ENGINE_METHOD_PKEY_ASN1_METHS`" -#: src/api/reference.md:646 +#: src/api/reference.md:648 msgid "`ENGINE_METHOD_PKEY_METHS`" msgstr "`ENGINE_METHOD_PKEY_METHS`" -#: src/api/reference.md:647 +#: src/api/reference.md:649 msgid "`ENGINE_METHOD_RAND`" msgstr "`ENGINE_METHOD_RAND`" -#: src/api/reference.md:648 +#: src/api/reference.md:650 msgid "`ENGINE_METHOD_RSA`" msgstr "`ENGINE_METHOD_RSA`" -#: src/api/reference.md:649 +#: src/api/reference.md:651 msgid "`ENOBUFS`" msgstr "`ENOBUFS`" -#: src/api/reference.md:650 +#: src/api/reference.md:652 msgid "`ENODATA`" msgstr "`ENODATA`" -#: src/api/reference.md:651 +#: src/api/reference.md:653 msgid "`ENODEV`" msgstr "`ENODEV`" -#: src/api/reference.md:652 +#: src/api/reference.md:654 msgid "`ENOENT`" msgstr "`ENOENT`" -#: src/api/reference.md:653 +#: src/api/reference.md:655 msgid "`ENOEXEC`" msgstr "`ENOEXEC`" -#: src/api/reference.md:654 +#: src/api/reference.md:656 msgid "`ENOLCK`" msgstr "`ENOLCK`" -#: src/api/reference.md:655 +#: src/api/reference.md:657 msgid "`ENOLINK`" msgstr "`ENOLINK`" -#: src/api/reference.md:656 +#: src/api/reference.md:658 msgid "`ENOMEM`" msgstr "`ENOMEM`" -#: src/api/reference.md:657 +#: src/api/reference.md:659 msgid "`ENOMSG`" msgstr "`ENOMSG`" -#: src/api/reference.md:658 +#: src/api/reference.md:660 msgid "`ENOPROTOOPT`" msgstr "`ENOPROTOOPT`" -#: src/api/reference.md:659 +#: src/api/reference.md:661 msgid "`ENOSPC`" msgstr "`ENOSPC`" -#: src/api/reference.md:660 +#: src/api/reference.md:662 msgid "`ENOSR`" msgstr "`ENOSR`" -#: src/api/reference.md:661 +#: src/api/reference.md:663 msgid "`ENOSTR`" msgstr "`ENOSTR`" -#: src/api/reference.md:662 +#: src/api/reference.md:664 msgid "`ENOSYS`" msgstr "`ENOSYS`" -#: src/api/reference.md:663 +#: src/api/reference.md:665 msgid "`ENOTCONN`" msgstr "`ENOTCONN`" -#: src/api/reference.md:664 +#: src/api/reference.md:666 msgid "`ENOTDIR`" msgstr "`ENOTDIR`" -#: src/api/reference.md:665 +#: src/api/reference.md:667 msgid "`ENOTEMPTY`" msgstr "`ENOTEMPTY`" -#: src/api/reference.md:666 +#: src/api/reference.md:668 msgid "`ENOTSOCK`" msgstr "`ENOTSOCK`" -#: src/api/reference.md:667 +#: src/api/reference.md:669 msgid "`ENOTSUP`" msgstr "`ENOTSUP`" -#: src/api/reference.md:668 +#: src/api/reference.md:670 msgid "`ENOTTY`" msgstr "`ENOTTY`" -#: src/api/reference.md:669 +#: src/api/reference.md:671 msgid "`ENXIO`" msgstr "`ENXIO`" -#: src/api/reference.md:670 +#: src/api/reference.md:672 msgid "`EOPNOTSUPP`" msgstr "`EOPNOTSUPP`" -#: src/api/reference.md:671 +#: src/api/reference.md:673 msgid "`EOVERFLOW`" msgstr "`EOVERFLOW`" -#: src/api/reference.md:672 +#: src/api/reference.md:674 msgid "`EPERM`" msgstr "`EPERM`" -#: src/api/reference.md:673 +#: src/api/reference.md:675 msgid "`EPIPE`" msgstr "`EPIPE`" -#: src/api/reference.md:674 +#: src/api/reference.md:676 msgid "`EPROTO`" msgstr "`EPROTO`" -#: src/api/reference.md:675 +#: src/api/reference.md:677 msgid "`EPROTONOSUPPORT`" msgstr "`EPROTONOSUPPORT`" -#: src/api/reference.md:676 +#: src/api/reference.md:678 msgid "`EPROTOTYPE`" msgstr "`EPROTOTYPE`" -#: src/api/reference.md:677 +#: src/api/reference.md:679 msgid "`ERANGE`" msgstr "`ERANGE`" -#: src/api/reference.md:678 +#: src/api/reference.md:680 msgid "`EROFS`" msgstr "`EROFS`" -#: src/api/reference.md:679 +#: src/api/reference.md:681 msgid "`ESPIPE`" msgstr "`ESPIPE`" -#: src/api/reference.md:680 +#: src/api/reference.md:682 msgid "`ESRCH`" msgstr "`ESRCH`" -#: src/api/reference.md:681 +#: src/api/reference.md:683 msgid "`ESTALE`" msgstr "`ESTALE`" -#: src/api/reference.md:682 +#: src/api/reference.md:684 msgid "`ETIME`" msgstr "`ETIME`" -#: src/api/reference.md:683 +#: src/api/reference.md:685 msgid "`ETIMEDOUT`" msgstr "`ETIMEDOUT`" -#: src/api/reference.md:684 +#: src/api/reference.md:686 msgid "`ETXTBSY`" msgstr "`ETXTBSY`" -#: src/api/reference.md:685 +#: src/api/reference.md:687 msgid "`EWOULDBLOCK`" msgstr "`EWOULDBLOCK`" -#: src/api/reference.md:686 +#: src/api/reference.md:688 msgid "`EXDEV`" msgstr "`EXDEV`" -#: src/api/reference.md:687 +#: src/api/reference.md:689 msgid "`F_OK`" msgstr "`F_OK`" -#: src/api/reference.md:688 +#: src/api/reference.md:690 msgid "`OPENSSL_VERSION_NUMBER`" msgstr "`OPENSSL_VERSION_NUMBER`" -#: src/api/reference.md:689 +#: src/api/reference.md:691 msgid "`O_APPEND`" msgstr "`O_APPEND`" -#: src/api/reference.md:690 +#: src/api/reference.md:692 msgid "`O_CREAT`" msgstr "`O_CREAT`" -#: src/api/reference.md:691 +#: src/api/reference.md:693 msgid "`O_DIRECT`" msgstr "`O_DIRECT`" -#: src/api/reference.md:692 +#: src/api/reference.md:694 msgid "`O_DIRECTORY`" msgstr "`O_DIRECTORY`" -#: src/api/reference.md:693 +#: src/api/reference.md:695 msgid "`O_DSYNC`" msgstr "`O_DSYNC`" -#: src/api/reference.md:694 +#: src/api/reference.md:696 msgid "`O_EXCL`" msgstr "`O_EXCL`" -#: src/api/reference.md:695 +#: src/api/reference.md:697 msgid "`O_NOATIME`" msgstr "`O_NOATIME`" -#: src/api/reference.md:696 +#: src/api/reference.md:698 msgid "`O_NOCTTY`" msgstr "`O_NOCTTY`" -#: src/api/reference.md:697 +#: src/api/reference.md:699 msgid "`O_NOFOLLOW`" msgstr "`O_NOFOLLOW`" -#: src/api/reference.md:698 +#: src/api/reference.md:700 msgid "`O_NONBLOCK`" msgstr "`O_NONBLOCK`" -#: src/api/reference.md:699 +#: src/api/reference.md:701 msgid "`O_RDONLY`" msgstr "`O_RDONLY`" -#: src/api/reference.md:700 +#: src/api/reference.md:702 msgid "`O_RDWR`" msgstr "`O_RDWR`" -#: src/api/reference.md:701 +#: src/api/reference.md:703 msgid "`O_SYMLINK`" msgstr "`O_SYMLINK`" -#: src/api/reference.md:702 +#: src/api/reference.md:704 msgid "`O_SYNC`" msgstr "`O_SYNC`" -#: src/api/reference.md:703 +#: src/api/reference.md:705 msgid "`O_TRUNC`" msgstr "`O_TRUNC`" -#: src/api/reference.md:704 +#: src/api/reference.md:706 msgid "`O_WRONLY`" msgstr "`O_WRONLY`" -#: src/api/reference.md:705 +#: src/api/reference.md:707 msgid "`POINT_CONVERSION_COMPRESSED`" msgstr "`POINT_CONVERSION_COMPRESSED`" -#: src/api/reference.md:706 +#: src/api/reference.md:708 msgid "`POINT_CONVERSION_HYBRID`" msgstr "`POINT_CONVERSION_HYBRID`" -#: src/api/reference.md:707 +#: src/api/reference.md:709 msgid "`POINT_CONVERSION_UNCOMPRESSED`" msgstr "`POINT_CONVERSION_UNCOMPRESSED`" -#: src/api/reference.md:708 +#: src/api/reference.md:710 msgid "`PRIORITY_ABOVE_NORMAL`" msgstr "`PRIORITY_ABOVE_NORMAL`" -#: src/api/reference.md:709 +#: src/api/reference.md:711 msgid "`PRIORITY_BELOW_NORMAL`" msgstr "`PRIORITY_BELOW_NORMAL`" -#: src/api/reference.md:710 +#: src/api/reference.md:712 msgid "`PRIORITY_HIGH`" msgstr "`PRIORITY_HIGH`" -#: src/api/reference.md:711 +#: src/api/reference.md:713 msgid "`PRIORITY_HIGHEST`" msgstr "`PRIORITY_HIGHEST`" -#: src/api/reference.md:712 +#: src/api/reference.md:714 msgid "`PRIORITY_LOW`" msgstr "`PRIORITY_LOW`" -#: src/api/reference.md:713 +#: src/api/reference.md:715 msgid "`PRIORITY_NORMAL`" msgstr "`PRIORITY_NORMAL`" -#: src/api/reference.md:714 +#: src/api/reference.md:716 msgid "`RSA_NO_PADDING`" msgstr "`RSA_NO_PADDING`" -#: src/api/reference.md:715 +#: src/api/reference.md:717 msgid "`RSA_PKCS1_OAEP_PADDING`" msgstr "`RSA_PKCS1_OAEP_PADDING`" -#: src/api/reference.md:716 +#: src/api/reference.md:718 msgid "`RSA_PKCS1_PADDING`" msgstr "`RSA_PKCS1_PADDING`" -#: src/api/reference.md:717 +#: src/api/reference.md:719 msgid "`RSA_PKCS1_PSS_PADDING`" msgstr "`RSA_PKCS1_PSS_PADDING`" -#: src/api/reference.md:718 +#: src/api/reference.md:720 msgid "`RSA_PSS_SALTLEN_AUTO`" msgstr "`RSA_PSS_SALTLEN_AUTO`" -#: src/api/reference.md:719 +#: src/api/reference.md:721 msgid "`RSA_PSS_SALTLEN_DIGEST`" msgstr "`RSA_PSS_SALTLEN_DIGEST`" -#: src/api/reference.md:720 +#: src/api/reference.md:722 msgid "`RSA_PSS_SALTLEN_MAX_SIGN`" msgstr "`RSA_PSS_SALTLEN_MAX_SIGN`" -#: src/api/reference.md:721 +#: src/api/reference.md:723 msgid "`RSA_X931_PADDING`" msgstr "`RSA_X931_PADDING`" -#: src/api/reference.md:722 +#: src/api/reference.md:724 msgid "`RTLD_DEEPBIND`" msgstr "`RTLD_DEEPBIND`" -#: src/api/reference.md:723 +#: src/api/reference.md:725 msgid "`RTLD_GLOBAL`" msgstr "`RTLD_GLOBAL`" -#: src/api/reference.md:724 +#: src/api/reference.md:726 msgid "`RTLD_LAZY`" msgstr "`RTLD_LAZY`" -#: src/api/reference.md:725 +#: src/api/reference.md:727 msgid "`RTLD_LOCAL`" msgstr "`RTLD_LOCAL`" -#: src/api/reference.md:726 +#: src/api/reference.md:728 msgid "`RTLD_NOW`" msgstr "`RTLD_NOW`" -#: src/api/reference.md:727 +#: src/api/reference.md:729 msgid "`R_OK`" msgstr "`R_OK`" -#: src/api/reference.md:728 +#: src/api/reference.md:730 msgid "`SIGABRT`" msgstr "`SIGABRT`" -#: src/api/reference.md:729 +#: src/api/reference.md:731 msgid "`SIGALRM`" msgstr "`SIGALRM`" -#: src/api/reference.md:730 +#: src/api/reference.md:732 msgid "`SIGBUS`" msgstr "`SIGBUS`" -#: src/api/reference.md:731 +#: src/api/reference.md:733 msgid "`SIGCHLD`" msgstr "`SIGCHLD`" -#: src/api/reference.md:732 +#: src/api/reference.md:734 msgid "`SIGCONT`" msgstr "`SIGCONT`" -#: src/api/reference.md:733 +#: src/api/reference.md:735 msgid "`SIGFPE`" msgstr "`SIGFPE`" -#: src/api/reference.md:734 +#: src/api/reference.md:736 msgid "`SIGHUP`" msgstr "`SIGHUP`" -#: src/api/reference.md:735 +#: src/api/reference.md:737 msgid "`SIGILL`" msgstr "`SIGILL`" -#: src/api/reference.md:736 +#: src/api/reference.md:738 msgid "`SIGINFO`" msgstr "`SIGINFO`" -#: src/api/reference.md:737 +#: src/api/reference.md:739 msgid "`SIGINT`" msgstr "`SIGINT`" -#: src/api/reference.md:738 +#: src/api/reference.md:740 msgid "`SIGIO`" msgstr "`SIGIO`" -#: src/api/reference.md:739 +#: src/api/reference.md:741 msgid "`SIGIOT`" msgstr "`SIGIOT`" -#: src/api/reference.md:740 +#: src/api/reference.md:742 msgid "`SIGKILL`" msgstr "`SIGKILL`" -#: src/api/reference.md:741 +#: src/api/reference.md:743 msgid "`SIGPIPE`" msgstr "`SIGPIPE`" -#: src/api/reference.md:742 +#: src/api/reference.md:744 msgid "`SIGPOLL`" msgstr "`SIGPOLL`" -#: src/api/reference.md:743 +#: src/api/reference.md:745 msgid "`SIGPROF`" msgstr "`SIGPROF`" -#: src/api/reference.md:744 +#: src/api/reference.md:746 msgid "`SIGPWR`" msgstr "`SIGPWR`" -#: src/api/reference.md:745 +#: src/api/reference.md:747 msgid "`SIGQUIT`" msgstr "`SIGQUIT`" -#: src/api/reference.md:746 +#: src/api/reference.md:748 msgid "`SIGSEGV`" msgstr "`SIGSEGV`" -#: src/api/reference.md:747 +#: src/api/reference.md:749 msgid "`SIGSTKFLT`" msgstr "`SIGSTKFLT`" -#: src/api/reference.md:748 +#: src/api/reference.md:750 msgid "`SIGSTOP`" msgstr "`SIGSTOP`" -#: src/api/reference.md:749 +#: src/api/reference.md:751 msgid "`SIGSYS`" msgstr "`SIGSYS`" -#: src/api/reference.md:750 +#: src/api/reference.md:752 msgid "`SIGTERM`" msgstr "`SIGTERM`" -#: src/api/reference.md:751 +#: src/api/reference.md:753 msgid "`SIGTRAP`" msgstr "`SIGTRAP`" -#: src/api/reference.md:752 +#: src/api/reference.md:754 msgid "`SIGTSTP`" msgstr "`SIGTSTP`" -#: src/api/reference.md:753 +#: src/api/reference.md:755 msgid "`SIGTTIN`" msgstr "`SIGTTIN`" -#: src/api/reference.md:754 +#: src/api/reference.md:756 msgid "`SIGTTOU`" msgstr "`SIGTTOU`" -#: src/api/reference.md:755 +#: src/api/reference.md:757 msgid "`SIGURG`" msgstr "`SIGURG`" -#: src/api/reference.md:756 +#: src/api/reference.md:758 msgid "`SIGUSR1`" msgstr "`SIGUSR1`" -#: src/api/reference.md:757 +#: src/api/reference.md:759 msgid "`SIGUSR2`" msgstr "`SIGUSR2`" -#: src/api/reference.md:758 +#: src/api/reference.md:760 msgid "`SIGVTALRM`" msgstr "`SIGVTALRM`" -#: src/api/reference.md:759 +#: src/api/reference.md:761 msgid "`SIGWINCH`" msgstr "`SIGWINCH`" -#: src/api/reference.md:760 +#: src/api/reference.md:762 msgid "`SIGXCPU`" msgstr "`SIGXCPU`" -#: src/api/reference.md:761 +#: src/api/reference.md:763 msgid "`SIGXFSZ`" msgstr "`SIGXFSZ`" -#: src/api/reference.md:762 +#: src/api/reference.md:764 msgid "`SSL_OP_ALL`" msgstr "`SSL_OP_ALL`" -#: src/api/reference.md:763 +#: src/api/reference.md:765 msgid "`SSL_OP_ALLOW_NO_DHE_KEX`" msgstr "`SSL_OP_ALLOW_NO_DHE_KEX`" -#: src/api/reference.md:764 +#: src/api/reference.md:766 msgid "`SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION`" msgstr "`SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION`" -#: src/api/reference.md:765 +#: src/api/reference.md:767 msgid "`SSL_OP_CIPHER_SERVER_PREFERENCE`" msgstr "`SSL_OP_CIPHER_SERVER_PREFERENCE`" -#: src/api/reference.md:766 +#: src/api/reference.md:768 msgid "`SSL_OP_CISCO_ANYCONNECT`" msgstr "`SSL_OP_CISCO_ANYCONNECT`" -#: src/api/reference.md:767 +#: src/api/reference.md:769 msgid "`SSL_OP_COOKIE_EXCHANGE`" msgstr "`SSL_OP_COOKIE_EXCHANGE`" -#: src/api/reference.md:768 +#: src/api/reference.md:770 msgid "`SSL_OP_CRYPTOPRO_TLSEXT_BUG`" msgstr "`SSL_OP_CRYPTOPRO_TLSEXT_BUG`" -#: src/api/reference.md:769 +#: src/api/reference.md:771 msgid "`SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS`" msgstr "`SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS`" -#: src/api/reference.md:770 +#: src/api/reference.md:772 msgid "`SSL_OP_LEGACY_SERVER_CONNECT`" msgstr "`SSL_OP_LEGACY_SERVER_CONNECT`" -#: src/api/reference.md:771 +#: src/api/reference.md:773 msgid "`SSL_OP_NO_COMPRESSION`" msgstr "`SSL_OP_NO_COMPRESSION`" -#: src/api/reference.md:772 +#: src/api/reference.md:774 msgid "`SSL_OP_NO_ENCRYPT_THEN_MAC`" msgstr "`SSL_OP_NO_ENCRYPT_THEN_MAC`" -#: src/api/reference.md:773 +#: src/api/reference.md:775 msgid "`SSL_OP_NO_QUERY_MTU`" msgstr "`SSL_OP_NO_QUERY_MTU`" -#: src/api/reference.md:774 +#: src/api/reference.md:776 msgid "`SSL_OP_NO_RENEGOTIATION`" msgstr "`SSL_OP_NO_RENEGOTIATION`" -#: src/api/reference.md:775 +#: src/api/reference.md:777 msgid "`SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION`" msgstr "`SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION`" -#: src/api/reference.md:776 +#: src/api/reference.md:778 msgid "`SSL_OP_NO_SSLv2`" msgstr "`SSL_OP_NO_SSLv2`" -#: src/api/reference.md:777 +#: src/api/reference.md:779 msgid "`SSL_OP_NO_SSLv3`" msgstr "`SSL_OP_NO_SSLv3`" -#: src/api/reference.md:778 +#: src/api/reference.md:780 msgid "`SSL_OP_NO_TICKET`" msgstr "`SSL_OP_NO_TICKET`" -#: src/api/reference.md:779 +#: src/api/reference.md:781 msgid "`SSL_OP_NO_TLSv1`" msgstr "`SSL_OP_NO_TLSv1`" -#: src/api/reference.md:780 +#: src/api/reference.md:782 msgid "`SSL_OP_NO_TLSv1_1`" msgstr "`SSL_OP_NO_TLSv1_1`" -#: src/api/reference.md:781 +#: src/api/reference.md:783 msgid "`SSL_OP_NO_TLSv1_2`" msgstr "`SSL_OP_NO_TLSv1_2`" -#: src/api/reference.md:782 +#: src/api/reference.md:784 msgid "`SSL_OP_NO_TLSv1_3`" msgstr "`SSL_OP_NO_TLSv1_3`" -#: src/api/reference.md:783 +#: src/api/reference.md:785 msgid "`SSL_OP_PRIORITIZE_CHACHA`" msgstr "`SSL_OP_PRIORITIZE_CHACHA`" -#: src/api/reference.md:784 +#: src/api/reference.md:786 msgid "`SSL_OP_TLS_ROLLBACK_BUG`" msgstr "`SSL_OP_TLS_ROLLBACK_BUG`" -#: src/api/reference.md:785 +#: src/api/reference.md:787 msgid "`S_IFBLK`" msgstr "`S_IFBLK`" -#: src/api/reference.md:786 +#: src/api/reference.md:788 msgid "`S_IFCHR`" msgstr "`S_IFCHR`" -#: src/api/reference.md:787 +#: src/api/reference.md:789 msgid "`S_IFDIR`" msgstr "`S_IFDIR`" -#: src/api/reference.md:788 +#: src/api/reference.md:790 msgid "`S_IFIFO`" msgstr "`S_IFIFO`" -#: src/api/reference.md:789 +#: src/api/reference.md:791 msgid "`S_IFLNK`" msgstr "`S_IFLNK`" -#: src/api/reference.md:790 +#: src/api/reference.md:792 msgid "`S_IFMT`" msgstr "`S_IFMT`" -#: src/api/reference.md:791 +#: src/api/reference.md:793 msgid "`S_IFREG`" msgstr "`S_IFREG`" -#: src/api/reference.md:792 +#: src/api/reference.md:794 msgid "`S_IFSOCK`" msgstr "`S_IFSOCK`" -#: src/api/reference.md:793 +#: src/api/reference.md:795 msgid "`S_IRGRP`" msgstr "`S_IRGRP`" -#: src/api/reference.md:794 +#: src/api/reference.md:796 msgid "`S_IROTH`" msgstr "`S_IROTH`" -#: src/api/reference.md:795 +#: src/api/reference.md:797 msgid "`S_IRUSR`" msgstr "`S_IRUSR`" -#: src/api/reference.md:796 +#: src/api/reference.md:798 msgid "`S_IRWXG`" msgstr "`S_IRWXG`" -#: src/api/reference.md:797 +#: src/api/reference.md:799 msgid "`S_IRWXO`" msgstr "`S_IRWXO`" -#: src/api/reference.md:798 +#: src/api/reference.md:800 msgid "`S_IRWXU`" msgstr "`S_IRWXU`" -#: src/api/reference.md:799 +#: src/api/reference.md:801 msgid "`S_IWGRP`" msgstr "`S_IWGRP`" -#: src/api/reference.md:800 +#: src/api/reference.md:802 msgid "`S_IWOTH`" msgstr "`S_IWOTH`" -#: src/api/reference.md:801 +#: src/api/reference.md:803 msgid "`S_IWUSR`" msgstr "`S_IWUSR`" -#: src/api/reference.md:802 +#: src/api/reference.md:804 msgid "`S_IXGRP`" msgstr "`S_IXGRP`" -#: src/api/reference.md:803 +#: src/api/reference.md:805 msgid "`S_IXOTH`" msgstr "`S_IXOTH`" -#: src/api/reference.md:804 +#: src/api/reference.md:806 msgid "`S_IXUSR`" msgstr "`S_IXUSR`" -#: src/api/reference.md:805 +#: src/api/reference.md:807 msgid "`TLS1_1_VERSION`" msgstr "`TLS1_1_VERSION`" -#: src/api/reference.md:806 +#: src/api/reference.md:808 msgid "`TLS1_2_VERSION`" msgstr "`TLS1_2_VERSION`" -#: src/api/reference.md:807 +#: src/api/reference.md:809 msgid "`TLS1_3_VERSION`" msgstr "`TLS1_3_VERSION`" -#: src/api/reference.md:808 +#: src/api/reference.md:810 msgid "`TLS1_VERSION`" msgstr "`TLS1_VERSION`" -#: src/api/reference.md:809 +#: src/api/reference.md:811 msgid "`UV_DIRENT_BLOCK`" msgstr "`UV_DIRENT_BLOCK`" -#: src/api/reference.md:810 +#: src/api/reference.md:812 msgid "`UV_DIRENT_CHAR`" msgstr "`UV_DIRENT_CHAR`" -#: src/api/reference.md:811 +#: src/api/reference.md:813 msgid "`UV_DIRENT_DIR`" msgstr "`UV_DIRENT_DIR`" -#: src/api/reference.md:812 +#: src/api/reference.md:814 msgid "`UV_DIRENT_FIFO`" msgstr "`UV_DIRENT_FIFO`" -#: src/api/reference.md:813 +#: src/api/reference.md:815 msgid "`UV_DIRENT_FILE`" msgstr "`UV_DIRENT_FILE`" -#: src/api/reference.md:814 +#: src/api/reference.md:816 msgid "`UV_DIRENT_LINK`" msgstr "`UV_DIRENT_LINK`" -#: src/api/reference.md:815 +#: src/api/reference.md:817 msgid "`UV_DIRENT_SOCKET`" msgstr "`UV_DIRENT_SOCKET`" -#: src/api/reference.md:816 +#: src/api/reference.md:818 msgid "`UV_DIRENT_UNKNOWN`" msgstr "`UV_DIRENT_UNKNOWN`" -#: src/api/reference.md:817 +#: src/api/reference.md:819 msgid "`UV_FS_COPYFILE_EXCL`" msgstr "`UV_FS_COPYFILE_EXCL`" -#: src/api/reference.md:818 +#: src/api/reference.md:820 msgid "`UV_FS_COPYFILE_FICLONE`" msgstr "`UV_FS_COPYFILE_FICLONE`" -#: src/api/reference.md:819 +#: src/api/reference.md:821 msgid "`UV_FS_COPYFILE_FICLONE_FORCE`" msgstr "`UV_FS_COPYFILE_FICLONE_FORCE`" -#: src/api/reference.md:820 +#: src/api/reference.md:822 msgid "`UV_FS_O_FILEMAP`" msgstr "`UV_FS_O_FILEMAP`" -#: src/api/reference.md:821 +#: src/api/reference.md:823 msgid "`UV_FS_SYMLINK_DIR`" msgstr "`UV_FS_SYMLINK_DIR`" -#: src/api/reference.md:822 +#: src/api/reference.md:824 msgid "`UV_FS_SYMLINK_JUNCTION`" msgstr "`UV_FS_SYMLINK_JUNCTION`" -#: src/api/reference.md:823 +#: src/api/reference.md:825 msgid "`W_OK`" msgstr "`W_OK`" -#: src/api/reference.md:824 +#: src/api/reference.md:826 msgid "`X_OK`" msgstr "`X_OK`" -#: src/api/reference.md:826 +#: src/api/reference.md:828 msgid "`defaultCoreCipherList`" msgstr "`defaultCoreCipherList`" -#: src/api/reference.md:828 src/cli/capabilities.md:52 +#: src/api/reference.md:830 src/cli/capabilities.md:52 msgid "`crypto`" msgstr "`crypto`" -#: src/api/reference.md:832 +#: src/api/reference.md:834 msgid "`Cipheriv`" msgstr "`Cipheriv`" -#: src/api/reference.md:833 +#: src/api/reference.md:835 msgid "`Decipheriv`" msgstr "`Decipheriv`" -#: src/api/reference.md:834 +#: src/api/reference.md:836 msgid "`DiffieHellman`" msgstr "`DiffieHellman`" -#: src/api/reference.md:835 +#: src/api/reference.md:837 msgid "`DiffieHellmanGroup`" msgstr "`DiffieHellmanGroup`" -#: src/api/reference.md:836 +#: src/api/reference.md:838 msgid "`ECDH`" msgstr "`ECDH`" -#: src/api/reference.md:837 +#: src/api/reference.md:839 msgid "`KeyObject`" msgstr "`KeyObject`" -#: src/api/reference.md:838 +#: src/api/reference.md:840 msgid "`X509Certificate`" msgstr "`X509Certificate`" -#: src/api/reference.md:842 +#: src/api/reference.md:844 msgid "`Hash` — module" msgstr "`Hash` — Modul" -#: src/api/reference.md:843 +#: src/api/reference.md:845 msgid "`Hmac` — module" msgstr "`Hmac` — Modul" -#: src/api/reference.md:844 +#: src/api/reference.md:846 msgid "`Sign` — module" msgstr "`Sign` — Modul" -#: src/api/reference.md:845 +#: src/api/reference.md:847 msgid "`Verify` — module" msgstr "`Verify` — Modul" -#: src/api/reference.md:846 +#: src/api/reference.md:848 msgid "`argon2` — module" msgstr "`argon2` — Modul" -#: src/api/reference.md:847 +#: src/api/reference.md:849 msgid "`argon2Sync` — module" msgstr "`argon2Sync` — Modul" -#: src/api/reference.md:848 +#: src/api/reference.md:850 msgid "`checkPrime` — module" msgstr "`checkPrime` — Modul" -#: src/api/reference.md:849 +#: src/api/reference.md:851 msgid "`checkPrimeSync` — module" msgstr "`checkPrimeSync` — Modul" -#: src/api/reference.md:850 +#: src/api/reference.md:852 msgid "`createCipheriv` — module" msgstr "`createCipheriv` — Modul" -#: src/api/reference.md:851 +#: src/api/reference.md:853 msgid "`createDecipheriv` — module" msgstr "`createDecipheriv` — Modul" -#: src/api/reference.md:852 +#: src/api/reference.md:854 msgid "`createDiffieHellman` — module" msgstr "`createDiffieHellman` — Modul" -#: src/api/reference.md:853 +#: src/api/reference.md:855 msgid "`createDiffieHellmanGroup` — module" msgstr "`createDiffieHellmanGroup` — Modul" -#: src/api/reference.md:854 +#: src/api/reference.md:856 msgid "`createECDH` — module" msgstr "`createECDH` — Modul" -#: src/api/reference.md:855 +#: src/api/reference.md:857 msgid "`createHash` — module" msgstr "`createHash` — Modul" -#: src/api/reference.md:856 +#: src/api/reference.md:858 msgid "`createHmac` — module" msgstr "`createHmac` — Modul" -#: src/api/reference.md:857 +#: src/api/reference.md:859 msgid "`createPrivateKey` — module" msgstr "`createPrivateKey` — Modul" -#: src/api/reference.md:858 +#: src/api/reference.md:860 msgid "`createPublicKey` — module" msgstr "`createPublicKey` — Modul" -#: src/api/reference.md:859 +#: src/api/reference.md:861 msgid "`createSecretKey` — module" msgstr "`createSecretKey` — Modul" -#: src/api/reference.md:860 src/api/reference.md:861 +#: src/api/reference.md:862 src/api/reference.md:863 msgid "`createSign` — module" msgstr "`createSign` — Modul" -#: src/api/reference.md:862 src/api/reference.md:863 +#: src/api/reference.md:864 src/api/reference.md:865 msgid "`createVerify` — module" msgstr "`createVerify` — Modul" -#: src/api/reference.md:864 +#: src/api/reference.md:866 msgid "`decapsulate` — module" msgstr "`decapsulate` — Modul" -#: src/api/reference.md:865 +#: src/api/reference.md:867 msgid "`diffieHellman` — module" msgstr "`diffieHellman` — Modul" -#: src/api/reference.md:866 +#: src/api/reference.md:868 msgid "`encapsulate` — module" msgstr "`encapsulate` — Modul" -#: src/api/reference.md:867 +#: src/api/reference.md:869 msgid "`generateKey` — module" msgstr "`generateKey` — Modul" -#: src/api/reference.md:868 +#: src/api/reference.md:870 msgid "`generateKeyPair` — module" msgstr "`generateKeyPair` — Modul" -#: src/api/reference.md:869 src/api/reference.md:870 +#: src/api/reference.md:871 src/api/reference.md:872 msgid "`generateKeyPairSync` — module" msgstr "`generateKeyPairSync` — Modul" -#: src/api/reference.md:871 +#: src/api/reference.md:873 msgid "`generateKeySync` — module" msgstr "`generateKeySync` — Modul" -#: src/api/reference.md:872 +#: src/api/reference.md:874 msgid "`generatePrime` — module" msgstr "`generatePrime` — Modul" -#: src/api/reference.md:873 +#: src/api/reference.md:875 msgid "`generatePrimeSync` — module" msgstr "`generatePrimeSync` — Modul" -#: src/api/reference.md:874 +#: src/api/reference.md:876 msgid "`getCipherInfo` — module" msgstr "`getCipherInfo` — Modul" -#: src/api/reference.md:875 src/api/reference.md:3312 +#: src/api/reference.md:877 src/api/reference.md:3321 msgid "`getCiphers` — module" msgstr "`getCiphers` — Modul" -#: src/api/reference.md:876 +#: src/api/reference.md:878 msgid "`getCurves` — module" msgstr "`getCurves` — Modul" -#: src/api/reference.md:877 +#: src/api/reference.md:879 msgid "`getDiffieHellman` — module" msgstr "`getDiffieHellman` — Modul" -#: src/api/reference.md:878 +#: src/api/reference.md:880 msgid "`getFips` — module" msgstr "`getFips` — Modul" -#: src/api/reference.md:879 +#: src/api/reference.md:881 msgid "`getHashes` — module" msgstr "`getHashes` — Modul" -#: src/api/reference.md:880 +#: src/api/reference.md:882 msgid "`getRandomValues` — module" msgstr "`getRandomValues` — Modul" -#: src/api/reference.md:882 +#: src/api/reference.md:884 msgid "`hkdf` — module" msgstr "`hkdf` — Modul" -#: src/api/reference.md:883 +#: src/api/reference.md:885 msgid "`hkdfSync` — module" msgstr "`hkdfSync` — Modul" -#: src/api/reference.md:884 +#: src/api/reference.md:886 msgid "`pbkdf2` — module" msgstr "`pbkdf2` — Modul" -#: src/api/reference.md:885 +#: src/api/reference.md:887 msgid "`pbkdf2Sync` — module" msgstr "`pbkdf2Sync` — Modul" -#: src/api/reference.md:886 +#: src/api/reference.md:888 msgid "`privateDecrypt` — module" msgstr "`privateDecrypt` — Modul" -#: src/api/reference.md:887 +#: src/api/reference.md:889 msgid "`privateEncrypt` — module" msgstr "`privateEncrypt` — Modul" -#: src/api/reference.md:888 +#: src/api/reference.md:890 msgid "`publicDecrypt` — module" msgstr "`publicDecrypt` — Modul" -#: src/api/reference.md:889 +#: src/api/reference.md:891 msgid "`publicEncrypt` — module" msgstr "`publicEncrypt` — Modul" -#: src/api/reference.md:890 +#: src/api/reference.md:892 msgid "`randomBytes` — module" msgstr "`randomBytes` — Modul" -#: src/api/reference.md:891 +#: src/api/reference.md:893 msgid "`randomFill` — module" msgstr "`randomFill` — Modul" -#: src/api/reference.md:892 +#: src/api/reference.md:894 msgid "`randomFillSync` — module" msgstr "`randomFillSync` — Modul" -#: src/api/reference.md:893 src/api/reference.md:894 +#: src/api/reference.md:895 src/api/reference.md:896 msgid "`randomInt` — module" msgstr "`randomInt` — Modul" -#: src/api/reference.md:895 +#: src/api/reference.md:897 msgid "`randomUUID` — module" msgstr "`randomUUID` — Modul" -#: src/api/reference.md:896 +#: src/api/reference.md:898 msgid "`scrypt` — module" msgstr "`scrypt` — Modul" -#: src/api/reference.md:897 +#: src/api/reference.md:899 msgid "`scryptSync` — module" msgstr "`scryptSync` — Modul" -#: src/api/reference.md:898 +#: src/api/reference.md:900 msgid "`secureHeapUsed` — module" msgstr "`secureHeapUsed` — Modul" -#: src/api/reference.md:899 +#: src/api/reference.md:901 msgid "`setFips` — module" msgstr "`setFips` — Modul" -#: src/api/reference.md:900 +#: src/api/reference.md:902 msgid "`sign` — module" msgstr "`sign` — Modul" -#: src/api/reference.md:901 +#: src/api/reference.md:903 msgid "`timingSafeEqual` — module" msgstr "`timingSafeEqual` — Modul" -#: src/api/reference.md:906 +#: src/api/reference.md:908 msgid "`Certificate`" msgstr "`Certificate`" -#: src/api/reference.md:908 +#: src/api/reference.md:910 msgid "`subtle`" msgstr "`subtle`" -#: src/api/reference.md:909 +#: src/api/reference.md:911 msgid "`webcrypto`" msgstr "`webcrypto`" -#: src/api/reference.md:911 +#: src/api/reference.md:913 msgid "`dgram`" msgstr "`dgram`" -#: src/api/reference.md:915 src/api/reference.md:1863 +#: src/api/reference.md:917 src/api/reference.md:1865 msgid "`Socket`" msgstr "`Socket`" -#: src/api/reference.md:919 src/api/reference.md:1871 +#: src/api/reference.md:921 src/api/reference.md:1873 msgid "`Socket` — module" msgstr "`Socket` — Modul" -#: src/api/reference.md:920 src/api/reference.md:1881 +#: src/api/reference.md:922 src/api/reference.md:1883 msgid "`addListener` — instance _(class: `Socket`)_" msgstr "`addListener` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:921 +#: src/api/reference.md:923 msgid "`addMembership` — instance _(class: `Socket`)_" msgstr "`addMembership` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:922 +#: src/api/reference.md:924 msgid "`addSourceSpecificMembership` — instance _(class: `Socket`)_" msgstr "`addSourceSpecificMembership` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:923 src/api/reference.md:1885 +#: src/api/reference.md:925 src/api/reference.md:1887 msgid "`address` — instance _(class: `Socket`)_" msgstr "`address` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:924 +#: src/api/reference.md:926 msgid "`bind` — instance _(class: `Socket`)_" msgstr "`bind` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:925 +#: src/api/reference.md:927 msgid "`close` — instance _(class: `Socket`)_" msgstr "`close` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:926 src/api/reference.md:1895 +#: src/api/reference.md:928 src/api/reference.md:1897 msgid "`connect` — instance _(class: `Socket`)_" msgstr "`connect` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:927 +#: src/api/reference.md:929 msgid "`createSocket` — module" msgstr "`createSocket` — Modul" -#: src/api/reference.md:928 +#: src/api/reference.md:930 msgid "`disconnect` — instance _(class: `Socket`)_" msgstr "`disconnect` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:929 +#: src/api/reference.md:931 msgid "`dropMembership` — instance _(class: `Socket`)_" msgstr "`dropMembership` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:930 +#: src/api/reference.md:932 msgid "`dropSourceSpecificMembership` — instance _(class: `Socket`)_" msgstr "`dropSourceSpecificMembership` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:931 +#: src/api/reference.md:933 msgid "`emit` — instance _(class: `Socket`)_" msgstr "`emit` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:932 src/api/reference.md:1904 +#: src/api/reference.md:934 src/api/reference.md:1906 msgid "`eventNames` — instance _(class: `Socket`)_" msgstr "`eventNames` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:933 +#: src/api/reference.md:935 msgid "`getRecvBufferSize` — instance _(class: `Socket`)_" msgstr "`getRecvBufferSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:934 +#: src/api/reference.md:936 msgid "`getSendBufferSize` — instance _(class: `Socket`)_" msgstr "`getSendBufferSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:935 +#: src/api/reference.md:937 msgid "`getSendQueueCount` — instance _(class: `Socket`)_" msgstr "`getSendQueueCount` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:936 +#: src/api/reference.md:938 msgid "`getSendQueueSize` — instance _(class: `Socket`)_" msgstr "`getSendQueueSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:937 src/api/reference.md:1932 +#: src/api/reference.md:939 src/api/reference.md:1934 msgid "`listenerCount` — instance _(class: `Socket`)_" msgstr "`listenerCount` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:938 src/api/reference.md:1941 +#: src/api/reference.md:940 src/api/reference.md:1943 msgid "`off` — instance _(class: `Socket`)_" msgstr "`off` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:939 src/api/reference.md:1943 +#: src/api/reference.md:941 src/api/reference.md:1945 msgid "`on` — instance _(class: `Socket`)_" msgstr "`on` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:940 src/api/reference.md:1944 +#: src/api/reference.md:942 src/api/reference.md:1946 msgid "`once` — instance _(class: `Socket`)_" msgstr "`once` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:941 src/api/reference.md:1959 +#: src/api/reference.md:943 src/api/reference.md:1961 msgid "`ref` — instance _(class: `Socket`)_" msgstr "`ref` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:942 src/api/reference.md:1960 +#: src/api/reference.md:944 src/api/reference.md:1962 msgid "`remoteAddress` — instance _(class: `Socket`)_" msgstr "`remoteAddress` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:943 src/api/reference.md:1965 +#: src/api/reference.md:945 src/api/reference.md:1967 msgid "`removeListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:944 +#: src/api/reference.md:946 msgid "`send` — instance _(class: `Socket`)_" msgstr "`send` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:945 +#: src/api/reference.md:947 msgid "`sendto` — instance _(class: `Socket`)_" msgstr "`sendto` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:946 +#: src/api/reference.md:948 msgid "`setBroadcast` — instance _(class: `Socket`)_" msgstr "`setBroadcast` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:947 +#: src/api/reference.md:949 msgid "`setMulticastInterface` — instance _(class: `Socket`)_" msgstr "`setMulticastInterface` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:948 +#: src/api/reference.md:950 msgid "`setMulticastLoopback` — instance _(class: `Socket`)_" msgstr "`setMulticastLoopback` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:949 +#: src/api/reference.md:951 msgid "`setMulticastTTL` — instance _(class: `Socket`)_" msgstr "`setMulticastTTL` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:950 +#: src/api/reference.md:952 msgid "`setRecvBufferSize` — instance _(class: `Socket`)_" msgstr "`setRecvBufferSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:951 +#: src/api/reference.md:953 msgid "`setSendBufferSize` — instance _(class: `Socket`)_" msgstr "`setSendBufferSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:952 +#: src/api/reference.md:954 msgid "`setTTL` — instance _(class: `Socket`)_" msgstr "`setTTL` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:953 src/api/reference.md:1984 +#: src/api/reference.md:955 src/api/reference.md:1986 msgid "`unref` — instance _(class: `Socket`)_" msgstr "`unref` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:959 +#: src/api/reference.md:961 msgid "`diagnostics_channel`" msgstr "`diagnostics_channel`" -#: src/api/reference.md:963 +#: src/api/reference.md:965 msgid "`BoundedChannel`" msgstr "`BoundedChannel`" -#: src/api/reference.md:964 +#: src/api/reference.md:966 msgid "`Channel`" msgstr "`Channel`" -#: src/api/reference.md:968 +#: src/api/reference.md:970 msgid "`boundedChannel` — module" msgstr "`boundedChannel` — Modul" -#: src/api/reference.md:969 +#: src/api/reference.md:971 msgid "`channel` — module" msgstr "`channel` — Modul" -#: src/api/reference.md:970 +#: src/api/reference.md:972 msgid "`hasSubscribers` — module" msgstr "`hasSubscribers` — Modul" -#: src/api/reference.md:972 +#: src/api/reference.md:974 msgid "`tracingChannel` — module" msgstr "`tracingChannel` — Modul" -#: src/api/reference.md:979 +#: src/api/reference.md:981 msgid "`dns`" msgstr "`dns`" -#: src/api/reference.md:983 src/api/reference.md:1092 +#: src/api/reference.md:985 src/api/reference.md:1094 msgid "`Resolver`" msgstr "`Resolver`" -#: src/api/reference.md:987 src/api/reference.md:1096 +#: src/api/reference.md:989 src/api/reference.md:1098 msgid "`Resolver` — module" msgstr "`Resolver` — Modul" -#: src/api/reference.md:988 src/api/reference.md:1097 +#: src/api/reference.md:990 src/api/reference.md:1099 msgid "`cancel` — instance _(class: `Resolver`)_" msgstr "`cancel` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:989 src/api/reference.md:1098 +#: src/api/reference.md:991 src/api/reference.md:1100 msgid "`getDefaultResultOrder` — module" msgstr "`getDefaultResultOrder` — Modul" -#: src/api/reference.md:990 src/api/reference.md:1099 +#: src/api/reference.md:992 src/api/reference.md:1101 msgid "`getServers` — module" msgstr "`getServers` — Modul" -#: src/api/reference.md:991 src/api/reference.md:1100 +#: src/api/reference.md:993 src/api/reference.md:1102 msgid "`getServers` — instance _(class: `Resolver`)_" msgstr "`getServers` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:992 src/api/reference.md:1101 +#: src/api/reference.md:994 src/api/reference.md:1103 msgid "`lookup` — module" msgstr "`lookup` — Modul" -#: src/api/reference.md:993 src/api/reference.md:1102 +#: src/api/reference.md:995 src/api/reference.md:1104 msgid "`lookupService` — module" msgstr "`lookupService` — Modul" -#: src/api/reference.md:994 src/api/reference.md:1103 src/api/reference.md:2070 -#: src/api/reference.md:2096 src/api/reference.md:2122 -#: src/api/reference.md:3416 +#: src/api/reference.md:996 src/api/reference.md:1105 src/api/reference.md:2072 +#: src/api/reference.md:2098 src/api/reference.md:2124 +#: src/api/reference.md:3436 msgid "`resolve` — module" msgstr "`resolve` — Modul" -#: src/api/reference.md:995 src/api/reference.md:1104 +#: src/api/reference.md:997 src/api/reference.md:1106 msgid "`resolve` — instance _(class: `Resolver`)_" msgstr "`resolve` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:996 src/api/reference.md:1105 +#: src/api/reference.md:998 src/api/reference.md:1107 msgid "`resolve4` — module" msgstr "`resolve4` — Modul" -#: src/api/reference.md:997 src/api/reference.md:1106 +#: src/api/reference.md:999 src/api/reference.md:1108 msgid "`resolve4` — instance _(class: `Resolver`)_" msgstr "`resolve4` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:998 src/api/reference.md:1107 +#: src/api/reference.md:1000 src/api/reference.md:1109 msgid "`resolve6` — module" msgstr "`resolve6` — Modul" -#: src/api/reference.md:999 src/api/reference.md:1108 +#: src/api/reference.md:1001 src/api/reference.md:1110 msgid "`resolve6` — instance _(class: `Resolver`)_" msgstr "`resolve6` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1000 src/api/reference.md:1109 +#: src/api/reference.md:1002 src/api/reference.md:1111 msgid "`resolveAny` — module" msgstr "`resolveAny` — Modul" -#: src/api/reference.md:1001 src/api/reference.md:1110 +#: src/api/reference.md:1003 src/api/reference.md:1112 msgid "`resolveAny` — instance _(class: `Resolver`)_" msgstr "`resolveAny` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1002 src/api/reference.md:1111 +#: src/api/reference.md:1004 src/api/reference.md:1113 msgid "`resolveCaa` — module" msgstr "`resolveCaa` — Modul" -#: src/api/reference.md:1003 src/api/reference.md:1112 +#: src/api/reference.md:1005 src/api/reference.md:1114 msgid "`resolveCaa` — instance _(class: `Resolver`)_" msgstr "`resolveCaa` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1004 src/api/reference.md:1113 +#: src/api/reference.md:1006 src/api/reference.md:1115 msgid "`resolveCname` — module" msgstr "`resolveCname` — Modul" -#: src/api/reference.md:1005 src/api/reference.md:1114 +#: src/api/reference.md:1007 src/api/reference.md:1116 msgid "`resolveCname` — instance _(class: `Resolver`)_" msgstr "`resolveCname` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1006 src/api/reference.md:1115 +#: src/api/reference.md:1008 src/api/reference.md:1117 msgid "`resolveMx` — module" msgstr "`resolveMx` — Modul" -#: src/api/reference.md:1007 src/api/reference.md:1116 +#: src/api/reference.md:1009 src/api/reference.md:1118 msgid "`resolveMx` — instance _(class: `Resolver`)_" msgstr "`resolveMx` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1008 src/api/reference.md:1117 +#: src/api/reference.md:1010 src/api/reference.md:1119 msgid "`resolveNaptr` — module" msgstr "`resolveNaptr` — Modul" -#: src/api/reference.md:1009 src/api/reference.md:1118 +#: src/api/reference.md:1011 src/api/reference.md:1120 msgid "`resolveNaptr` — instance _(class: `Resolver`)_" msgstr "`resolveNaptr` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1010 src/api/reference.md:1119 +#: src/api/reference.md:1012 src/api/reference.md:1121 msgid "`resolveNs` — module" msgstr "`resolveNs` — Modul" -#: src/api/reference.md:1011 src/api/reference.md:1120 +#: src/api/reference.md:1013 src/api/reference.md:1122 msgid "`resolveNs` — instance _(class: `Resolver`)_" msgstr "`resolveNs` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1012 src/api/reference.md:1121 +#: src/api/reference.md:1014 src/api/reference.md:1123 msgid "`resolvePtr` — module" msgstr "`resolvePtr` — Modul" -#: src/api/reference.md:1013 src/api/reference.md:1122 +#: src/api/reference.md:1015 src/api/reference.md:1124 msgid "`resolvePtr` — instance _(class: `Resolver`)_" msgstr "`resolvePtr` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1014 src/api/reference.md:1123 +#: src/api/reference.md:1016 src/api/reference.md:1125 msgid "`resolveSoa` — module" msgstr "`resolveSoa` — Modul" -#: src/api/reference.md:1015 src/api/reference.md:1124 +#: src/api/reference.md:1017 src/api/reference.md:1126 msgid "`resolveSoa` — instance _(class: `Resolver`)_" msgstr "`resolveSoa` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1016 src/api/reference.md:1125 +#: src/api/reference.md:1018 src/api/reference.md:1127 msgid "`resolveSrv` — module" msgstr "`resolveSrv` — Modul" -#: src/api/reference.md:1017 src/api/reference.md:1126 +#: src/api/reference.md:1019 src/api/reference.md:1128 msgid "`resolveSrv` — instance _(class: `Resolver`)_" msgstr "`resolveSrv` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1018 src/api/reference.md:1127 +#: src/api/reference.md:1020 src/api/reference.md:1129 msgid "`resolveTlsa` — module" msgstr "`resolveTlsa` — Modul" -#: src/api/reference.md:1019 src/api/reference.md:1128 +#: src/api/reference.md:1021 src/api/reference.md:1130 msgid "`resolveTlsa` — instance _(class: `Resolver`)_" msgstr "`resolveTlsa` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1020 src/api/reference.md:1129 +#: src/api/reference.md:1022 src/api/reference.md:1131 msgid "`resolveTxt` — module" msgstr "`resolveTxt` — Modul" -#: src/api/reference.md:1021 src/api/reference.md:1130 +#: src/api/reference.md:1023 src/api/reference.md:1132 msgid "`resolveTxt` — instance _(class: `Resolver`)_" msgstr "`resolveTxt` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1022 src/api/reference.md:1131 -#: src/api/reference.md:1803 +#: src/api/reference.md:1024 src/api/reference.md:1133 +#: src/api/reference.md:1805 msgid "`reverse` — module" msgstr "`reverse` — Modul" -#: src/api/reference.md:1023 src/api/reference.md:1132 +#: src/api/reference.md:1025 src/api/reference.md:1134 msgid "`reverse` — instance _(class: `Resolver`)_" msgstr "`reverse` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1024 src/api/reference.md:1133 +#: src/api/reference.md:1026 src/api/reference.md:1135 msgid "`setDefaultResultOrder` — module" msgstr "`setDefaultResultOrder` — Modul" -#: src/api/reference.md:1025 src/api/reference.md:1134 +#: src/api/reference.md:1027 src/api/reference.md:1136 msgid "`setLocalAddress` — instance _(class: `Resolver`)_" msgstr "`setLocalAddress` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1026 src/api/reference.md:1135 +#: src/api/reference.md:1028 src/api/reference.md:1137 msgid "`setServers` — module" msgstr "`setServers` — Modul" -#: src/api/reference.md:1027 src/api/reference.md:1136 +#: src/api/reference.md:1029 src/api/reference.md:1138 msgid "`setServers` — instance _(class: `Resolver`)_" msgstr "`setServers` — Instanz _(Klasse: `Resolver`)_" -#: src/api/reference.md:1031 src/api/reference.md:1032 +#: src/api/reference.md:1033 src/api/reference.md:1034 msgid "`ADDRCONFIG`" msgstr "`ADDRCONFIG`" -#: src/api/reference.md:1033 src/api/reference.md:1034 -#: src/api/reference.md:1140 +#: src/api/reference.md:1035 src/api/reference.md:1036 +#: src/api/reference.md:1142 msgid "`ADDRGETNETWORKPARAMS`" msgstr "`ADDRGETNETWORKPARAMS`" -#: src/api/reference.md:1035 src/api/reference.md:1036 +#: src/api/reference.md:1037 src/api/reference.md:1038 msgid "`ALL`" msgstr "`ALL`" -#: src/api/reference.md:1037 src/api/reference.md:1038 -#: src/api/reference.md:1141 +#: src/api/reference.md:1039 src/api/reference.md:1040 +#: src/api/reference.md:1143 msgid "`BADFAMILY`" msgstr "`BADFAMILY`" -#: src/api/reference.md:1039 src/api/reference.md:1040 -#: src/api/reference.md:1142 +#: src/api/reference.md:1041 src/api/reference.md:1042 +#: src/api/reference.md:1144 msgid "`BADFLAGS`" msgstr "`BADFLAGS`" -#: src/api/reference.md:1041 src/api/reference.md:1042 -#: src/api/reference.md:1143 +#: src/api/reference.md:1043 src/api/reference.md:1044 +#: src/api/reference.md:1145 msgid "`BADHINTS`" msgstr "`BADHINTS`" -#: src/api/reference.md:1043 src/api/reference.md:1044 -#: src/api/reference.md:1144 +#: src/api/reference.md:1045 src/api/reference.md:1046 +#: src/api/reference.md:1146 msgid "`BADNAME`" msgstr "`BADNAME`" -#: src/api/reference.md:1045 src/api/reference.md:1046 -#: src/api/reference.md:1145 +#: src/api/reference.md:1047 src/api/reference.md:1048 +#: src/api/reference.md:1147 msgid "`BADQUERY`" msgstr "`BADQUERY`" -#: src/api/reference.md:1047 src/api/reference.md:1048 -#: src/api/reference.md:1146 +#: src/api/reference.md:1049 src/api/reference.md:1050 +#: src/api/reference.md:1148 msgid "`BADRESP`" msgstr "`BADRESP`" -#: src/api/reference.md:1049 src/api/reference.md:1050 -#: src/api/reference.md:1147 +#: src/api/reference.md:1051 src/api/reference.md:1052 +#: src/api/reference.md:1149 msgid "`BADSTR`" msgstr "`BADSTR`" -#: src/api/reference.md:1051 src/api/reference.md:1052 -#: src/api/reference.md:1148 +#: src/api/reference.md:1053 src/api/reference.md:1054 +#: src/api/reference.md:1150 msgid "`CANCELLED`" msgstr "`CANCELLED`" -#: src/api/reference.md:1053 src/api/reference.md:1054 -#: src/api/reference.md:1149 +#: src/api/reference.md:1055 src/api/reference.md:1056 +#: src/api/reference.md:1151 msgid "`CONNREFUSED`" msgstr "`CONNREFUSED`" -#: src/api/reference.md:1055 src/api/reference.md:1056 -#: src/api/reference.md:1150 +#: src/api/reference.md:1057 src/api/reference.md:1058 +#: src/api/reference.md:1152 msgid "`DESTRUCTION`" msgstr "`DESTRUCTION`" -#: src/api/reference.md:1057 src/api/reference.md:1058 -#: src/api/reference.md:1151 +#: src/api/reference.md:1059 src/api/reference.md:1060 +#: src/api/reference.md:1153 msgid "`EOF`" msgstr "`EOF`" -#: src/api/reference.md:1059 src/api/reference.md:1060 -#: src/api/reference.md:1152 +#: src/api/reference.md:1061 src/api/reference.md:1062 +#: src/api/reference.md:1154 msgid "`FILE`" msgstr "`FILE`" -#: src/api/reference.md:1061 src/api/reference.md:1062 -#: src/api/reference.md:1153 +#: src/api/reference.md:1063 src/api/reference.md:1064 +#: src/api/reference.md:1155 msgid "`FORMERR`" msgstr "`FORMERR`" -#: src/api/reference.md:1063 src/api/reference.md:1064 -#: src/api/reference.md:1154 +#: src/api/reference.md:1065 src/api/reference.md:1066 +#: src/api/reference.md:1156 msgid "`LOADIPHLPAPI`" msgstr "`LOADIPHLPAPI`" -#: src/api/reference.md:1065 src/api/reference.md:1066 -#: src/api/reference.md:1155 +#: src/api/reference.md:1067 src/api/reference.md:1068 +#: src/api/reference.md:1157 msgid "`NODATA`" msgstr "`NODATA`" -#: src/api/reference.md:1067 src/api/reference.md:1068 -#: src/api/reference.md:1156 +#: src/api/reference.md:1069 src/api/reference.md:1070 +#: src/api/reference.md:1158 msgid "`NOMEM`" msgstr "`NOMEM`" -#: src/api/reference.md:1069 src/api/reference.md:1070 -#: src/api/reference.md:1157 +#: src/api/reference.md:1071 src/api/reference.md:1072 +#: src/api/reference.md:1159 msgid "`NONAME`" msgstr "`NONAME`" -#: src/api/reference.md:1071 src/api/reference.md:1072 -#: src/api/reference.md:1158 +#: src/api/reference.md:1073 src/api/reference.md:1074 +#: src/api/reference.md:1160 msgid "`NOTFOUND`" msgstr "`NOTFOUND`" -#: src/api/reference.md:1073 src/api/reference.md:1074 -#: src/api/reference.md:1159 +#: src/api/reference.md:1075 src/api/reference.md:1076 +#: src/api/reference.md:1161 msgid "`NOTIMP`" msgstr "`NOTIMP`" -#: src/api/reference.md:1075 src/api/reference.md:1076 -#: src/api/reference.md:1160 +#: src/api/reference.md:1077 src/api/reference.md:1078 +#: src/api/reference.md:1162 msgid "`NOTINITIALIZED`" msgstr "`NOTINITIALIZED`" -#: src/api/reference.md:1077 src/api/reference.md:1078 -#: src/api/reference.md:1161 +#: src/api/reference.md:1079 src/api/reference.md:1080 +#: src/api/reference.md:1163 msgid "`REFUSED`" msgstr "`REFUSED`" -#: src/api/reference.md:1079 src/api/reference.md:1080 -#: src/api/reference.md:1162 +#: src/api/reference.md:1081 src/api/reference.md:1082 +#: src/api/reference.md:1164 msgid "`SERVFAIL`" msgstr "`SERVFAIL`" -#: src/api/reference.md:1081 src/api/reference.md:1082 -#: src/api/reference.md:1163 +#: src/api/reference.md:1083 src/api/reference.md:1084 +#: src/api/reference.md:1165 msgid "`TIMEOUT`" msgstr "`TIMEOUT`" -#: src/api/reference.md:1083 src/api/reference.md:1084 +#: src/api/reference.md:1085 src/api/reference.md:1086 msgid "`V4MAPPED`" msgstr "`V4MAPPED`" -#: src/api/reference.md:1086 src/api/reference.md:1396 -#: src/api/reference.md:3086 src/api/reference.md:3087 -#: src/api/reference.md:3276 +#: src/api/reference.md:1088 src/api/reference.md:1398 +#: src/api/reference.md:2865 src/api/reference.md:3095 +#: src/api/reference.md:3096 src/api/reference.md:3285 msgid "`promises`" msgstr "`promises`" -#: src/api/reference.md:1088 +#: src/api/reference.md:1090 msgid "`dns/promises`" msgstr "`dns/promises`" -#: src/api/reference.md:1166 src/api/reference.md:2783 +#: src/api/reference.md:1168 src/api/reference.md:2788 msgid "`domain`" msgstr "`domain`" -#: src/api/reference.md:1170 +#: src/api/reference.md:1172 msgid "`Domain`" msgstr "`Domain`" -#: src/api/reference.md:1174 +#: src/api/reference.md:1176 msgid "`Domain` — module" msgstr "`Domain` — Modul" -#: src/api/reference.md:1175 +#: src/api/reference.md:1177 msgid "`add` — instance" msgstr "`add` — Instanz" -#: src/api/reference.md:1176 src/api/reference.md:1217 -#: src/api/reference.md:3021 +#: src/api/reference.md:1178 src/api/reference.md:1219 +#: src/api/reference.md:3030 msgid "`addListener` — instance" msgstr "`addListener` — Instanz" -#: src/api/reference.md:1177 +#: src/api/reference.md:1179 msgid "`bind` — instance" msgstr "`bind` — Instanz" -#: src/api/reference.md:1178 src/api/reference.md:2255 +#: src/api/reference.md:1180 src/api/reference.md:2260 msgid "`create` — module" msgstr "`create` — Modul" -#: src/api/reference.md:1179 +#: src/api/reference.md:1181 msgid "`createDomain` — module" msgstr "`createDomain` — Modul" -#: src/api/reference.md:1180 src/api/reference.md:1221 -#: src/api/reference.md:3030 src/api/reference.md:3699 +#: src/api/reference.md:1182 src/api/reference.md:1223 +#: src/api/reference.md:3039 src/api/reference.md:3719 msgid "`emit` — instance" msgstr "`emit` — Instanz" -#: src/api/reference.md:1181 +#: src/api/reference.md:1183 msgid "`enter` — instance" msgstr "`enter` — Instanz" -#: src/api/reference.md:1183 +#: src/api/reference.md:1185 msgid "`intercept` — instance" msgstr "`intercept` — Instanz" -#: src/api/reference.md:1184 src/api/reference.md:1232 -#: src/api/reference.md:2849 src/api/reference.md:3046 -#: src/api/reference.md:3701 +#: src/api/reference.md:1186 src/api/reference.md:1234 +#: src/api/reference.md:2854 src/api/reference.md:3055 +#: src/api/reference.md:3721 msgid "`on` — instance" msgstr "`on` — Instanz" -#: src/api/reference.md:1185 +#: src/api/reference.md:1187 msgid "`remove` — instance" msgstr "`remove` — Instanz" -#: src/api/reference.md:1190 +#: src/api/reference.md:1192 msgid "`_stack`" msgstr "`_stack`" -#: src/api/reference.md:1191 +#: src/api/reference.md:1193 msgid "`active`" msgstr "`active`" -#: src/api/reference.md:1192 +#: src/api/reference.md:1194 msgid "`members`" msgstr "`members`" -#: src/api/reference.md:1198 +#: src/api/reference.md:1200 msgid "`createRandom` — module _(class: `Wallet`)_" msgstr "`createRandom` — Modul _(Klasse: `Wallet`)_" -#: src/api/reference.md:1199 +#: src/api/reference.md:1201 msgid "`formatEther` — module" msgstr "`formatEther` — Modul" -#: src/api/reference.md:1200 +#: src/api/reference.md:1202 msgid "`formatUnits` — module" msgstr "`formatUnits` — Modul" -#: src/api/reference.md:1201 +#: src/api/reference.md:1203 msgid "`getAddress` — module" msgstr "`getAddress` — Modul" -#: src/api/reference.md:1202 +#: src/api/reference.md:1204 msgid "`parseEther` — module" msgstr "`parseEther` — Modul" -#: src/api/reference.md:1203 +#: src/api/reference.md:1205 msgid "`parseUnits` — module" msgstr "`parseUnits` — Modul" -#: src/api/reference.md:1205 +#: src/api/reference.md:1207 msgid "`events`" msgstr "`events`" -#: src/api/reference.md:1209 +#: src/api/reference.md:1211 msgid "`EventEmitter`" msgstr "`EventEmitter`" -#: src/api/reference.md:1210 +#: src/api/reference.md:1212 msgid "`EventEmitterAsyncResource`" msgstr "`EventEmitterAsyncResource`" -#: src/api/reference.md:1214 +#: src/api/reference.md:1216 msgid "`EventEmitter` — module" msgstr "`EventEmitter` — Modul" -#: src/api/reference.md:1215 +#: src/api/reference.md:1217 msgid "`EventEmitterAsyncResource` — module" msgstr "`EventEmitterAsyncResource` — Modul" -#: src/api/reference.md:1216 +#: src/api/reference.md:1218 msgid "`addAbortListener` — module" msgstr "`addAbortListener` — Modul" -#: src/api/reference.md:1218 +#: src/api/reference.md:1220 msgid "`asyncId` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`asyncId` — Instanz _(Klasse: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1219 +#: src/api/reference.md:1221 msgid "`asyncResource` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`asyncResource` — Instanz _(Klasse: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1220 +#: src/api/reference.md:1222 msgid "`domain` — instance" msgstr "`domain` — Instanz" -#: src/api/reference.md:1222 +#: src/api/reference.md:1224 msgid "`emitDestroy` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`emitDestroy` — Instanz _(Klasse: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1223 src/api/reference.md:3033 +#: src/api/reference.md:1225 src/api/reference.md:3042 msgid "`eventNames` — instance" msgstr "`eventNames` — Instanz" -#: src/api/reference.md:1224 +#: src/api/reference.md:1226 msgid "`getEventListeners` — module" msgstr "`getEventListeners` — Modul" -#: src/api/reference.md:1225 src/api/reference.md:3036 +#: src/api/reference.md:1227 src/api/reference.md:3045 msgid "`getMaxListeners` — instance" msgstr "`getMaxListeners` — Instanz" -#: src/api/reference.md:1226 +#: src/api/reference.md:1228 msgid "`getMaxListeners` — module" msgstr "`getMaxListeners` — Modul" -#: src/api/reference.md:1227 +#: src/api/reference.md:1229 msgid "`init` — module" msgstr "`init` — Modul" -#: src/api/reference.md:1228 src/api/reference.md:3043 +#: src/api/reference.md:1230 src/api/reference.md:3052 msgid "`listenerCount` — instance" msgstr "`listenerCount` — Instanz" -#: src/api/reference.md:1229 +#: src/api/reference.md:1231 msgid "`listenerCount` — module" msgstr "`listenerCount` — Modul" -#: src/api/reference.md:1230 src/api/reference.md:3044 +#: src/api/reference.md:1232 src/api/reference.md:3053 msgid "`listeners` — instance" msgstr "`listeners` — Instanz" -#: src/api/reference.md:1231 src/api/reference.md:3045 +#: src/api/reference.md:1233 src/api/reference.md:3054 msgid "`off` — instance" msgstr "`off` — Instanz" -#: src/api/reference.md:1233 +#: src/api/reference.md:1235 msgid "`on` — module" msgstr "`on` — Modul" -#: src/api/reference.md:1234 src/api/reference.md:3047 +#: src/api/reference.md:1236 src/api/reference.md:3056 msgid "`once` — instance" msgstr "`once` — Instanz" -#: src/api/reference.md:1235 +#: src/api/reference.md:1237 msgid "`once` — module" msgstr "`once` — Modul" -#: src/api/reference.md:1236 src/api/reference.md:3051 +#: src/api/reference.md:1238 src/api/reference.md:3060 msgid "`prependListener` — instance" msgstr "`prependListener` — Instanz" -#: src/api/reference.md:1237 src/api/reference.md:3052 +#: src/api/reference.md:1239 src/api/reference.md:3061 msgid "`prependOnceListener` — instance" msgstr "`prependOnceListener` — Instanz" -#: src/api/reference.md:1238 src/api/reference.md:3054 +#: src/api/reference.md:1240 src/api/reference.md:3063 msgid "`rawListeners` — instance" msgstr "`rawListeners` — Instanz" -#: src/api/reference.md:1239 src/api/reference.md:3065 +#: src/api/reference.md:1241 src/api/reference.md:3074 msgid "`removeAllListeners` — instance" msgstr "`removeAllListeners` — Instanz" -#: src/api/reference.md:1240 src/api/reference.md:3066 +#: src/api/reference.md:1242 src/api/reference.md:3075 msgid "`removeListener` — instance" msgstr "`removeListener` — Instanz" -#: src/api/reference.md:1241 src/api/reference.md:3070 +#: src/api/reference.md:1243 src/api/reference.md:3079 msgid "`setMaxListeners` — instance" msgstr "`setMaxListeners` — Instanz" -#: src/api/reference.md:1242 +#: src/api/reference.md:1244 msgid "`setMaxListeners` — module" msgstr "`setMaxListeners` — Modul" -#: src/api/reference.md:1243 +#: src/api/reference.md:1245 msgid "`triggerAsyncId` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`triggerAsyncId` — Instanz _(Klasse: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1247 +#: src/api/reference.md:1249 msgid "`captureRejectionSymbol`" msgstr "`captureRejectionSymbol`" -#: src/api/reference.md:1248 +#: src/api/reference.md:1250 msgid "`captureRejections`" msgstr "`captureRejections`" -#: src/api/reference.md:1250 +#: src/api/reference.md:1252 msgid "`defaultMaxListeners`" msgstr "`defaultMaxListeners`" -#: src/api/reference.md:1251 +#: src/api/reference.md:1253 msgid "`errorMonitor`" msgstr "`errorMonitor`" -#: src/api/reference.md:1252 +#: src/api/reference.md:1254 msgid "`usingDomains`" msgstr "`usingDomains`" -#: src/api/reference.md:1254 +#: src/api/reference.md:1256 msgid "`fetch`" msgstr "`fetch`" -#: src/api/reference.md:1259 src/api/reference.md:1996 +#: src/api/reference.md:1261 src/api/reference.md:1998 msgid "`FormData`" msgstr "`FormData`" -#: src/api/reference.md:1260 src/api/reference.md:1997 +#: src/api/reference.md:1262 src/api/reference.md:1999 msgid "`Headers`" msgstr "`Headers`" -#: src/api/reference.md:1261 src/api/reference.md:1998 +#: src/api/reference.md:1263 src/api/reference.md:2000 msgid "`Request`" msgstr "`Request`" -#: src/api/reference.md:1262 src/api/reference.md:1999 +#: src/api/reference.md:1264 src/api/reference.md:2001 msgid "`Response`" msgstr "`Response`" -#: src/api/reference.md:1268 +#: src/api/reference.md:1270 msgid "`ffi`" msgstr "`ffi`" -#: src/api/reference.md:1273 +#: src/api/reference.md:1275 msgid "`getRawPointer` — module" msgstr "`getRawPointer` — Modul" -#: src/api/reference.md:1276 +#: src/api/reference.md:1278 msgid "`toString` — module" msgstr "`toString` — Modul" -#: src/api/reference.md:1282 +#: src/api/reference.md:1284 msgid "`fs`" msgstr "`fs`" -#: src/api/reference.md:1286 +#: src/api/reference.md:1288 msgid "`Dir`" msgstr "`Dir`" -#: src/api/reference.md:1287 +#: src/api/reference.md:1289 msgid "`Dirent`" msgstr "`Dirent`" -#: src/api/reference.md:1288 +#: src/api/reference.md:1290 msgid "`FileReadStream`" msgstr "`FileReadStream`" -#: src/api/reference.md:1289 +#: src/api/reference.md:1291 msgid "`FileWriteStream`" msgstr "`FileWriteStream`" -#: src/api/reference.md:1290 src/api/reference.md:3339 +#: src/api/reference.md:1292 src/api/reference.md:3359 msgid "`ReadStream`" msgstr "`ReadStream`" -#: src/api/reference.md:1291 +#: src/api/reference.md:1293 msgid "`Stats`" msgstr "`Stats`" -#: src/api/reference.md:1292 +#: src/api/reference.md:1294 msgid "`Utf8Stream`" msgstr "`Utf8Stream`" -#: src/api/reference.md:1293 src/api/reference.md:3340 +#: src/api/reference.md:1295 src/api/reference.md:3360 msgid "`WriteStream`" msgstr "`WriteStream`" -#: src/api/reference.md:1297 src/api/reference.md:1298 +#: src/api/reference.md:1299 src/api/reference.md:1300 msgid "`_toUnixTimestamp` — module" msgstr "`_toUnixTimestamp` — Modul" -#: src/api/reference.md:1299 src/api/reference.md:1402 +#: src/api/reference.md:1301 src/api/reference.md:1404 msgid "`access` — module" msgstr "`access` — Modul" -#: src/api/reference.md:1300 +#: src/api/reference.md:1302 msgid "`accessSync` — module" msgstr "`accessSync` — Modul" -#: src/api/reference.md:1301 src/api/reference.md:1403 +#: src/api/reference.md:1303 src/api/reference.md:1405 msgid "`appendFile` — module" msgstr "`appendFile` — Modul" -#: src/api/reference.md:1302 +#: src/api/reference.md:1304 msgid "`appendFileSync` — module" msgstr "`appendFileSync` — Modul" -#: src/api/reference.md:1303 src/api/reference.md:1404 +#: src/api/reference.md:1305 src/api/reference.md:1406 msgid "`chmod` — module" msgstr "`chmod` — Modul" -#: src/api/reference.md:1304 +#: src/api/reference.md:1306 msgid "`chmodSync` — module" msgstr "`chmodSync` — Modul" -#: src/api/reference.md:1305 src/api/reference.md:1405 +#: src/api/reference.md:1307 src/api/reference.md:1407 msgid "`chown` — module" msgstr "`chown` — Modul" -#: src/api/reference.md:1306 +#: src/api/reference.md:1308 msgid "`chownSync` — module" msgstr "`chownSync` — Modul" -#: src/api/reference.md:1307 src/api/reference.md:1737 +#: src/api/reference.md:1309 src/api/reference.md:1739 msgid "`close` — module" msgstr "`close` — Modul" -#: src/api/reference.md:1308 +#: src/api/reference.md:1310 msgid "`closeSync` — module" msgstr "`closeSync` — Modul" -#: src/api/reference.md:1309 src/api/reference.md:1406 +#: src/api/reference.md:1311 src/api/reference.md:1408 msgid "`copyFile` — module" msgstr "`copyFile` — Modul" -#: src/api/reference.md:1310 +#: src/api/reference.md:1312 msgid "`copyFileSync` — module" msgstr "`copyFileSync` — Modul" -#: src/api/reference.md:1311 src/api/reference.md:1407 +#: src/api/reference.md:1313 src/api/reference.md:1409 msgid "`cp` — module" msgstr "`cp` — Modul" -#: src/api/reference.md:1312 +#: src/api/reference.md:1314 msgid "`cpSync` — module" msgstr "`cpSync` — Modul" -#: src/api/reference.md:1313 +#: src/api/reference.md:1315 msgid "`createReadStream` — module" msgstr "`createReadStream` — Modul" -#: src/api/reference.md:1314 +#: src/api/reference.md:1316 msgid "`createWriteStream` — module" msgstr "`createWriteStream` — Modul" -#: src/api/reference.md:1315 +#: src/api/reference.md:1317 msgid "`exists` — module" msgstr "`exists` — Modul" -#: src/api/reference.md:1316 +#: src/api/reference.md:1318 msgid "`existsSync` — module" msgstr "`existsSync` — Modul" -#: src/api/reference.md:1317 +#: src/api/reference.md:1319 msgid "`fchmod` — module" msgstr "`fchmod` — Modul" -#: src/api/reference.md:1318 +#: src/api/reference.md:1320 msgid "`fchmodSync` — module" msgstr "`fchmodSync` — Modul" -#: src/api/reference.md:1319 +#: src/api/reference.md:1321 msgid "`fchown` — module" msgstr "`fchown` — Modul" -#: src/api/reference.md:1320 +#: src/api/reference.md:1322 msgid "`fchownSync` — module" msgstr "`fchownSync` — Modul" -#: src/api/reference.md:1321 +#: src/api/reference.md:1323 msgid "`fdatasync` — module" msgstr "`fdatasync` — Modul" -#: src/api/reference.md:1322 +#: src/api/reference.md:1324 msgid "`fdatasyncSync` — module" msgstr "`fdatasyncSync` — Modul" -#: src/api/reference.md:1323 +#: src/api/reference.md:1325 msgid "`fstat` — module" msgstr "`fstat` — Modul" -#: src/api/reference.md:1324 +#: src/api/reference.md:1326 msgid "`fstatSync` — module" msgstr "`fstatSync` — Modul" -#: src/api/reference.md:1325 +#: src/api/reference.md:1327 msgid "`fsync` — module" msgstr "`fsync` — Modul" -#: src/api/reference.md:1326 +#: src/api/reference.md:1328 msgid "`fsyncSync` — module" msgstr "`fsyncSync` — Modul" -#: src/api/reference.md:1327 +#: src/api/reference.md:1329 msgid "`ftruncate` — module" msgstr "`ftruncate` — Modul" -#: src/api/reference.md:1328 +#: src/api/reference.md:1330 msgid "`ftruncateSync` — module" msgstr "`ftruncateSync` — Modul" -#: src/api/reference.md:1329 +#: src/api/reference.md:1331 msgid "`futimes` — module" msgstr "`futimes` — Modul" -#: src/api/reference.md:1330 +#: src/api/reference.md:1332 msgid "`futimesSync` — module" msgstr "`futimesSync` — Modul" -#: src/api/reference.md:1331 src/api/reference.md:1408 +#: src/api/reference.md:1333 src/api/reference.md:1410 msgid "`glob` — module" msgstr "`glob` — Modul" -#: src/api/reference.md:1332 +#: src/api/reference.md:1334 msgid "`globSync` — module" msgstr "`globSync` — Modul" -#: src/api/reference.md:1333 src/api/reference.md:1409 +#: src/api/reference.md:1335 src/api/reference.md:1411 msgid "`lchmod` — module" msgstr "`lchmod` — Modul" -#: src/api/reference.md:1334 +#: src/api/reference.md:1336 msgid "`lchmodSync` — module" msgstr "`lchmodSync` — Modul" -#: src/api/reference.md:1335 src/api/reference.md:1410 +#: src/api/reference.md:1337 src/api/reference.md:1412 msgid "`lchown` — module" msgstr "`lchown` — Modul" -#: src/api/reference.md:1336 +#: src/api/reference.md:1338 msgid "`lchownSync` — module" msgstr "`lchownSync` — Modul" -#: src/api/reference.md:1337 src/api/reference.md:1411 +#: src/api/reference.md:1339 src/api/reference.md:1413 msgid "`link` — module" msgstr "`link` — Modul" -#: src/api/reference.md:1338 +#: src/api/reference.md:1340 msgid "`linkSync` — module" msgstr "`linkSync` — Modul" -#: src/api/reference.md:1339 src/api/reference.md:1412 +#: src/api/reference.md:1341 src/api/reference.md:1414 msgid "`lstat` — module" msgstr "`lstat` — Modul" -#: src/api/reference.md:1340 +#: src/api/reference.md:1342 msgid "`lstatSync` — module" msgstr "`lstatSync` — Modul" -#: src/api/reference.md:1341 src/api/reference.md:1413 +#: src/api/reference.md:1343 src/api/reference.md:1415 msgid "`lutimes` — module" msgstr "`lutimes` — Modul" -#: src/api/reference.md:1342 +#: src/api/reference.md:1344 msgid "`lutimesSync` — module" msgstr "`lutimesSync` — Modul" -#: src/api/reference.md:1343 src/api/reference.md:1414 +#: src/api/reference.md:1345 src/api/reference.md:1416 msgid "`mkdir` — module" msgstr "`mkdir` — Modul" -#: src/api/reference.md:1344 +#: src/api/reference.md:1346 msgid "`mkdirSync` — module" msgstr "`mkdirSync` — Modul" -#: src/api/reference.md:1345 src/api/reference.md:1415 +#: src/api/reference.md:1347 src/api/reference.md:1417 msgid "`mkdtemp` — module" msgstr "`mkdtemp` — Modul" -#: src/api/reference.md:1346 +#: src/api/reference.md:1348 msgid "`mkdtempDisposableSync` — module" msgstr "`mkdtempDisposableSync` — Modul" -#: src/api/reference.md:1347 +#: src/api/reference.md:1349 msgid "`mkdtempSync` — module" msgstr "`mkdtempSync` — Modul" -#: src/api/reference.md:1348 src/api/reference.md:1417 +#: src/api/reference.md:1350 src/api/reference.md:1419 msgid "`open` — module" msgstr "`open` — Modul" -#: src/api/reference.md:1349 +#: src/api/reference.md:1351 msgid "`openAsBlob` — module" msgstr "`openAsBlob` — Modul" -#: src/api/reference.md:1350 +#: src/api/reference.md:1352 msgid "`openSync` — module" msgstr "`openSync` — Modul" -#: src/api/reference.md:1351 src/api/reference.md:1418 +#: src/api/reference.md:1353 src/api/reference.md:1420 msgid "`opendir` — module" msgstr "`opendir` — Modul" -#: src/api/reference.md:1352 +#: src/api/reference.md:1354 msgid "`opendirSync` — module" msgstr "`opendirSync` — Modul" -#: src/api/reference.md:1353 +#: src/api/reference.md:1355 msgid "`read` — module" msgstr "`read` — Modul" -#: src/api/reference.md:1354 src/api/reference.md:1421 +#: src/api/reference.md:1356 src/api/reference.md:1423 msgid "`readFile` — module" msgstr "`readFile` — Modul" -#: src/api/reference.md:1355 +#: src/api/reference.md:1357 msgid "`readFileSync` — module" msgstr "`readFileSync` — Modul" -#: src/api/reference.md:1356 +#: src/api/reference.md:1358 msgid "`readSync` — module" msgstr "`readSync` — Modul" -#: src/api/reference.md:1357 src/api/reference.md:1422 +#: src/api/reference.md:1359 src/api/reference.md:1424 msgid "`readdir` — module" msgstr "`readdir` — Modul" -#: src/api/reference.md:1358 +#: src/api/reference.md:1360 msgid "`readdirSync` — module" msgstr "`readdirSync` — Modul" -#: src/api/reference.md:1359 src/api/reference.md:1423 +#: src/api/reference.md:1361 src/api/reference.md:1425 msgid "`readlink` — module" msgstr "`readlink` — Modul" -#: src/api/reference.md:1360 +#: src/api/reference.md:1362 msgid "`readlinkSync` — module" msgstr "`readlinkSync` — Modul" -#: src/api/reference.md:1361 +#: src/api/reference.md:1363 msgid "`readv` — module" msgstr "`readv` — Modul" -#: src/api/reference.md:1362 +#: src/api/reference.md:1364 msgid "`readvSync` — module" msgstr "`readvSync` — Modul" -#: src/api/reference.md:1363 src/api/reference.md:1424 +#: src/api/reference.md:1365 src/api/reference.md:1426 msgid "`realpath` — module" msgstr "`realpath` — Modul" -#: src/api/reference.md:1364 +#: src/api/reference.md:1366 msgid "`realpathSync` — module" msgstr "`realpathSync` — Modul" -#: src/api/reference.md:1365 src/api/reference.md:1425 +#: src/api/reference.md:1367 src/api/reference.md:1427 msgid "`rename` — module" msgstr "`rename` — Modul" -#: src/api/reference.md:1366 +#: src/api/reference.md:1368 msgid "`renameSync` — module" msgstr "`renameSync` — Modul" -#: src/api/reference.md:1367 src/api/reference.md:1426 +#: src/api/reference.md:1369 src/api/reference.md:1428 msgid "`rm` — module" msgstr "`rm` — Modul" -#: src/api/reference.md:1368 +#: src/api/reference.md:1370 msgid "`rmSync` — module" msgstr "`rmSync` — Modul" -#: src/api/reference.md:1369 src/api/reference.md:1427 +#: src/api/reference.md:1371 src/api/reference.md:1429 msgid "`rmdir` — module" msgstr "`rmdir` — Modul" -#: src/api/reference.md:1370 +#: src/api/reference.md:1372 msgid "`rmdirSync` — module" msgstr "`rmdirSync` — Modul" -#: src/api/reference.md:1371 src/api/reference.md:1428 +#: src/api/reference.md:1373 src/api/reference.md:1430 msgid "`stat` — module" msgstr "`stat` — Modul" -#: src/api/reference.md:1372 +#: src/api/reference.md:1374 msgid "`statSync` — module" msgstr "`statSync` — Modul" -#: src/api/reference.md:1373 src/api/reference.md:1429 +#: src/api/reference.md:1375 src/api/reference.md:1431 msgid "`statfs` — module" msgstr "`statfs` — Modul" -#: src/api/reference.md:1374 +#: src/api/reference.md:1376 msgid "`statfsSync` — module" msgstr "`statfsSync` — Modul" -#: src/api/reference.md:1375 src/api/reference.md:1430 +#: src/api/reference.md:1377 src/api/reference.md:1432 msgid "`symlink` — module" msgstr "`symlink` — Modul" -#: src/api/reference.md:1376 +#: src/api/reference.md:1378 msgid "`symlinkSync` — module" msgstr "`symlinkSync` — Modul" -#: src/api/reference.md:1377 src/api/reference.md:1431 +#: src/api/reference.md:1379 src/api/reference.md:1433 msgid "`truncate` — module" msgstr "`truncate` — Modul" -#: src/api/reference.md:1378 +#: src/api/reference.md:1380 msgid "`truncateSync` — module" msgstr "`truncateSync` — Modul" -#: src/api/reference.md:1379 src/api/reference.md:1432 +#: src/api/reference.md:1381 src/api/reference.md:1434 msgid "`unlink` — module" msgstr "`unlink` — Modul" -#: src/api/reference.md:1380 +#: src/api/reference.md:1382 msgid "`unlinkSync` — module" msgstr "`unlinkSync` — Modul" -#: src/api/reference.md:1381 +#: src/api/reference.md:1383 msgid "`unwatchFile` — module" msgstr "`unwatchFile` — Modul" -#: src/api/reference.md:1382 src/api/reference.md:1433 +#: src/api/reference.md:1384 src/api/reference.md:1435 msgid "`utimes` — module" msgstr "`utimes` — Modul" -#: src/api/reference.md:1383 +#: src/api/reference.md:1385 msgid "`utimesSync` — module" msgstr "`utimesSync` — Modul" -#: src/api/reference.md:1384 src/api/reference.md:1434 +#: src/api/reference.md:1386 src/api/reference.md:1436 msgid "`watch` — module" msgstr "`watch` — Modul" -#: src/api/reference.md:1385 +#: src/api/reference.md:1387 msgid "`watchFile` — module" msgstr "`watchFile` — Modul" -#: src/api/reference.md:1387 src/api/reference.md:1435 +#: src/api/reference.md:1389 src/api/reference.md:1437 msgid "`writeFile` — module" msgstr "`writeFile` — Modul" -#: src/api/reference.md:1388 +#: src/api/reference.md:1390 msgid "`writeFileSync` — module" msgstr "`writeFileSync` — Modul" -#: src/api/reference.md:1389 +#: src/api/reference.md:1391 msgid "`writeSync` — module" msgstr "`writeSync` — Modul" -#: src/api/reference.md:1390 +#: src/api/reference.md:1392 msgid "`writev` — module" msgstr "`writev` — Modul" -#: src/api/reference.md:1391 +#: src/api/reference.md:1393 msgid "`writevSync` — module" msgstr "`writevSync` — Modul" -#: src/api/reference.md:1398 +#: src/api/reference.md:1400 msgid "`fs/promises`" msgstr "`fs/promises`" -#: src/api/reference.md:1416 +#: src/api/reference.md:1418 msgid "`mkdtempDisposable` — module" msgstr "`mkdtempDisposable` — Modul" -#: src/api/reference.md:1419 +#: src/api/reference.md:1421 msgid "`pull` — instance _(class: `FileHandle`)_" msgstr "`pull` — Instanz _(Klasse: `FileHandle`)_" -#: src/api/reference.md:1420 +#: src/api/reference.md:1422 msgid "`pullSync` — instance _(class: `FileHandle`)_" msgstr "`pullSync` — Instanz _(Klasse: `FileHandle`)_" -#: src/api/reference.md:1436 +#: src/api/reference.md:1438 msgid "`writer` — instance _(class: `FileHandle`)_" msgstr "`writer` — Instanz _(Klasse: `FileHandle`)_" -#: src/api/reference.md:1443 +#: src/api/reference.md:1445 msgid "`http`" msgstr "`http`" -#: src/api/reference.md:1447 src/api/reference.md:1678 -#: src/api/reference.md:3380 +#: src/api/reference.md:1449 src/api/reference.md:1680 +#: src/api/reference.md:3400 msgid "`Agent`" msgstr "`Agent`" -#: src/api/reference.md:1448 +#: src/api/reference.md:1450 msgid "`ClientRequest`" msgstr "`ClientRequest`" -#: src/api/reference.md:1449 src/api/reference.md:1450 +#: src/api/reference.md:1451 src/api/reference.md:1452 msgid "`IncomingMessage`" msgstr "`IncomingMessage`" -#: src/api/reference.md:1451 src/api/reference.md:1452 +#: src/api/reference.md:1453 src/api/reference.md:1454 msgid "`OutgoingMessage`" msgstr "`OutgoingMessage`" -#: src/api/reference.md:1453 src/api/reference.md:1454 -#: src/api/reference.md:1679 src/api/reference.md:1680 -#: src/api/reference.md:1862 +#: src/api/reference.md:1455 src/api/reference.md:1456 +#: src/api/reference.md:1681 src/api/reference.md:1682 +#: src/api/reference.md:1864 msgid "`Server`" msgstr "`Server`" -#: src/api/reference.md:1455 src/api/reference.md:1456 +#: src/api/reference.md:1457 src/api/reference.md:1458 msgid "`ServerResponse`" msgstr "`ServerResponse`" -#: src/api/reference.md:1457 src/api/reference.md:3686 +#: src/api/reference.md:1459 src/api/reference.md:3706 msgid "`WebSocket`" msgstr "`WebSocket`" -#: src/api/reference.md:1461 src/api/reference.md:1684 -#: src/api/reference.md:3385 +#: src/api/reference.md:1463 src/api/reference.md:1686 +#: src/api/reference.md:3405 msgid "`Agent` — module" msgstr "`Agent` — Modul" -#: src/api/reference.md:1462 src/api/reference.md:1685 -#: src/api/reference.md:1870 src/api/reference.md:3299 -#: src/api/reference.md:3691 +#: src/api/reference.md:1464 src/api/reference.md:1687 +#: src/api/reference.md:1872 src/api/reference.md:3308 +#: src/api/reference.md:3711 msgid "`Server` — module" msgstr "`Server` — Modul" -#: src/api/reference.md:1463 +#: src/api/reference.md:1465 msgid "`__get_aborted` — instance _(class: `ClientRequest`)_" msgstr "`__get_aborted` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1464 +#: src/api/reference.md:1466 msgid "`__get_aborted` — instance _(class: `IncomingMessage`)_" msgstr "`__get_aborted` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1465 +#: src/api/reference.md:1467 msgid "`__get_complete` — instance _(class: `IncomingMessage`)_" msgstr "`__get_complete` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1466 +#: src/api/reference.md:1468 msgid "`__get_connection` — instance _(class: `IncomingMessage`)_" msgstr "`__get_connection` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1467 +#: src/api/reference.md:1469 msgid "`__get_connection` — instance _(class: `ClientRequest`)_" msgstr "`__get_connection` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1468 +#: src/api/reference.md:1470 msgid "`__get_createConnection` — instance _(class: `Agent`)_" msgstr "`__get_createConnection` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1469 +#: src/api/reference.md:1471 msgid "`__get_createSocket` — instance _(class: `Agent`)_" msgstr "`__get_createSocket` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1470 +#: src/api/reference.md:1472 msgid "`__get_defaultPort` — instance _(class: `Agent`)_" msgstr "`__get_defaultPort` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1471 +#: src/api/reference.md:1473 msgid "`__get_destroyed` — instance _(class: `Agent`)_" msgstr "`__get_destroyed` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1472 +#: src/api/reference.md:1474 msgid "`__get_destroyed` — instance _(class: `ClientRequest`)_" msgstr "`__get_destroyed` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1473 +#: src/api/reference.md:1475 msgid "`__get_destroyed` — instance _(class: `IncomingMessage`)_" msgstr "`__get_destroyed` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1474 +#: src/api/reference.md:1476 msgid "`__get_finished` — instance _(class: `ClientRequest`)_" msgstr "`__get_finished` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1475 +#: src/api/reference.md:1477 msgid "`__get_freeSockets` — instance _(class: `Agent`)_" msgstr "`__get_freeSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1476 +#: src/api/reference.md:1478 msgid "`__get_headers` — instance _(class: `IncomingMessage`)_" msgstr "`__get_headers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1477 +#: src/api/reference.md:1479 msgid "`__get_headersSent` — instance _(class: `ServerResponse`)_" msgstr "`__get_headersSent` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1478 +#: src/api/reference.md:1480 msgid "`__get_headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_headersTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1479 +#: src/api/reference.md:1481 msgid "`__get_host` — instance _(class: `ClientRequest`)_" msgstr "`__get_host` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1480 +#: src/api/reference.md:1482 msgid "`__get_httpVersion` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersion` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1481 +#: src/api/reference.md:1483 msgid "`__get_httpVersionMajor` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersionMajor` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1482 +#: src/api/reference.md:1484 msgid "`__get_httpVersionMinor` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersionMinor` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1483 +#: src/api/reference.md:1485 msgid "`__get_keepAlive` — instance _(class: `Agent`)_" msgstr "`__get_keepAlive` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1484 +#: src/api/reference.md:1486 msgid "`__get_keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`__get_keepAliveMsecs` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1485 +#: src/api/reference.md:1487 msgid "`__get_keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_keepAliveTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1486 +#: src/api/reference.md:1488 msgid "`__get_keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`__get_keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1487 +#: src/api/reference.md:1489 msgid "`__get_listening` — instance _(class: `HttpServer`)_" msgstr "`__get_listening` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1488 +#: src/api/reference.md:1490 msgid "`__get_maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxFreeSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1489 +#: src/api/reference.md:1491 msgid "`__get_maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`__get_maxHeadersCount` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1490 +#: src/api/reference.md:1492 msgid "`__get_maxHeadersCount` — instance _(class: `ClientRequest`)_" msgstr "`__get_maxHeadersCount` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1491 +#: src/api/reference.md:1493 msgid "`__get_maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`__get_maxRequestsPerSocket` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1492 +#: src/api/reference.md:1494 msgid "`__get_maxSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1493 +#: src/api/reference.md:1495 msgid "`__get_maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxTotalSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1494 +#: src/api/reference.md:1496 msgid "`__get_method` — instance _(class: `ClientRequest`)_" msgstr "`__get_method` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1495 +#: src/api/reference.md:1497 msgid "`__get_method` — instance _(class: `IncomingMessage`)_" msgstr "`__get_method` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1496 +#: src/api/reference.md:1498 msgid "`__get_path` — instance _(class: `ClientRequest`)_" msgstr "`__get_path` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1497 +#: src/api/reference.md:1499 msgid "`__get_protocol` — instance _(class: `Agent`)_" msgstr "`__get_protocol` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1498 +#: src/api/reference.md:1500 msgid "`__get_protocol` — instance _(class: `ClientRequest`)_" msgstr "`__get_protocol` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1499 +#: src/api/reference.md:1501 #, fuzzy msgid "`__get_rawHeaders` — instance _(class: `IncomingMessage`)_" msgstr "`__get_headers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1500 +#: src/api/reference.md:1502 msgid "`__get_req` — instance _(class: `IncomingMessage`)_" msgstr "`__get_req` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1501 +#: src/api/reference.md:1503 msgid "`__get_requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_requestTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1502 +#: src/api/reference.md:1504 msgid "`__get_requests` — instance _(class: `Agent`)_" msgstr "`__get_requests` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1503 +#: src/api/reference.md:1505 msgid "`__get_reusedSocket` — instance _(class: `ClientRequest`)_" msgstr "`__get_reusedSocket` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1504 +#: src/api/reference.md:1506 msgid "`__get_socket` — instance _(class: `IncomingMessage`)_" msgstr "`__get_socket` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1505 +#: src/api/reference.md:1507 msgid "`__get_socket` — instance _(class: `ClientRequest`)_" msgstr "`__get_socket` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1506 +#: src/api/reference.md:1508 msgid "`__get_sockets` — instance _(class: `Agent`)_" msgstr "`__get_sockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1507 +#: src/api/reference.md:1509 msgid "`__get_statusCode` — instance _(class: `IncomingMessage`)_" msgstr "`__get_statusCode` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1508 +#: src/api/reference.md:1510 msgid "`__get_statusCode` — instance _(class: `ServerResponse`)_" msgstr "`__get_statusCode` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1509 +#: src/api/reference.md:1511 msgid "`__get_statusMessage` — instance _(class: `IncomingMessage`)_" msgstr "`__get_statusMessage` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1510 +#: src/api/reference.md:1512 msgid "`__get_timeout` — instance _(class: `HttpServer`)_" msgstr "`__get_timeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1511 +#: src/api/reference.md:1513 msgid "`__get_trailers` — instance _(class: `IncomingMessage`)_" msgstr "`__get_trailers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1512 +#: src/api/reference.md:1514 msgid "`__get_url` — instance _(class: `IncomingMessage`)_" msgstr "`__get_url` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1513 +#: src/api/reference.md:1515 msgid "`__get_writableEnded` — instance _(class: `ClientRequest`)_" msgstr "`__get_writableEnded` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1514 +#: src/api/reference.md:1516 msgid "`__get_writableEnded` — instance _(class: `ServerResponse`)_" msgstr "`__get_writableEnded` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1515 +#: src/api/reference.md:1517 msgid "`__get_writableFinished` — instance _(class: `ClientRequest`)_" msgstr "`__get_writableFinished` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1516 +#: src/api/reference.md:1518 msgid "`__get_writableFinished` — instance _(class: `ServerResponse`)_" msgstr "`__get_writableFinished` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1517 +#: src/api/reference.md:1519 msgid "`__set_createConnection` — instance _(class: `Agent`)_" msgstr "`__set_createConnection` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1518 +#: src/api/reference.md:1520 msgid "`__set_createSocket` — instance _(class: `Agent`)_" msgstr "`__set_createSocket` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1519 +#: src/api/reference.md:1521 msgid "`__set_headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_headersTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1520 +#: src/api/reference.md:1522 msgid "`__set_keepAlive` — instance _(class: `Agent`)_" msgstr "`__set_keepAlive` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1521 +#: src/api/reference.md:1523 msgid "`__set_keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`__set_keepAliveMsecs` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1522 +#: src/api/reference.md:1524 msgid "`__set_keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_keepAliveTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1523 +#: src/api/reference.md:1525 msgid "`__set_keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`__set_keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1524 +#: src/api/reference.md:1526 msgid "`__set_maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxFreeSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1525 +#: src/api/reference.md:1527 msgid "`__set_maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`__set_maxHeadersCount` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1526 +#: src/api/reference.md:1528 msgid "`__set_maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`__set_maxRequestsPerSocket` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1527 +#: src/api/reference.md:1529 msgid "`__set_maxSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1528 +#: src/api/reference.md:1530 msgid "`__set_maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxTotalSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1529 +#: src/api/reference.md:1531 msgid "`__set_protocol` — instance _(class: `Agent`)_" msgstr "`__set_protocol` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1530 +#: src/api/reference.md:1532 msgid "`__set_requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_requestTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1531 +#: src/api/reference.md:1533 msgid "`__set_sendDate` — instance _(class: `ServerResponse`)_" msgstr "`__set_sendDate` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1532 +#: src/api/reference.md:1534 msgid "`__set_statusCode` — instance _(class: `ServerResponse`)_" msgstr "`__set_statusCode` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1533 +#: src/api/reference.md:1535 msgid "`__set_statusMessage` — instance _(class: `ServerResponse`)_" msgstr "`__set_statusMessage` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1534 +#: src/api/reference.md:1536 msgid "`__set_strictContentLength` — instance _(class: `ServerResponse`)_" msgstr "`__set_strictContentLength` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1535 +#: src/api/reference.md:1537 msgid "`__set_timeout` — instance _(class: `HttpServer`)_" msgstr "`__set_timeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1536 +#: src/api/reference.md:1538 msgid "`_connectionListener` — module" msgstr "`_connectionListener` — Modul" -#: src/api/reference.md:1537 +#: src/api/reference.md:1539 msgid "`abort` — instance _(class: `ClientRequest`)_" msgstr "`abort` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1538 +#: src/api/reference.md:1540 msgid "`addListener` — instance _(class: `HttpServer`)_" msgstr "`addListener` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1539 +#: src/api/reference.md:1541 msgid "`addListener` — instance _(class: `IncomingMessage`)_" msgstr "`addListener` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1540 +#: src/api/reference.md:1542 msgid "`addListener` — instance _(class: `ServerResponse`)_" msgstr "`addListener` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1541 +#: src/api/reference.md:1543 msgid "`addTrailers` — instance _(class: `ServerResponse`)_" msgstr "`addTrailers` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1542 +#: src/api/reference.md:1544 msgid "`address` — instance _(class: `HttpServer`)_" msgstr "`address` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1543 +#: src/api/reference.md:1545 msgid "`appendHeader` — instance _(class: `ServerResponse`)_" msgstr "`appendHeader` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1544 src/api/reference.md:3388 +#: src/api/reference.md:1546 src/api/reference.md:3408 msgid "`close` — instance _(class: `Agent`)_" msgstr "`close` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1545 +#: src/api/reference.md:1547 msgid "`close` — instance _(class: `HttpServer`)_" msgstr "`close` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1546 +#: src/api/reference.md:1548 msgid "`closeAllConnections` — instance _(class: `HttpServer`)_" msgstr "`closeAllConnections` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1547 +#: src/api/reference.md:1549 msgid "`closeIdleConnections` — instance _(class: `HttpServer`)_" msgstr "`closeIdleConnections` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1548 +#: src/api/reference.md:1550 #, fuzzy msgid "`complete` — instance _(class: `IncomingMessage`)_" msgstr "`__get_complete` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1549 +#: src/api/reference.md:1551 msgid "`connection` — instance _(class: `IncomingMessage`)_" msgstr "`connection` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1550 +#: src/api/reference.md:1552 msgid "`cork` — instance _(class: `ClientRequest`)_" msgstr "`cork` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1551 +#: src/api/reference.md:1553 msgid "`cork` — instance _(class: `ServerResponse`)_" msgstr "`cork` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1552 src/api/reference.md:1553 -#: src/api/reference.md:1662 src/api/reference.md:1706 -#: src/api/reference.md:1707 src/api/reference.md:1899 -#: src/api/reference.md:3308 +#: src/api/reference.md:1554 src/api/reference.md:1555 +#: src/api/reference.md:1664 src/api/reference.md:1708 +#: src/api/reference.md:1709 src/api/reference.md:1901 +#: src/api/reference.md:3317 msgid "`createServer` — module" msgstr "`createServer` — Modul" -#: src/api/reference.md:1554 +#: src/api/reference.md:1556 msgid "`defaultPort` — instance _(class: `Agent`)_" msgstr "`defaultPort` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1555 src/api/reference.md:3390 +#: src/api/reference.md:1557 src/api/reference.md:3410 msgid "`destroy` — instance _(class: `Agent`)_" msgstr "`destroy` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1556 +#: src/api/reference.md:1558 msgid "`destroy` — instance _(class: `IncomingMessage`)_" msgstr "`destroy` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1557 +#: src/api/reference.md:1559 msgid "`destroy` — instance _(class: `ClientRequest`)_" msgstr "`destroy` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1558 +#: src/api/reference.md:1560 msgid "`destroyed` — instance _(class: `Agent`)_" msgstr "`destroyed` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1559 +#: src/api/reference.md:1561 msgid "`end` — instance _(class: `ServerResponse`)_" msgstr "`end` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1560 +#: src/api/reference.md:1562 msgid "`flushHeaders` — instance _(class: `ClientRequest`)_" msgstr "`flushHeaders` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1561 +#: src/api/reference.md:1563 msgid "`flushHeaders` — instance _(class: `ServerResponse`)_" msgstr "`flushHeaders` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1562 +#: src/api/reference.md:1564 msgid "`freeSockets` — instance _(class: `Agent`)_" msgstr "`freeSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1563 src/api/reference.md:1708 +#: src/api/reference.md:1565 src/api/reference.md:1710 msgid "`get` — module" msgstr "`get` — Modul" -#: src/api/reference.md:1564 +#: src/api/reference.md:1566 msgid "`getHeader` — instance _(class: `ClientRequest`)_" msgstr "`getHeader` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1565 +#: src/api/reference.md:1567 msgid "`getHeader` — instance _(class: `ServerResponse`)_" msgstr "`getHeader` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1566 +#: src/api/reference.md:1568 msgid "`getHeaderNames` — instance _(class: `ClientRequest`)_" msgstr "`getHeaderNames` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1567 +#: src/api/reference.md:1569 msgid "`getHeaderNames` — instance _(class: `ServerResponse`)_" msgstr "`getHeaderNames` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1568 +#: src/api/reference.md:1570 msgid "`getHeaders` — instance _(class: `ClientRequest`)_" msgstr "`getHeaders` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1569 +#: src/api/reference.md:1571 msgid "`getHeaders` — instance _(class: `ServerResponse`)_" msgstr "`getHeaders` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1570 +#: src/api/reference.md:1572 msgid "`getName` — instance _(class: `Agent`)_" msgstr "`getName` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1571 +#: src/api/reference.md:1573 msgid "`getRawHeaderNames` — instance _(class: `ClientRequest`)_" msgstr "`getRawHeaderNames` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1572 +#: src/api/reference.md:1574 msgid "`getStatus` — instance _(class: `ServerResponse`)_" msgstr "`getStatus` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1573 +#: src/api/reference.md:1575 msgid "`hasHeader` — instance _(class: `ClientRequest`)_" msgstr "`hasHeader` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1574 +#: src/api/reference.md:1576 msgid "`hasHeader` — instance _(class: `ServerResponse`)_" msgstr "`hasHeader` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1575 +#: src/api/reference.md:1577 msgid "`headers` — instance _(class: `IncomingMessage`)_" msgstr "`headers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1576 +#: src/api/reference.md:1578 msgid "`headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`headersTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1577 +#: src/api/reference.md:1579 msgid "`httpVersion` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1578 +#: src/api/reference.md:1580 #, fuzzy msgid "`httpVersionMajor` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1579 +#: src/api/reference.md:1581 #, fuzzy msgid "`httpVersionMinor` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1580 +#: src/api/reference.md:1582 msgid "`keepAlive` — instance _(class: `Agent`)_" msgstr "`keepAlive` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1581 +#: src/api/reference.md:1583 msgid "`keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`keepAliveMsecs` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1582 +#: src/api/reference.md:1584 msgid "`keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`keepAliveTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1583 +#: src/api/reference.md:1585 msgid "`keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1584 +#: src/api/reference.md:1586 msgid "" "`keepSocketAlive` — instance _(class: `Agent`)_ ⚠ **stub** — reqwest owns " "the keep-alive pool; per-socket hooks are no-ops, warns once (#4917)" @@ -40592,113 +40328,113 @@ msgstr "" "besitzt den keep-alive pool; per-socket Haken sind keine opps, warnt einmal " "(#4917)" -#: src/api/reference.md:1585 +#: src/api/reference.md:1587 msgid "`listen` — instance _(class: `HttpServer`)_" msgstr "`listen` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1586 +#: src/api/reference.md:1588 msgid "`listenerCount` — instance _(class: `ClientRequest`)_" msgstr "`listenerCount` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1587 +#: src/api/reference.md:1589 msgid "`listening` — instance _(class: `HttpServer`)_" msgstr "`listening` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1588 +#: src/api/reference.md:1590 msgid "`maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`maxFreeSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1589 +#: src/api/reference.md:1591 msgid "`maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`maxHeadersCount` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1590 +#: src/api/reference.md:1592 msgid "`maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`maxRequestsPerSocket` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1591 +#: src/api/reference.md:1593 msgid "`maxSockets` — instance _(class: `Agent`)_" msgstr "`maxSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1592 +#: src/api/reference.md:1594 msgid "`maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`maxTotalSockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1593 +#: src/api/reference.md:1595 msgid "`method` — instance _(class: `IncomingMessage`)_" msgstr "`method` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1594 +#: src/api/reference.md:1596 msgid "`on` — instance _(class: `HttpServer`)_" msgstr "`on` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1595 +#: src/api/reference.md:1597 msgid "`on` — instance _(class: `IncomingMessage`)_" msgstr "`on` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1596 +#: src/api/reference.md:1598 msgid "`on` — instance _(class: `ServerResponse`)_" msgstr "`on` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1597 +#: src/api/reference.md:1599 msgid "`once` — instance _(class: `IncomingMessage`)_" msgstr "`once` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1598 +#: src/api/reference.md:1600 msgid "`once` — instance _(class: `ClientRequest`)_" msgstr "`once` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1599 +#: src/api/reference.md:1601 msgid "`pause` — instance _(class: `IncomingMessage`)_" msgstr "`pause` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1600 +#: src/api/reference.md:1602 msgid "`protocol` — instance _(class: `Agent`)_" msgstr "`protocol` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1601 +#: src/api/reference.md:1603 #, fuzzy msgid "`rawHeaders` — instance _(class: `IncomingMessage`)_" msgstr "`headers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1602 +#: src/api/reference.md:1604 msgid "`read` — instance _(class: `IncomingMessage`)_" msgstr "`read` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1603 +#: src/api/reference.md:1605 msgid "`ref` — instance _(class: `HttpServer`)_" msgstr "`ref` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1604 +#: src/api/reference.md:1606 msgid "`removeHeader` — instance _(class: `ClientRequest`)_" msgstr "`removeHeader` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1605 +#: src/api/reference.md:1607 msgid "`removeHeader` — instance _(class: `ServerResponse`)_" msgstr "`removeHeader` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1606 +#: src/api/reference.md:1608 msgid "`req` — instance _(class: `IncomingMessage`)_" msgstr "`req` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1607 src/api/reference.md:1718 -#: src/api/reference.md:3393 +#: src/api/reference.md:1609 src/api/reference.md:1720 +#: src/api/reference.md:3413 msgid "`request` — module" msgstr "`request` — Modul" -#: src/api/reference.md:1608 +#: src/api/reference.md:1610 msgid "`requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`requestTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1609 +#: src/api/reference.md:1611 msgid "`requests` — instance _(class: `Agent`)_" msgstr "`requests` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1610 +#: src/api/reference.md:1612 msgid "`resume` — instance _(class: `IncomingMessage`)_" msgstr "`resume` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1611 +#: src/api/reference.md:1613 msgid "" "`reuseSocket` — instance _(class: `Agent`)_ ⚠ **stub** — reqwest owns the " "keep-alive pool; per-socket hooks are no-ops, warns once (#4917)" @@ -40707,369 +40443,369 @@ msgstr "" "besitzt den keep-alive pool; per-socket Haken sind keine opps, warnt einmal " "(#4917)" -#: src/api/reference.md:1612 +#: src/api/reference.md:1614 msgid "`setEncoding` — instance _(class: `IncomingMessage`)_" msgstr "`setEncoding` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1613 +#: src/api/reference.md:1615 msgid "`setGlobalProxyFromEnv` — module" msgstr "`setGlobalProxyFromEnv` — Modul" -#: src/api/reference.md:1614 +#: src/api/reference.md:1616 msgid "`setHeader` — instance _(class: `ClientRequest`)_" msgstr "`setHeader` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1615 +#: src/api/reference.md:1617 msgid "`setHeader` — instance _(class: `ServerResponse`)_" msgstr "`setHeader` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1616 +#: src/api/reference.md:1618 msgid "`setHeaders` — instance _(class: `ServerResponse`)_" msgstr "`setHeaders` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1617 +#: src/api/reference.md:1619 msgid "`setMaxIdleHTTPParsers` — module" msgstr "`setMaxIdleHTTPParsers` — Modul" -#: src/api/reference.md:1618 +#: src/api/reference.md:1620 msgid "`setNoDelay` — instance _(class: `ClientRequest`)_" msgstr "`setNoDelay` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1619 +#: src/api/reference.md:1621 msgid "`setSocketKeepAlive` — instance _(class: `ClientRequest`)_" msgstr "`setSocketKeepAlive` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1620 +#: src/api/reference.md:1622 msgid "`setStatus` — instance _(class: `ServerResponse`)_" msgstr "`setStatus` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1621 +#: src/api/reference.md:1623 msgid "`setTimeout` — instance _(class: `HttpServer`)_" msgstr "`setTimeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1622 +#: src/api/reference.md:1624 msgid "`setTimeout` — instance _(class: `IncomingMessage`)_" msgstr "`setTimeout` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1623 +#: src/api/reference.md:1625 msgid "`setTimeout` — instance _(class: `ClientRequest`)_" msgstr "`setTimeout` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1624 +#: src/api/reference.md:1626 msgid "`setTimeout` — instance _(class: `ServerResponse`)_" msgstr "`setTimeout` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1625 +#: src/api/reference.md:1627 msgid "`socket` — instance _(class: `IncomingMessage`)_" msgstr "`socket` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1626 +#: src/api/reference.md:1628 msgid "`sockets` — instance _(class: `Agent`)_" msgstr "`sockets` — Instanz _(Klasse: `Agent`)_" -#: src/api/reference.md:1627 +#: src/api/reference.md:1629 msgid "`statusCode` — instance _(class: `IncomingMessage`)_" msgstr "`statusCode` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1628 +#: src/api/reference.md:1630 msgid "`statusMessage` — instance _(class: `IncomingMessage`)_" msgstr "`statusMessage` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1629 +#: src/api/reference.md:1631 msgid "`timeout` — instance _(class: `HttpServer`)_" msgstr "`timeout` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1630 +#: src/api/reference.md:1632 msgid "`trailers` — instance _(class: `IncomingMessage`)_" msgstr "`trailers` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1631 +#: src/api/reference.md:1633 msgid "`uncork` — instance _(class: `ClientRequest`)_" msgstr "`uncork` — Instanz _(Klasse: `ClientRequest`)_" -#: src/api/reference.md:1632 +#: src/api/reference.md:1634 msgid "`uncork` — instance _(class: `ServerResponse`)_" msgstr "`uncork` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1633 +#: src/api/reference.md:1635 msgid "`unref` — instance _(class: `HttpServer`)_" msgstr "`unref` — Instanz _(Klasse: `HttpServer`)_" -#: src/api/reference.md:1634 +#: src/api/reference.md:1636 msgid "`url` — instance _(class: `IncomingMessage`)_" msgstr "`url` — Instanz _(Klasse: `IncomingMessage`)_" -#: src/api/reference.md:1635 +#: src/api/reference.md:1637 msgid "`validateHeaderName` — module" msgstr "`validateHeaderName` — Modul" -#: src/api/reference.md:1636 +#: src/api/reference.md:1638 msgid "`validateHeaderValue` — module" msgstr "`validateHeaderValue` — Modul" -#: src/api/reference.md:1637 +#: src/api/reference.md:1639 msgid "`write` — instance _(class: `ServerResponse`)_" msgstr "`write` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1638 +#: src/api/reference.md:1640 msgid "`writeContinue` — instance _(class: `ServerResponse`)_" msgstr "`writeContinue` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1639 +#: src/api/reference.md:1641 msgid "`writeEarlyHints` — instance _(class: `ServerResponse`)_" msgstr "`writeEarlyHints` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1640 +#: src/api/reference.md:1642 msgid "`writeHead` — instance _(class: `ServerResponse`)_" msgstr "`writeHead` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1641 +#: src/api/reference.md:1643 msgid "`writeProcessing` — instance _(class: `ServerResponse`)_" msgstr "`writeProcessing` — Instanz _(Klasse: `ServerResponse`)_" -#: src/api/reference.md:1645 +#: src/api/reference.md:1647 msgid "`METHODS`" msgstr "`METHODS`" -#: src/api/reference.md:1646 +#: src/api/reference.md:1648 msgid "`STATUS_CODES`" msgstr "`STATUS_CODES`" -#: src/api/reference.md:1647 src/api/reference.md:1726 +#: src/api/reference.md:1649 src/api/reference.md:1728 msgid "`globalAgent`" msgstr "`globalAgent`" -#: src/api/reference.md:1648 +#: src/api/reference.md:1650 msgid "`kConnectionsCheckingInterval`" msgstr "`kConnectionsCheckingInterval`" -#: src/api/reference.md:1649 +#: src/api/reference.md:1651 msgid "`maxHeaderSize`" msgstr "`maxHeaderSize`" -#: src/api/reference.md:1651 +#: src/api/reference.md:1653 msgid "`http2`" msgstr "`http2`" -#: src/api/reference.md:1655 +#: src/api/reference.md:1657 msgid "`Http2ServerRequest`" msgstr "`Http2ServerRequest`" -#: src/api/reference.md:1656 +#: src/api/reference.md:1658 msgid "`Http2ServerResponse`" msgstr "`Http2ServerResponse`" -#: src/api/reference.md:1661 +#: src/api/reference.md:1663 msgid "`createSecureServer` — module" msgstr "`createSecureServer` — Modul" -#: src/api/reference.md:1663 +#: src/api/reference.md:1665 msgid "`getDefaultSettings` — module" msgstr "`getDefaultSettings` — Modul" -#: src/api/reference.md:1664 +#: src/api/reference.md:1666 msgid "`getPackedSettings` — module" msgstr "`getPackedSettings` — Modul" -#: src/api/reference.md:1665 +#: src/api/reference.md:1667 msgid "`getUnpackedSettings` — module" msgstr "`getUnpackedSettings` — Modul" -#: src/api/reference.md:1666 +#: src/api/reference.md:1668 msgid "`performServerHandshake` — module" msgstr "`performServerHandshake` — Modul" -#: src/api/reference.md:1672 +#: src/api/reference.md:1674 msgid "`sensitiveHeaders`" msgstr "`sensitiveHeaders`" -#: src/api/reference.md:1674 +#: src/api/reference.md:1676 msgid "`https`" msgstr "`https`" -#: src/api/reference.md:1686 +#: src/api/reference.md:1688 msgid "`__get_headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_headersTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1687 +#: src/api/reference.md:1689 msgid "`__get_keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_keepAliveTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1688 +#: src/api/reference.md:1690 msgid "`__get_keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`__get_keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1689 +#: src/api/reference.md:1691 msgid "`__get_listening` — instance _(class: `HttpsServer`)_" msgstr "`__get_listening` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1690 +#: src/api/reference.md:1692 msgid "`__get_maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`__get_maxHeadersCount` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1691 +#: src/api/reference.md:1693 msgid "`__get_maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`__get_maxRequestsPerSocket` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1692 +#: src/api/reference.md:1694 msgid "`__get_requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_requestTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1693 +#: src/api/reference.md:1695 msgid "`__get_timeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_timeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1694 +#: src/api/reference.md:1696 msgid "`__set_headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_headersTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1695 +#: src/api/reference.md:1697 msgid "`__set_keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_keepAliveTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1696 +#: src/api/reference.md:1698 msgid "`__set_keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`__set_keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1697 +#: src/api/reference.md:1699 msgid "`__set_maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`__set_maxHeadersCount` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1698 +#: src/api/reference.md:1700 msgid "`__set_maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`__set_maxRequestsPerSocket` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1699 +#: src/api/reference.md:1701 msgid "`__set_requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_requestTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1700 +#: src/api/reference.md:1702 msgid "`__set_timeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_timeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1701 +#: src/api/reference.md:1703 msgid "`addListener` — instance _(class: `HttpsServer`)_" msgstr "`addListener` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1702 +#: src/api/reference.md:1704 msgid "`address` — instance _(class: `HttpsServer`)_" msgstr "`address` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1703 +#: src/api/reference.md:1705 msgid "`close` — instance _(class: `HttpsServer`)_" msgstr "`close` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1704 +#: src/api/reference.md:1706 msgid "`closeAllConnections` — instance _(class: `HttpsServer`)_" msgstr "`closeAllConnections` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1705 +#: src/api/reference.md:1707 msgid "`closeIdleConnections` — instance _(class: `HttpsServer`)_" msgstr "`closeIdleConnections` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1709 +#: src/api/reference.md:1711 msgid "`headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`headersTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1710 +#: src/api/reference.md:1712 msgid "`keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`keepAliveTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1711 +#: src/api/reference.md:1713 msgid "`keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`keepAliveTimeoutBuffer` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1712 +#: src/api/reference.md:1714 msgid "`listen` — instance _(class: `HttpsServer`)_" msgstr "`listen` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1713 +#: src/api/reference.md:1715 msgid "`listening` — instance _(class: `HttpsServer`)_" msgstr "`listening` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1714 +#: src/api/reference.md:1716 msgid "`maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`maxHeadersCount` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1715 +#: src/api/reference.md:1717 msgid "`maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`maxRequestsPerSocket` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1716 +#: src/api/reference.md:1718 msgid "`on` — instance _(class: `HttpsServer`)_" msgstr "`on` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1717 +#: src/api/reference.md:1719 msgid "`ref` — instance _(class: `HttpsServer`)_" msgstr "`ref` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1719 +#: src/api/reference.md:1721 msgid "`requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`requestTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1720 +#: src/api/reference.md:1722 msgid "`setTimeout` — instance _(class: `HttpsServer`)_" msgstr "`setTimeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1721 +#: src/api/reference.md:1723 msgid "`timeout` — instance _(class: `HttpsServer`)_" msgstr "`timeout` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1722 +#: src/api/reference.md:1724 msgid "`unref` — instance _(class: `HttpsServer`)_" msgstr "`unref` — Instanz _(Klasse: `HttpsServer`)_" -#: src/api/reference.md:1728 +#: src/api/reference.md:1730 msgid "`inspector`" msgstr "`inspector`" -#: src/api/reference.md:1732 src/api/reference.md:1763 -#: src/api/reference.md:2948 +#: src/api/reference.md:1734 src/api/reference.md:1765 +#: src/api/reference.md:2957 msgid "`Session`" msgstr "`Session`" -#: src/api/reference.md:1736 src/api/reference.md:1767 -#: src/api/reference.md:2955 +#: src/api/reference.md:1738 src/api/reference.md:1769 +#: src/api/reference.md:2964 msgid "`Session` — module" msgstr "`Session` — Modul" -#: src/api/reference.md:1738 src/api/reference.md:1768 +#: src/api/reference.md:1740 src/api/reference.md:1770 msgid "`connect` — instance _(class: `Session`)_" msgstr "`connect` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1739 src/api/reference.md:1769 +#: src/api/reference.md:1741 src/api/reference.md:1771 msgid "`connectToMainThread` — instance _(class: `Session`)_" msgstr "`connectToMainThread` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1740 +#: src/api/reference.md:1742 msgid "`debug` — module _(class: `console`)_" msgstr "`debug` — Modul _(Klasse: `console`)_" -#: src/api/reference.md:1741 src/api/reference.md:1770 +#: src/api/reference.md:1743 src/api/reference.md:1772 msgid "`disconnect` — instance _(class: `Session`)_" msgstr "`disconnect` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1742 +#: src/api/reference.md:1744 msgid "`error` — module _(class: `console`)_" msgstr "`error` — Modul _(Klasse: `console`)_" -#: src/api/reference.md:1743 +#: src/api/reference.md:1745 msgid "`info` — module _(class: `console`)_" msgstr "`info` — Modul _(Klasse: `console`)_" -#: src/api/reference.md:1744 +#: src/api/reference.md:1746 msgid "`log` — module _(class: `console`)_" msgstr "`log` — Modul _(Klasse: `console`)_" -#: src/api/reference.md:1745 src/api/reference.md:1771 +#: src/api/reference.md:1747 src/api/reference.md:1773 msgid "`on` — instance _(class: `Session`)_" msgstr "`on` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1746 src/api/reference.md:1772 +#: src/api/reference.md:1748 src/api/reference.md:1774 msgid "`once` — instance _(class: `Session`)_" msgstr "`once` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1747 +#: src/api/reference.md:1749 msgid "" "`open` — module ⚠ **stub** — accepts port/host but binds no real WebSocket " "inspector endpoint; sessions are in-process fakes (#4916)" @@ -41077,7 +40813,7 @@ msgstr "" "`open` — Modul **Stub** — akzeptiert port/host, bindet aber keinen echten " "WebSocket-Inspektor-Endpunkt; Sitzungen sind In-Prozess-Fakten (#4916)" -#: src/api/reference.md:1748 +#: src/api/reference.md:1750 msgid "" "`post` — instance _(class: `Session`)_ ⚠ **stub** — only Runtime.enable and " "a canned Runtime.evaluate subset respond; every other protocol method throws " @@ -41087,7 +40823,7 @@ msgstr "" "und eine Runtime.evaluate-Untergruppe reagieren; jede andere " "Protokollmethode wirft Inspektor-Fehler -32601 (#4916)" -#: src/api/reference.md:1749 +#: src/api/reference.md:1751 msgid "" "`url` — module ⚠ **stub** — always undefined: Perry never exposes a real " "inspector endpoint (#4916)" @@ -41095,7 +40831,7 @@ msgstr "" "`url` — Modul **Stub** — immer undefiniert: Perry setzt nie einen echten " "Inspector-Endpunkt (#4916) frei" -#: src/api/reference.md:1750 +#: src/api/reference.md:1752 msgid "" "`waitForDebugger` — module ⚠ **stub** — returns immediately after open(); " "there is no debugger to wait for (#4916)" @@ -41103,3091 +40839,3106 @@ msgstr "" "`waitForDebugger` — Modul **Stub** — kehrt sofort nach open() zurück; es " "gibt keinen Debugger, auf den man warten kann (#4916)" -#: src/api/reference.md:1751 +#: src/api/reference.md:1753 msgid "`warn` — module _(class: `console`)_" msgstr "`warn` — Modul _(Klasse: `console`)_" -#: src/api/reference.md:1755 +#: src/api/reference.md:1757 msgid "`Network`" msgstr "`Network`" -#: src/api/reference.md:1759 +#: src/api/reference.md:1761 msgid "`inspector/promises`" msgstr "`inspector/promises`" -#: src/api/reference.md:1773 +#: src/api/reference.md:1775 msgid "`post` — instance _(class: `Session`)_" msgstr "`post` — Instanz _(Klasse: `Session`)_" -#: src/api/reference.md:1779 +#: src/api/reference.md:1781 msgid "`lodash`" msgstr "`lodash`" -#: src/api/reference.md:1783 +#: src/api/reference.md:1785 msgid "`camelCase` — module" msgstr "`camelCase` — Modul" -#: src/api/reference.md:1784 +#: src/api/reference.md:1786 msgid "`chunk` — module" msgstr "`chunk` — Modul" -#: src/api/reference.md:1785 src/api/reference.md:1786 +#: src/api/reference.md:1787 src/api/reference.md:1788 msgid "`clamp` — module" msgstr "`clamp` — Modul" -#: src/api/reference.md:1787 +#: src/api/reference.md:1789 msgid "`compact` — module" msgstr "`compact` — Modul" -#: src/api/reference.md:1788 +#: src/api/reference.md:1790 msgid "`drop` — module" msgstr "`drop` — Modul" -#: src/api/reference.md:1789 +#: src/api/reference.md:1791 msgid "`first` — module" msgstr "`first` — Modul" -#: src/api/reference.md:1790 +#: src/api/reference.md:1792 msgid "`flatten` — module" msgstr "`flatten` — Modul" -#: src/api/reference.md:1791 +#: src/api/reference.md:1793 msgid "`head` — module" msgstr "`head` — Modul" -#: src/api/reference.md:1792 +#: src/api/reference.md:1794 msgid "`inRange` — module" msgstr "`inRange` — Modul" -#: src/api/reference.md:1793 +#: src/api/reference.md:1795 msgid "`kebabCase` — module" msgstr "`kebabCase` — Modul" -#: src/api/reference.md:1794 +#: src/api/reference.md:1796 msgid "`last` — module" msgstr "`last` — Modul" -#: src/api/reference.md:1795 +#: src/api/reference.md:1797 msgid "`max` — module" msgstr "`max` — Modul" -#: src/api/reference.md:1796 +#: src/api/reference.md:1798 msgid "`maxBy` — module" msgstr "`maxBy` — Modul" -#: src/api/reference.md:1797 +#: src/api/reference.md:1799 msgid "`mean` — module" msgstr "`mean` — Modul" -#: src/api/reference.md:1798 +#: src/api/reference.md:1800 msgid "`meanBy` — module" msgstr "`meanBy` — Modul" -#: src/api/reference.md:1799 +#: src/api/reference.md:1801 msgid "`min` — module" msgstr "`min` — Modul" -#: src/api/reference.md:1800 +#: src/api/reference.md:1802 msgid "`minBy` — module" msgstr "`minBy` — Modul" -#: src/api/reference.md:1801 +#: src/api/reference.md:1803 msgid "`random` — module" msgstr "`random` — Modul" -#: src/api/reference.md:1802 +#: src/api/reference.md:1804 msgid "`range` — module" msgstr "`range` — Modul" -#: src/api/reference.md:1804 +#: src/api/reference.md:1806 msgid "`size` — module" msgstr "`size` — Modul" -#: src/api/reference.md:1805 +#: src/api/reference.md:1807 msgid "`snakeCase` — module" msgstr "`snakeCase` — Modul" -#: src/api/reference.md:1806 +#: src/api/reference.md:1808 msgid "`sum` — module" msgstr "`sum` — Modul" -#: src/api/reference.md:1807 +#: src/api/reference.md:1809 msgid "`sumBy` — module" msgstr "`sumBy` — Modul" -#: src/api/reference.md:1808 +#: src/api/reference.md:1810 msgid "`tail` — module" msgstr "`tail` — Modul" -#: src/api/reference.md:1809 +#: src/api/reference.md:1811 msgid "`take` — module" msgstr "`take` — Modul" -#: src/api/reference.md:1810 +#: src/api/reference.md:1812 msgid "`times` — module" msgstr "`times` — Modul" -#: src/api/reference.md:1811 +#: src/api/reference.md:1813 msgid "`uniq` — module" msgstr "`uniq` — Modul" -#: src/api/reference.md:1813 +#: src/api/reference.md:1815 msgid "`module`" msgstr "`module`" -#: src/api/reference.md:1817 src/api/reference.md:1848 +#: src/api/reference.md:1819 src/api/reference.md:1850 msgid "`Module`" msgstr "`Module`" -#: src/api/reference.md:1818 +#: src/api/reference.md:1820 msgid "`SourceMap`" msgstr "`SourceMap`" -#: src/api/reference.md:1822 +#: src/api/reference.md:1824 msgid "`Module` — module" msgstr "`Module` — Modul" -#: src/api/reference.md:1823 +#: src/api/reference.md:1825 msgid "`SourceMap` — module" msgstr "`SourceMap` — Modul" -#: src/api/reference.md:1824 +#: src/api/reference.md:1826 msgid "`_findPath` — module" msgstr "`_findPath` — Modul" -#: src/api/reference.md:1825 +#: src/api/reference.md:1827 msgid "`_initPaths` — module" msgstr "`_initPaths` — Modul" -#: src/api/reference.md:1826 +#: src/api/reference.md:1828 msgid "`_load` — module" msgstr "`_load` — Modul" -#: src/api/reference.md:1827 +#: src/api/reference.md:1829 msgid "`_nodeModulePaths` — module" msgstr "`_nodeModulePaths` — Modul" -#: src/api/reference.md:1828 +#: src/api/reference.md:1830 msgid "`_preloadModules` — module" msgstr "`_preloadModules` — Modul" -#: src/api/reference.md:1829 +#: src/api/reference.md:1831 msgid "`_resolveFilename` — module" msgstr "`_resolveFilename` — Modul" -#: src/api/reference.md:1830 +#: src/api/reference.md:1832 msgid "`_resolveLookupPaths` — module" msgstr "`_resolveLookupPaths` — Modul" -#: src/api/reference.md:1831 +#: src/api/reference.md:1833 msgid "`createRequire` — module" msgstr "`createRequire` — Modul" -#: src/api/reference.md:1832 +#: src/api/reference.md:1834 msgid "`enableCompileCache` — module" msgstr "`enableCompileCache` — Modul" -#: src/api/reference.md:1833 +#: src/api/reference.md:1835 msgid "`findPackageJSON` — module" msgstr "`findPackageJSON` — Modul" -#: src/api/reference.md:1834 +#: src/api/reference.md:1836 msgid "`findSourceMap` — module" msgstr "`findSourceMap` — Modul" -#: src/api/reference.md:1835 +#: src/api/reference.md:1837 msgid "`flushCompileCache` — module" msgstr "`flushCompileCache` — Modul" -#: src/api/reference.md:1836 +#: src/api/reference.md:1838 msgid "`getCompileCacheDir` — module" msgstr "`getCompileCacheDir` — Modul" -#: src/api/reference.md:1837 +#: src/api/reference.md:1839 msgid "`getSourceMapsSupport` — module" msgstr "`getSourceMapsSupport` — Modul" -#: src/api/reference.md:1838 +#: src/api/reference.md:1840 msgid "`isBuiltin` — module" msgstr "`isBuiltin` — Modul" -#: src/api/reference.md:1839 +#: src/api/reference.md:1841 msgid "`register` — module" msgstr "`register` — Modul" -#: src/api/reference.md:1840 +#: src/api/reference.md:1842 msgid "`registerHooks` — module" msgstr "`registerHooks` — Modul" -#: src/api/reference.md:1841 +#: src/api/reference.md:1843 msgid "`runMain` — module" msgstr "`runMain` — Modul" -#: src/api/reference.md:1842 +#: src/api/reference.md:1844 msgid "`setSourceMapsSupport` — module" msgstr "`setSourceMapsSupport` — Modul" -#: src/api/reference.md:1843 +#: src/api/reference.md:1845 msgid "`stripTypeScriptTypes` — module" msgstr "`stripTypeScriptTypes` — Modul" -#: src/api/reference.md:1844 +#: src/api/reference.md:1846 msgid "`syncBuiltinESMExports` — module" msgstr "`syncBuiltinESMExports` — Modul" -#: src/api/reference.md:1849 +#: src/api/reference.md:1851 msgid "`_cache`" msgstr "`_cache`" -#: src/api/reference.md:1850 +#: src/api/reference.md:1852 msgid "`_extensions`" msgstr "`_extensions`" -#: src/api/reference.md:1851 +#: src/api/reference.md:1853 msgid "`_pathCache`" msgstr "`_pathCache`" -#: src/api/reference.md:1852 src/api/reference.md:2897 +#: src/api/reference.md:1854 src/api/reference.md:2906 msgid "`builtinModules`" msgstr "`builtinModules`" -#: src/api/reference.md:1855 +#: src/api/reference.md:1857 msgid "`globalPaths`" msgstr "`globalPaths`" -#: src/api/reference.md:1861 +#: src/api/reference.md:1863 msgid "`BlockList`" msgstr "`BlockList`" -#: src/api/reference.md:1864 +#: src/api/reference.md:1866 msgid "`SocketAddress`" msgstr "`SocketAddress`" -#: src/api/reference.md:1865 src/api/reference.md:3011 +#: src/api/reference.md:1867 src/api/reference.md:3020 msgid "`Stream`" msgstr "`Stream`" -#: src/api/reference.md:1869 +#: src/api/reference.md:1871 msgid "`BlockList` — module" msgstr "`BlockList` — Modul" -#: src/api/reference.md:1872 +#: src/api/reference.md:1874 msgid "`SocketAddress` — module" msgstr "`SocketAddress` — Modul" -#: src/api/reference.md:1873 +#: src/api/reference.md:1875 msgid "`Stream` — module" msgstr "`Stream` — Modul" -#: src/api/reference.md:1874 +#: src/api/reference.md:1876 msgid "`__set_dropMaxConnection` — instance _(class: `Server`)_" msgstr "`__set_dropMaxConnection` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1875 +#: src/api/reference.md:1877 msgid "`__set_maxConnections` — instance _(class: `Server`)_" msgstr "`__set_maxConnections` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1876 +#: src/api/reference.md:1878 msgid "`_createServerHandle` — module" msgstr "`_createServerHandle` — Modul" -#: src/api/reference.md:1877 +#: src/api/reference.md:1879 msgid "`_normalizeArgs` — module" msgstr "`_normalizeArgs` — Modul" -#: src/api/reference.md:1878 +#: src/api/reference.md:1880 #, fuzzy msgid "`_readableState` — instance" msgstr "`readyState` — Instanz" -#: src/api/reference.md:1879 +#: src/api/reference.md:1881 #, fuzzy msgid "`_writableState` — instance" msgstr "`writable` — Instanz" -#: src/api/reference.md:1880 +#: src/api/reference.md:1882 msgid "`addAddress` — instance _(class: `BlockList`)_" msgstr "`addAddress` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1882 src/api/reference.md:3301 +#: src/api/reference.md:1884 src/api/reference.md:3310 msgid "`addListener` — instance _(class: `Server`)_" msgstr "`addListener` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1883 +#: src/api/reference.md:1885 msgid "`addRange` — instance _(class: `BlockList`)_" msgstr "`addRange` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1884 +#: src/api/reference.md:1886 msgid "`addSubnet` — instance _(class: `BlockList`)_" msgstr "`addSubnet` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1886 +#: src/api/reference.md:1888 msgid "`address` — instance _(class: `SocketAddress`)_" msgstr "`address` — Instanz _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1887 src/api/reference.md:3302 +#: src/api/reference.md:1889 src/api/reference.md:3311 msgid "`address` — instance _(class: `Server`)_" msgstr "`address` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1888 +#: src/api/reference.md:1890 msgid "`autoSelectFamilyAttemptedAddresses` — instance _(class: `Socket`)_" msgstr "`autoSelectFamilyAttemptedAddresses` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1889 +#: src/api/reference.md:1891 msgid "`bufferSize` — instance _(class: `Socket`)_" msgstr "`bufferSize` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1890 +#: src/api/reference.md:1892 msgid "`bytesRead` — instance _(class: `Socket`)_" msgstr "`bytesRead` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1891 +#: src/api/reference.md:1893 msgid "`bytesWritten` — instance _(class: `Socket`)_" msgstr "`bytesWritten` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1892 +#: src/api/reference.md:1894 msgid "`check` — instance _(class: `BlockList`)_" msgstr "`check` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1893 src/api/reference.md:3304 +#: src/api/reference.md:1895 src/api/reference.md:3313 msgid "`close` — instance _(class: `Server`)_" msgstr "`close` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1896 +#: src/api/reference.md:1898 msgid "`connecting` — instance _(class: `Socket`)_" msgstr "`connecting` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1897 +#: src/api/reference.md:1899 msgid "`cork` — instance _(class: `Socket`)_" msgstr "`cork` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1898 +#: src/api/reference.md:1900 msgid "`createConnection` — module" msgstr "`createConnection` — Modul" -#: src/api/reference.md:1900 +#: src/api/reference.md:1902 msgid "`destroy` — instance _(class: `Socket`)_" msgstr "`destroy` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1901 +#: src/api/reference.md:1903 msgid "`destroyed` — instance _(class: `Socket`)_" msgstr "`destroyed` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1902 +#: src/api/reference.md:1904 msgid "`dropMaxConnection` — instance _(class: `Server`)_" msgstr "`dropMaxConnection` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1903 +#: src/api/reference.md:1905 msgid "`end` — instance _(class: `Socket`)_" msgstr "`end` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1905 src/api/reference.md:3309 +#: src/api/reference.md:1907 src/api/reference.md:3318 msgid "`eventNames` — instance _(class: `Server`)_" msgstr "`eventNames` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1906 +#: src/api/reference.md:1908 msgid "`exportKeyingMaterial` — instance _(class: `Socket`)_" msgstr "`exportKeyingMaterial` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1907 +#: src/api/reference.md:1909 msgid "`family` — instance _(class: `SocketAddress`)_" msgstr "`family` — Instanz _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1908 +#: src/api/reference.md:1910 msgid "`flowlabel` — instance _(class: `SocketAddress`)_" msgstr "`flowlabel` — Instanz _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1909 +#: src/api/reference.md:1911 msgid "`fromJSON` — instance _(class: `BlockList`)_" msgstr "`fromJSON` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1910 +#: src/api/reference.md:1912 msgid "`getCertificate` — instance _(class: `Socket`)_" msgstr "`getCertificate` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1911 +#: src/api/reference.md:1913 msgid "`getCipher` — instance _(class: `Socket`)_" msgstr "`getCipher` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1912 +#: src/api/reference.md:1914 msgid "`getConnections` — instance _(class: `Server`)_" msgstr "`getConnections` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1913 +#: src/api/reference.md:1915 msgid "`getDefaultAutoSelectFamily` — module" msgstr "`getDefaultAutoSelectFamily` — Modul" -#: src/api/reference.md:1914 +#: src/api/reference.md:1916 msgid "`getDefaultAutoSelectFamilyAttemptTimeout` — module" msgstr "`getDefaultAutoSelectFamilyAttemptTimeout` — Modul" -#: src/api/reference.md:1915 +#: src/api/reference.md:1917 msgid "`getEphemeralKeyInfo` — instance _(class: `Socket`)_" msgstr "`getEphemeralKeyInfo` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1916 +#: src/api/reference.md:1918 msgid "`getFinished` — instance _(class: `Socket`)_" msgstr "`getFinished` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1917 +#: src/api/reference.md:1919 msgid "`getPeerCertificate` — instance _(class: `Socket`)_" msgstr "`getPeerCertificate` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1918 +#: src/api/reference.md:1920 msgid "`getPeerFinished` — instance _(class: `Socket`)_" msgstr "`getPeerFinished` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1919 +#: src/api/reference.md:1921 msgid "`getPeerX509Certificate` — instance _(class: `Socket`)_" msgstr "`getPeerX509Certificate` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1920 +#: src/api/reference.md:1922 msgid "`getProtocol` — instance _(class: `Socket`)_" msgstr "`getProtocol` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1921 +#: src/api/reference.md:1923 msgid "`getSession` — instance _(class: `Socket`)_" msgstr "`getSession` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1922 +#: src/api/reference.md:1924 msgid "`getSharedSigalgs` — instance _(class: `Socket`)_" msgstr "`getSharedSigalgs` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1923 +#: src/api/reference.md:1925 msgid "`getTypeOfService` — instance _(class: `Socket`)_" msgstr "`getTypeOfService` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1924 +#: src/api/reference.md:1926 msgid "`getX509Certificate` — instance _(class: `Socket`)_" msgstr "`getX509Certificate` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1925 +#: src/api/reference.md:1927 msgid "`isBlockList` — module _(class: `BlockList`)_" msgstr "`isBlockList` — Modul _(Klasse: `BlockList`)_" -#: src/api/reference.md:1926 +#: src/api/reference.md:1928 msgid "`isIP` — module" msgstr "`isIP` — Modul" -#: src/api/reference.md:1927 +#: src/api/reference.md:1929 msgid "`isIPv4` — module" msgstr "`isIPv4` — Modul" -#: src/api/reference.md:1928 +#: src/api/reference.md:1930 msgid "`isIPv6` — module" msgstr "`isIPv6` — Modul" -#: src/api/reference.md:1929 +#: src/api/reference.md:1931 msgid "`isSessionReused` — instance _(class: `Socket`)_" msgstr "`isSessionReused` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1930 +#: src/api/reference.md:1932 #, fuzzy msgid "`isSocketAddress` — module _(class: `SocketAddress`)_" msgstr "`parse` — Modul _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1931 src/api/reference.md:3314 +#: src/api/reference.md:1933 src/api/reference.md:3323 msgid "`listen` — instance _(class: `Server`)_" msgstr "`listen` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1933 src/api/reference.md:3315 +#: src/api/reference.md:1935 src/api/reference.md:3324 msgid "`listenerCount` — instance _(class: `Server`)_" msgstr "`listenerCount` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1934 +#: src/api/reference.md:1936 msgid "`listeners` — instance _(class: `Socket`)_" msgstr "`listeners` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1935 +#: src/api/reference.md:1937 msgid "`listeners` — instance _(class: `Server`)_" msgstr "`listeners` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1936 +#: src/api/reference.md:1938 msgid "`listening` — instance _(class: `Server`)_" msgstr "`listening` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1937 +#: src/api/reference.md:1939 msgid "`localAddress` — instance _(class: `Socket`)_" msgstr "`localAddress` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1938 +#: src/api/reference.md:1940 msgid "`localFamily` — instance _(class: `Socket`)_" msgstr "`localFamily` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1939 +#: src/api/reference.md:1941 msgid "`localPort` — instance _(class: `Socket`)_" msgstr "`localPort` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1940 +#: src/api/reference.md:1942 msgid "`maxConnections` — instance _(class: `Server`)_" msgstr "`maxConnections` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1942 src/api/reference.md:3316 +#: src/api/reference.md:1944 src/api/reference.md:3325 msgid "`off` — instance _(class: `Server`)_" msgstr "`off` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1945 src/api/reference.md:3318 +#: src/api/reference.md:1947 src/api/reference.md:3327 msgid "`once` — instance _(class: `Server`)_" msgstr "`once` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1946 +#: src/api/reference.md:1948 msgid "`parse` — module _(class: `SocketAddress`)_" msgstr "`parse` — Modul _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1947 +#: src/api/reference.md:1949 msgid "`pause` — instance _(class: `Socket`)_" msgstr "`pause` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1948 +#: src/api/reference.md:1950 msgid "`pending` — instance _(class: `Socket`)_" msgstr "`pending` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1949 +#: src/api/reference.md:1951 #, fuzzy msgid "`pipe` — instance _(class: `Socket`)_" msgstr "`pause` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1950 +#: src/api/reference.md:1952 msgid "`port` — instance _(class: `SocketAddress`)_" msgstr "`port` — Instanz _(Klasse: `SocketAddress`)_" -#: src/api/reference.md:1951 +#: src/api/reference.md:1953 #, fuzzy msgid "`prependListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1952 +#: src/api/reference.md:1954 #, fuzzy msgid "`prependOnceListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1953 +#: src/api/reference.md:1955 msgid "`rawListeners` — instance _(class: `Socket`)_" msgstr "`rawListeners` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1954 +#: src/api/reference.md:1956 msgid "`rawListeners` — instance _(class: `Server`)_" msgstr "`rawListeners` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1955 +#: src/api/reference.md:1957 #, fuzzy msgid "`read` — instance _(class: `Socket`)_" msgstr "`end` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1956 src/api/reference.md:3056 +#: src/api/reference.md:1958 src/api/reference.md:3065 msgid "`readable` — instance" msgstr "`readable` — Instanz" -#: src/api/reference.md:1957 src/api/reference.md:3060 +#: src/api/reference.md:1959 src/api/reference.md:3069 msgid "`readableEnded` — instance" msgstr "`readableEnded` — Instanz" -#: src/api/reference.md:1958 +#: src/api/reference.md:1960 msgid "`readyState` — instance _(class: `Socket`)_" msgstr "`readyState` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1961 +#: src/api/reference.md:1963 msgid "`remoteFamily` — instance _(class: `Socket`)_" msgstr "`remoteFamily` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1962 +#: src/api/reference.md:1964 msgid "`remotePort` — instance _(class: `Socket`)_" msgstr "`remotePort` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1963 +#: src/api/reference.md:1965 msgid "`removeAllListeners` — instance _(class: `Socket`)_" msgstr "`removeAllListeners` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1964 src/api/reference.md:3319 +#: src/api/reference.md:1966 src/api/reference.md:3328 msgid "`removeAllListeners` — instance _(class: `Server`)_" msgstr "`removeAllListeners` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1966 src/api/reference.md:3320 +#: src/api/reference.md:1968 src/api/reference.md:3329 msgid "`removeListener` — instance _(class: `Server`)_" msgstr "`removeListener` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:1967 +#: src/api/reference.md:1969 msgid "`resetAndDestroy` — instance _(class: `Socket`)_" msgstr "`resetAndDestroy` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1968 +#: src/api/reference.md:1970 msgid "`resume` — instance _(class: `Socket`)_" msgstr "`resume` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1969 +#: src/api/reference.md:1971 msgid "`rules` — instance _(class: `BlockList`)_" msgstr "`rules` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1970 +#: src/api/reference.md:1972 msgid "`setDefaultAutoSelectFamily` — module" msgstr "`setDefaultAutoSelectFamily` — Modul" -#: src/api/reference.md:1971 +#: src/api/reference.md:1973 msgid "`setDefaultAutoSelectFamilyAttemptTimeout` — module" msgstr "`setDefaultAutoSelectFamilyAttemptTimeout` — Modul" -#: src/api/reference.md:1972 +#: src/api/reference.md:1974 msgid "`setDefaultEncoding` — instance _(class: `Socket`)_" msgstr "`setDefaultEncoding` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1973 +#: src/api/reference.md:1975 msgid "`setEncoding` — instance _(class: `Socket`)_" msgstr "`setEncoding` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1974 +#: src/api/reference.md:1976 msgid "`setKeepAlive` — instance _(class: `Socket`)_" msgstr "`setKeepAlive` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1975 +#: src/api/reference.md:1977 msgid "`setKeyCert` — instance _(class: `Socket`)_" msgstr "`setKeyCert` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1976 +#: src/api/reference.md:1978 msgid "`setMaxSendFragment` — instance _(class: `Socket`)_" msgstr "`setMaxSendFragment` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1977 +#: src/api/reference.md:1979 msgid "`setNoDelay` — instance _(class: `Socket`)_" msgstr "`setNoDelay` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1978 +#: src/api/reference.md:1980 msgid "`setTimeout` — instance _(class: `Socket`)_" msgstr "`setTimeout` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1979 +#: src/api/reference.md:1981 msgid "`setTypeOfService` — instance _(class: `Socket`)_" msgstr "`setTypeOfService` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1980 +#: src/api/reference.md:1982 msgid "`timeout` — instance _(class: `Socket`)_" msgstr "`timeout` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1981 +#: src/api/reference.md:1983 msgid "`toJSON` — instance _(class: `BlockList`)_" msgstr "`toJSON` — Instanz _(Klasse: `BlockList`)_" -#: src/api/reference.md:1982 +#: src/api/reference.md:1984 msgid "`uncork` — instance _(class: `Socket`)_" msgstr "`uncork` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1983 +#: src/api/reference.md:1985 #, fuzzy msgid "`unpipe` — instance _(class: `Socket`)_" msgstr "`unref` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1985 +#: src/api/reference.md:1987 msgid "`upgradeToTLS` — instance _(class: `Socket`)_" msgstr "`upgradeToTLS` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1986 src/api/reference.md:3074 +#: src/api/reference.md:1988 src/api/reference.md:3083 msgid "`writable` — instance" msgstr "`writable` — Instanz" -#: src/api/reference.md:1987 +#: src/api/reference.md:1989 #, fuzzy msgid "`writableCorked` — instance _(class: `Socket`)_" msgstr "`write` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1988 src/api/reference.md:3076 +#: src/api/reference.md:1990 src/api/reference.md:3085 msgid "`writableEnded` — instance" msgstr "`writableEnded` — Instanz" -#: src/api/reference.md:1989 +#: src/api/reference.md:1991 msgid "`write` — instance _(class: `Socket`)_" msgstr "`write` — Instanz _(Klasse: `Socket`)_" -#: src/api/reference.md:1991 +#: src/api/reference.md:1993 msgid "`node-fetch`" msgstr "`node-fetch`" -#: src/api/reference.md:2005 +#: src/api/reference.md:2007 msgid "`node-pty`" msgstr "`node-pty`" -#: src/api/reference.md:2019 +#: src/api/reference.md:2021 msgid "`createTransport` — module" msgstr "`createTransport` — Modul" -#: src/api/reference.md:2020 +#: src/api/reference.md:2022 msgid "`sendMail` — instance" msgstr "`sendMail` — Instanz" -#: src/api/reference.md:2021 +#: src/api/reference.md:2023 msgid "`verify` — instance" msgstr "`verify` — Instanz" -#: src/api/reference.md:2023 +#: src/api/reference.md:2025 msgid "`os`" msgstr "`os`" -#: src/api/reference.md:2027 +#: src/api/reference.md:2029 msgid "`arch` — module" msgstr "`arch` — Modul" -#: src/api/reference.md:2028 +#: src/api/reference.md:2030 msgid "`availableParallelism` — module" msgstr "`availableParallelism` — Modul" -#: src/api/reference.md:2029 +#: src/api/reference.md:2031 msgid "`cpus` — module" msgstr "`cpus` — Modul" -#: src/api/reference.md:2030 +#: src/api/reference.md:2032 msgid "`endianness` — module" msgstr "`endianness` — Modul" -#: src/api/reference.md:2031 +#: src/api/reference.md:2033 msgid "`freemem` — module" msgstr "`freemem` — Modul" -#: src/api/reference.md:2032 +#: src/api/reference.md:2034 msgid "`getPriority` — module" msgstr "`getPriority` — Modul" -#: src/api/reference.md:2033 +#: src/api/reference.md:2035 msgid "`homedir` — module" msgstr "`homedir` — Modul" -#: src/api/reference.md:2034 +#: src/api/reference.md:2036 msgid "`hostname` — module" msgstr "`hostname` — Modul" -#: src/api/reference.md:2035 +#: src/api/reference.md:2037 msgid "`loadavg` — module" msgstr "`loadavg` — Modul" -#: src/api/reference.md:2036 +#: src/api/reference.md:2038 msgid "`machine` — module" msgstr "`machine` — Modul" -#: src/api/reference.md:2037 +#: src/api/reference.md:2039 msgid "`networkInterfaces` — module" msgstr "`networkInterfaces` — Modul" -#: src/api/reference.md:2038 +#: src/api/reference.md:2040 msgid "`platform` — module" msgstr "`platform` — Modul" -#: src/api/reference.md:2039 +#: src/api/reference.md:2041 msgid "`release` — module" msgstr "`release` — Modul" -#: src/api/reference.md:2040 +#: src/api/reference.md:2042 msgid "`setPriority` — module" msgstr "`setPriority` — Modul" -#: src/api/reference.md:2041 +#: src/api/reference.md:2043 msgid "`tmpdir` — module" msgstr "`tmpdir` — Modul" -#: src/api/reference.md:2042 +#: src/api/reference.md:2044 msgid "`totalmem` — module" msgstr "`totalmem` — Modul" -#: src/api/reference.md:2043 +#: src/api/reference.md:2045 msgid "`type` — module" msgstr "`type` — Modul" -#: src/api/reference.md:2044 src/api/reference.md:2767 +#: src/api/reference.md:2046 src/api/reference.md:2772 msgid "`uptime` — module" msgstr "`uptime` — Modul" -#: src/api/reference.md:2045 +#: src/api/reference.md:2047 msgid "`userInfo` — module" msgstr "`userInfo` — Modul" -#: src/api/reference.md:2046 +#: src/api/reference.md:2048 msgid "`version` — module" msgstr "`version` — Modul" -#: src/api/reference.md:2050 +#: src/api/reference.md:2052 msgid "`EOL`" msgstr "`EOL`" -#: src/api/reference.md:2053 +#: src/api/reference.md:2055 msgid "`devNull`" msgstr "`devNull`" -#: src/api/reference.md:2055 +#: src/api/reference.md:2057 msgid "`path`" msgstr "`path`" -#: src/api/reference.md:2059 src/api/reference.md:2085 -#: src/api/reference.md:2111 +#: src/api/reference.md:2061 src/api/reference.md:2087 +#: src/api/reference.md:2113 msgid "`_makeLong` — module" msgstr "`_makeLong` — Modul" -#: src/api/reference.md:2060 src/api/reference.md:2086 -#: src/api/reference.md:2112 +#: src/api/reference.md:2062 src/api/reference.md:2088 +#: src/api/reference.md:2114 msgid "`basename` — module" msgstr "`basename` — Modul" - -#: src/api/reference.md:2061 src/api/reference.md:2087 -#: src/api/reference.md:2113 + +#: src/api/reference.md:2063 src/api/reference.md:2089 +#: src/api/reference.md:2115 msgid "`dirname` — module" msgstr "`dirname` — Modul" -#: src/api/reference.md:2062 src/api/reference.md:2088 -#: src/api/reference.md:2114 +#: src/api/reference.md:2064 src/api/reference.md:2090 +#: src/api/reference.md:2116 msgid "`extname` — module" msgstr "`extname` — Modul" -#: src/api/reference.md:2063 src/api/reference.md:2089 -#: src/api/reference.md:2115 src/api/reference.md:3186 -#: src/api/reference.md:3413 src/api/reference.md:3448 +#: src/api/reference.md:2065 src/api/reference.md:2091 +#: src/api/reference.md:2117 src/api/reference.md:3195 +#: src/api/reference.md:3433 src/api/reference.md:3468 msgid "`format` — module" msgstr "`format` — Modul" -#: src/api/reference.md:2064 src/api/reference.md:2090 -#: src/api/reference.md:2116 +#: src/api/reference.md:2066 src/api/reference.md:2092 +#: src/api/reference.md:2118 msgid "`isAbsolute` — module" msgstr "`isAbsolute` — Modul" -#: src/api/reference.md:2065 src/api/reference.md:2091 -#: src/api/reference.md:2117 +#: src/api/reference.md:2067 src/api/reference.md:2093 +#: src/api/reference.md:2119 msgid "`join` — module" msgstr "`join` — Modul" -#: src/api/reference.md:2066 src/api/reference.md:2092 -#: src/api/reference.md:2118 +#: src/api/reference.md:2068 src/api/reference.md:2094 +#: src/api/reference.md:2120 msgid "`matchesGlob` — module" msgstr "`matchesGlob` — Modul" -#: src/api/reference.md:2067 src/api/reference.md:2093 -#: src/api/reference.md:2119 +#: src/api/reference.md:2069 src/api/reference.md:2095 +#: src/api/reference.md:2121 msgid "`normalize` — module" msgstr "`normalize` — Modul" -#: src/api/reference.md:2068 src/api/reference.md:2094 -#: src/api/reference.md:2120 src/api/reference.md:2825 -#: src/api/reference.md:3414 +#: src/api/reference.md:2070 src/api/reference.md:2096 +#: src/api/reference.md:2122 src/api/reference.md:2830 +#: src/api/reference.md:3434 msgid "`parse` — module" msgstr "`parse` — Modul" -#: src/api/reference.md:2069 src/api/reference.md:2095 -#: src/api/reference.md:2121 +#: src/api/reference.md:2071 src/api/reference.md:2097 +#: src/api/reference.md:2123 msgid "`relative` — module" msgstr "`relative` — Modul" -#: src/api/reference.md:2071 src/api/reference.md:2097 -#: src/api/reference.md:2123 +#: src/api/reference.md:2073 src/api/reference.md:2099 +#: src/api/reference.md:2125 msgid "`toNamespacedPath` — module" msgstr "`toNamespacedPath` — Modul" -#: src/api/reference.md:2076 src/api/reference.md:2102 -#: src/api/reference.md:2128 +#: src/api/reference.md:2078 src/api/reference.md:2104 +#: src/api/reference.md:2130 msgid "`delimiter`" msgstr "`delimiter`" -#: src/api/reference.md:2077 src/api/reference.md:2103 -#: src/api/reference.md:2129 +#: src/api/reference.md:2079 src/api/reference.md:2105 +#: src/api/reference.md:2131 msgid "`posix`" msgstr "`posix`" -#: src/api/reference.md:2078 src/api/reference.md:2104 -#: src/api/reference.md:2130 +#: src/api/reference.md:2080 src/api/reference.md:2106 +#: src/api/reference.md:2132 msgid "`sep`" msgstr "`sep`" -#: src/api/reference.md:2079 src/api/reference.md:2105 -#: src/api/reference.md:2131 +#: src/api/reference.md:2081 src/api/reference.md:2107 +#: src/api/reference.md:2133 msgid "`win32`" msgstr "`win32`" -#: src/api/reference.md:2081 +#: src/api/reference.md:2083 msgid "`path/posix`" msgstr "`path/posix`" -#: src/api/reference.md:2107 +#: src/api/reference.md:2109 msgid "`path/win32`" msgstr "`path/win32`" -#: src/api/reference.md:2133 +#: src/api/reference.md:2135 msgid "`perf_hooks`" msgstr "`perf_hooks`" -#: src/api/reference.md:2137 +#: src/api/reference.md:2139 msgid "`Performance`" msgstr "`Performance`" -#: src/api/reference.md:2138 +#: src/api/reference.md:2140 msgid "`PerformanceEntry`" msgstr "`PerformanceEntry`" -#: src/api/reference.md:2139 +#: src/api/reference.md:2141 msgid "`PerformanceMark`" msgstr "`PerformanceMark`" -#: src/api/reference.md:2140 +#: src/api/reference.md:2142 msgid "`PerformanceMeasure`" msgstr "`PerformanceMeasure`" -#: src/api/reference.md:2141 +#: src/api/reference.md:2143 msgid "`PerformanceObserver`" msgstr "`PerformanceObserver`" -#: src/api/reference.md:2142 +#: src/api/reference.md:2144 msgid "`PerformanceObserverEntryList`" msgstr "`PerformanceObserverEntryList`" -#: src/api/reference.md:2143 +#: src/api/reference.md:2145 msgid "`PerformanceResourceTiming`" msgstr "`PerformanceResourceTiming`" -#: src/api/reference.md:2147 +#: src/api/reference.md:2149 msgid "`createHistogram` — module" msgstr "`createHistogram` — Modul" -#: src/api/reference.md:2148 +#: src/api/reference.md:2150 msgid "`disconnect` — instance _(class: `PerformanceObserver`)_" msgstr "`disconnect` — Instanz _(Klasse: `PerformanceObserver`)_" -#: src/api/reference.md:2149 +#: src/api/reference.md:2151 msgid "`eventLoopUtilization` — module" msgstr "`eventLoopUtilization` — Modul" -#: src/api/reference.md:2150 +#: src/api/reference.md:2152 msgid "`monitorEventLoopDelay` — module" msgstr "`monitorEventLoopDelay` — Modul" -#: src/api/reference.md:2151 +#: src/api/reference.md:2153 msgid "`observe` — instance _(class: `PerformanceObserver`)_" msgstr "`observe` — Instanz _(Klasse: `PerformanceObserver`)_" -#: src/api/reference.md:2152 +#: src/api/reference.md:2154 msgid "`takeRecords` — instance _(class: `PerformanceObserver`)_" msgstr "`takeRecords` — Instanz _(Klasse: `PerformanceObserver`)_" -#: src/api/reference.md:2153 +#: src/api/reference.md:2155 msgid "`timerify` — module" msgstr "`timerify` — Modul" -#: src/api/reference.md:2159 +#: src/api/reference.md:2161 msgid "`performance`" msgstr "`performance`" -#: src/api/reference.md:2161 src/contributing/architecture.md:26 +#: src/api/reference.md:2163 src/contributing/architecture.md:27 msgid "`perry`" msgstr "`perry`" -#: src/api/reference.md:2165 +#: src/api/reference.md:2167 msgid "`embeddedFiles` — module" msgstr "`embeddedFiles` — Modul" -#: src/api/reference.md:2166 +#: src/api/reference.md:2168 msgid "`readEmbedded` — module" msgstr "`readEmbedded` — Modul" -#: src/api/reference.md:2176 +#: src/api/reference.md:2178 msgid "`js_ads_banner_create` — module" msgstr "`js_ads_banner_create` — Modul" -#: src/api/reference.md:2177 +#: src/api/reference.md:2179 msgid "`js_ads_banner_destroy` — module" msgstr "`js_ads_banner_destroy` — Modul" -#: src/api/reference.md:2178 +#: src/api/reference.md:2180 msgid "`js_ads_interstitial_load` — module" msgstr "`js_ads_interstitial_load` — Modul" -#: src/api/reference.md:2179 +#: src/api/reference.md:2181 msgid "`js_ads_interstitial_show` — module" msgstr "`js_ads_interstitial_show` — Modul" -#: src/api/reference.md:2180 +#: src/api/reference.md:2182 msgid "`js_ads_request_consent` — module" msgstr "`js_ads_request_consent` — Modul" -#: src/api/reference.md:2181 +#: src/api/reference.md:2183 msgid "`js_ads_rewarded_load` — module" msgstr "`js_ads_rewarded_load` — Modul" -#: src/api/reference.md:2182 +#: src/api/reference.md:2184 msgid "`js_ads_rewarded_show` — module" msgstr "`js_ads_rewarded_show` — Modul" -#: src/api/reference.md:2184 +#: src/api/reference.md:2186 msgid "`perry/audio`" msgstr "`perry/audio`" -#: src/api/reference.md:2188 +#: src/api/reference.md:2190 msgid "`createBus` — module" msgstr "`createBus` — Modul" -#: src/api/reference.md:2189 +#: src/api/reference.md:2191 msgid "`crossfade` — module" msgstr "`crossfade` — Modul" -#: src/api/reference.md:2190 +#: src/api/reference.md:2192 msgid "`destroyBus` — module" msgstr "`destroyBus` — Modul" -#: src/api/reference.md:2191 +#: src/api/reference.md:2193 msgid "`fadeIn` — module" msgstr "`fadeIn` — Modul" -#: src/api/reference.md:2192 +#: src/api/reference.md:2194 msgid "`fadeOut` — module" msgstr "`fadeOut` — Modul" -#: src/api/reference.md:2193 src/api/reference.md:2332 +#: src/api/reference.md:2195 src/api/reference.md:2337 msgid "`getDuration` — module" msgstr "`getDuration` — Modul" -#: src/api/reference.md:2194 +#: src/api/reference.md:2196 msgid "`getPosition` — module" msgstr "`getPosition` — Modul" -#: src/api/reference.md:2195 src/api/reference.md:2334 +#: src/api/reference.md:2197 src/api/reference.md:2339 msgid "`isPlaying` — module" msgstr "`isPlaying` — Modul" -#: src/api/reference.md:2196 +#: src/api/reference.md:2198 msgid "`loadSound` — module" msgstr "`loadSound` — Modul" -#: src/api/reference.md:2197 +#: src/api/reference.md:2199 msgid "`muteBus` — module" msgstr "`muteBus` — Modul" -#: src/api/reference.md:2198 +#: src/api/reference.md:2200 msgid "`onEnded` — module" msgstr "`onEnded` — Modul" -#: src/api/reference.md:2199 +#: src/api/reference.md:2201 msgid "`onLoaded` — module" msgstr "`onLoaded` — Modul" -#: src/api/reference.md:2200 src/api/reference.md:2337 +#: src/api/reference.md:2202 src/api/reference.md:2342 msgid "`pause` — module" msgstr "`pause` — Modul" -#: src/api/reference.md:2201 src/api/reference.md:2338 +#: src/api/reference.md:2203 src/api/reference.md:2343 msgid "`play` — module" msgstr "`play` — Modul" -#: src/api/reference.md:2202 +#: src/api/reference.md:2204 msgid "`resume` — module" msgstr "`resume` — Modul" -#: src/api/reference.md:2203 +#: src/api/reference.md:2205 msgid "`resumeAll` — module" msgstr "`resumeAll` — Modul" -#: src/api/reference.md:2204 +#: src/api/reference.md:2206 msgid "`setMasterVolume` — module" msgstr "`setMasterVolume` — Modul" -#: src/api/reference.md:2205 +#: src/api/reference.md:2207 msgid "`setPan` — module" msgstr "`setPan` — Modul" -#: src/api/reference.md:2206 src/api/reference.md:2341 +#: src/api/reference.md:2208 src/api/reference.md:2346 msgid "`setRate` — module" msgstr "`setRate` — Modul" -#: src/api/reference.md:2207 src/api/reference.md:2342 +#: src/api/reference.md:2209 src/api/reference.md:2347 msgid "`setVolume` — module" msgstr "`setVolume` — Modul" -#: src/api/reference.md:2208 +#: src/api/reference.md:2210 msgid "`soloBus` — module" msgstr "`soloBus` — Modul" -#: src/api/reference.md:2209 src/api/reference.md:2247 -#: src/api/reference.md:2276 src/api/reference.md:2289 -#: src/api/reference.md:2343 +#: src/api/reference.md:2211 src/api/reference.md:2252 +#: src/api/reference.md:2281 src/api/reference.md:2294 +#: src/api/reference.md:2348 msgid "`stop` — module" msgstr "`stop` — Modul" -#: src/api/reference.md:2210 +#: src/api/reference.md:2212 msgid "`suspend` — module" msgstr "`suspend` — Modul" -#: src/api/reference.md:2211 +#: src/api/reference.md:2213 msgid "`unload` — module" msgstr "`unload` — Modul" -#: src/api/reference.md:2213 +#: src/api/reference.md:2215 msgid "`perry/background`" msgstr "`perry/background`" -#: src/api/reference.md:2217 +#: src/api/reference.md:2219 msgid "`cancel` — module" msgstr "`cancel` — Modul" -#: src/api/reference.md:2218 +#: src/api/reference.md:2220 msgid "`registerTask` — module" msgstr "`registerTask` — Modul" -#: src/api/reference.md:2219 +#: src/api/reference.md:2221 msgid "`schedule` — module" msgstr "`schedule` — Modul" -#: src/api/reference.md:2221 +#: src/api/reference.md:2223 #, fuzzy msgid "`perry/component`" msgstr "`perry/compose`" -#: src/api/reference.md:2225 +#: src/api/reference.md:2227 #, fuzzy msgid "`callComponent` — module" msgstr "`compose` — Modul" -#: src/api/reference.md:2226 +#: src/api/reference.md:2228 #, fuzzy msgid "`callComponentImport` — module" msgstr "`composeUp` — Modul" -#: src/api/reference.md:2227 +#: src/api/reference.md:2229 +#, fuzzy +msgid "`closeComponentHttpServer` — module" +msgstr "`compose` — Modul" + +#: src/api/reference.md:2230 #, fuzzy msgid "`createComponentHostResource` — module" msgstr "`createInterface` — Modul" -#: src/api/reference.md:2228 +#: src/api/reference.md:2231 +#, fuzzy +msgid "`createComponentHttpServer` — module" +msgstr "`createInterface` — Modul" + +#: src/api/reference.md:2232 #, fuzzy msgid "`createComponentResource` — module" msgstr "`createInterface` — Modul" -#: src/api/reference.md:2229 +#: src/api/reference.md:2233 #, fuzzy msgid "`disposeComponent` — module" msgstr "`isContext` — Modul" -#: src/api/reference.md:2230 +#: src/api/reference.md:2234 #, fuzzy msgid "`getComponentHostResourceValue` — module" msgstr "`executionAsyncResource` — Modul" -#: src/api/reference.md:2231 +#: src/api/reference.md:2235 #, fuzzy msgid "`getComponentResourceValue` — module" msgstr "`executionAsyncResource` — Modul" -#: src/api/reference.md:2232 +#: src/api/reference.md:2236 +#, fuzzy +msgid "`handleComponentHttpRequest` — module" +msgstr "`createInterface` — Modul" + +#: src/api/reference.md:2237 #, fuzzy msgid "`loadComponent` — module" msgstr "`moment` — Modul" -#: src/api/reference.md:2233 +#: src/api/reference.md:2238 #, fuzzy msgid "`registerComponentExports` — module" msgstr "`registerHooks` — Modul" -#: src/api/reference.md:2234 +#: src/api/reference.md:2239 #, fuzzy msgid "`runComponentCommand` — module" msgstr "`runInContext` — Modul" -#: src/api/reference.md:2236 +#: src/api/reference.md:2241 msgid "`perry/compose`" msgstr "`perry/compose`" -#: src/api/reference.md:2240 src/api/reference.md:2282 +#: src/api/reference.md:2245 src/api/reference.md:2287 msgid "`config` — module" msgstr "`config` — Modul" -#: src/api/reference.md:2241 src/api/reference.md:2283 +#: src/api/reference.md:2246 src/api/reference.md:2288 msgid "`down` — module" msgstr "`down` — Modul" -#: src/api/reference.md:2243 src/api/reference.md:2266 -#: src/api/reference.md:2285 +#: src/api/reference.md:2248 src/api/reference.md:2271 +#: src/api/reference.md:2290 msgid "`logs` — module" msgstr "`logs` — Modul" -#: src/api/reference.md:2244 src/api/reference.md:2286 +#: src/api/reference.md:2249 src/api/reference.md:2291 msgid "`ps` — module" msgstr "`ps` — Modul" -#: src/api/reference.md:2245 src/api/reference.md:2287 +#: src/api/reference.md:2250 src/api/reference.md:2292 msgid "`restart` — module" msgstr "`restart` — Modul" -#: src/api/reference.md:2246 src/api/reference.md:2275 -#: src/api/reference.md:2288 +#: src/api/reference.md:2251 src/api/reference.md:2280 +#: src/api/reference.md:2293 msgid "`start` — module" msgstr "`start` — Modul" -#: src/api/reference.md:2248 src/api/reference.md:2290 +#: src/api/reference.md:2253 src/api/reference.md:2295 msgid "`up` — module" msgstr "`up` — Modul" -#: src/api/reference.md:2250 +#: src/api/reference.md:2255 msgid "`perry/container`" msgstr "`perry/container`" -#: src/api/reference.md:2254 +#: src/api/reference.md:2259 msgid "`composeUp` — module" msgstr "`composeUp` — Modul" -#: src/api/reference.md:2256 +#: src/api/reference.md:2261 msgid "`detectBackend` — module" msgstr "`detectBackend` — Modul" -#: src/api/reference.md:2257 +#: src/api/reference.md:2262 msgid "`downAll` — module" msgstr "`downAll` — Modul" -#: src/api/reference.md:2258 +#: src/api/reference.md:2263 msgid "`downByProject` — module" msgstr "`downByProject` — Modul" -#: src/api/reference.md:2260 +#: src/api/reference.md:2265 msgid "`getAvailableBackends` — module" msgstr "`getAvailableBackends` — Modul" -#: src/api/reference.md:2261 +#: src/api/reference.md:2266 msgid "`getBackend` — module" msgstr "`getBackend` — Modul" -#: src/api/reference.md:2262 +#: src/api/reference.md:2267 msgid "`getBackendPriority` — module" msgstr "`getBackendPriority` — Modul" -#: src/api/reference.md:2263 src/api/reference.md:3193 -#: src/api/reference.md:3455 +#: src/api/reference.md:2268 src/api/reference.md:3202 +#: src/api/reference.md:3475 msgid "`inspect` — module" msgstr "`inspect` — Modul" -#: src/api/reference.md:2264 +#: src/api/reference.md:2269 msgid "`list` — module" msgstr "`list` — Modul" -#: src/api/reference.md:2265 +#: src/api/reference.md:2270 msgid "`listImages` — module" msgstr "`listImages` — Modul" -#: src/api/reference.md:2267 +#: src/api/reference.md:2272 msgid "`pullImage` — module" msgstr "`pullImage` — Modul" -#: src/api/reference.md:2268 +#: src/api/reference.md:2273 msgid "`remove` — module" msgstr "`remove` — Modul" -#: src/api/reference.md:2269 +#: src/api/reference.md:2274 msgid "`removeIfExists` — module" msgstr "`removeIfExists` — Modul" -#: src/api/reference.md:2270 +#: src/api/reference.md:2275 msgid "`removeImage` — module" msgstr "`removeImage` — Modul" -#: src/api/reference.md:2271 src/api/reference.md:2498 -#: src/api/reference.md:3231 +#: src/api/reference.md:2276 src/api/reference.md:2503 +#: src/api/reference.md:3240 msgid "`run` — module" msgstr "`run` — Modul" -#: src/api/reference.md:2272 +#: src/api/reference.md:2277 msgid "`selectBackendFor` — module" msgstr "`selectBackendFor` — Modul" -#: src/api/reference.md:2273 +#: src/api/reference.md:2278 msgid "`setBackend` — module" msgstr "`setBackend` — Modul" -#: src/api/reference.md:2274 +#: src/api/reference.md:2279 msgid "`setBackends` — module" msgstr "`setBackends` — Modul" -#: src/api/reference.md:2278 +#: src/api/reference.md:2283 msgid "`perry/container-compose`" msgstr "`perry/container-compose`" -#: src/api/reference.md:2292 +#: src/api/reference.md:2297 msgid "`perry/gc`" msgstr "`perry/gc`" -#: src/api/reference.md:2296 +#: src/api/reference.md:2301 msgid "`collect` — module" msgstr "`collect` — Modul" -#: src/api/reference.md:2297 +#: src/api/reference.md:2302 msgid "`idleHint` — module" msgstr "`idleHint` — Modul" -#: src/api/reference.md:2298 +#: src/api/reference.md:2303 msgid "`minor` — module" msgstr "`minor` — Modul" -#: src/api/reference.md:2300 +#: src/api/reference.md:2305 msgid "`perry/i18n`" msgstr "`perry/i18n`" -#: src/api/reference.md:2304 +#: src/api/reference.md:2309 msgid "`Currency` — module" msgstr "`Currency` — Modul" -#: src/api/reference.md:2305 +#: src/api/reference.md:2310 msgid "`FormatNumber` — module" msgstr "`FormatNumber` — Modul" -#: src/api/reference.md:2306 +#: src/api/reference.md:2311 msgid "`FormatTime` — module" msgstr "`FormatTime` — Modul" -#: src/api/reference.md:2307 +#: src/api/reference.md:2312 msgid "`LongDate` — module" msgstr "`LongDate` — Modul" -#: src/api/reference.md:2308 +#: src/api/reference.md:2313 msgid "`Percent` — module" msgstr "`Percent` — Modul" -#: src/api/reference.md:2309 +#: src/api/reference.md:2314 msgid "`Raw` — module" msgstr "`Raw` — Modul" -#: src/api/reference.md:2310 +#: src/api/reference.md:2315 msgid "`ShortDate` — module" msgstr "`ShortDate` — Modul" -#: src/api/reference.md:2311 +#: src/api/reference.md:2316 msgid "`t` — module" msgstr "`t` — Modul" -#: src/api/reference.md:2313 +#: src/api/reference.md:2318 msgid "`perry/ios`" msgstr "`perry/ios`" -#: src/api/reference.md:2317 +#: src/api/reference.md:2322 msgid "`createLanguageModelSession` — module" msgstr "`createLanguageModelSession` — Modul" -#: src/api/reference.md:2318 +#: src/api/reference.md:2323 msgid "`destroyLanguageModelSession` — module" msgstr "`destroyLanguageModelSession` — Modul" -#: src/api/reference.md:2319 +#: src/api/reference.md:2324 msgid "`foundationModelAvailability` — module" msgstr "`foundationModelAvailability` — Modul" -#: src/api/reference.md:2320 +#: src/api/reference.md:2325 msgid "`getLayoutEnvironment` — module" msgstr "`getLayoutEnvironment` — Modul" -#: src/api/reference.md:2321 +#: src/api/reference.md:2326 msgid "`offLayoutChange` — module" msgstr "`offLayoutChange` — Modul" -#: src/api/reference.md:2322 +#: src/api/reference.md:2327 msgid "`onLayoutChange` — module" msgstr "`onLayoutChange` — Modul" -#: src/api/reference.md:2323 +#: src/api/reference.md:2328 msgid "`respond` — module" msgstr "`respond` — Modul" -#: src/api/reference.md:2325 +#: src/api/reference.md:2330 msgid "`perry/media`" msgstr "`perry/media`" -#: src/api/reference.md:2329 +#: src/api/reference.md:2334 msgid "`createPlayer` — module" msgstr "`createPlayer` — Modul" -#: src/api/reference.md:2330 +#: src/api/reference.md:2335 msgid "`destroy` — module" msgstr "`destroy` — Modul" -#: src/api/reference.md:2331 +#: src/api/reference.md:2336 msgid "`getCurrentTime` — module" msgstr "`getCurrentTime` — Modul" -#: src/api/reference.md:2333 +#: src/api/reference.md:2338 msgid "`getState` — module" msgstr "`getState` — Modul" -#: src/api/reference.md:2335 +#: src/api/reference.md:2340 msgid "`onStateChange` — module" msgstr "`onStateChange` — Modul" -#: src/api/reference.md:2336 +#: src/api/reference.md:2341 msgid "`onTimeUpdate` — module" msgstr "`onTimeUpdate` — Modul" -#: src/api/reference.md:2339 +#: src/api/reference.md:2344 msgid "`seek` — module" msgstr "`seek` — Modul" -#: src/api/reference.md:2340 +#: src/api/reference.md:2345 msgid "`setNowPlaying` — module" msgstr "`setNowPlaying` — Modul" -#: src/api/reference.md:2345 +#: src/api/reference.md:2350 msgid "`perry/native`" msgstr "`perry/native`" -#: src/api/reference.md:2349 +#: src/api/reference.md:2354 msgid "`alignof` — module _(intrinsic)_" msgstr "`alignof` — Modul _(intrinsisch)_" -#: src/api/reference.md:2350 +#: src/api/reference.md:2355 msgid "`f32` — module" msgstr "`f32` — Modul" -#: src/api/reference.md:2351 +#: src/api/reference.md:2356 msgid "`f64` — module" msgstr "`f64` — Modul" -#: src/api/reference.md:2352 +#: src/api/reference.md:2357 msgid "`i16` — module" msgstr "`i16` — Modul" -#: src/api/reference.md:2353 +#: src/api/reference.md:2358 msgid "`i32` — module" msgstr "`i32` — Modul" -#: src/api/reference.md:2354 +#: src/api/reference.md:2359 msgid "`i64` — module" msgstr "`i64` — Modul" -#: src/api/reference.md:2355 +#: src/api/reference.md:2360 msgid "`i8` — module" msgstr "`i8` — Modul" -#: src/api/reference.md:2356 +#: src/api/reference.md:2361 msgid "`isize` — module" msgstr "`isize` — Modul" -#: src/api/reference.md:2357 +#: src/api/reference.md:2362 msgid "`offsetof` — module _(intrinsic)_" msgstr "`offsetof` — Modul _(intrinsisch)_" -#: src/api/reference.md:2358 +#: src/api/reference.md:2363 msgid "`sizeof` — module _(intrinsic)_" msgstr "`sizeof` — Modul _(intrinsisch)_" -#: src/api/reference.md:2359 +#: src/api/reference.md:2364 msgid "`u16` — module" msgstr "`u16` — Modul" -#: src/api/reference.md:2360 +#: src/api/reference.md:2365 msgid "`u32` — module" msgstr "`u32` — Modul" -#: src/api/reference.md:2361 +#: src/api/reference.md:2366 msgid "`u64` — module" msgstr "`u64` — Modul" -#: src/api/reference.md:2362 +#: src/api/reference.md:2367 msgid "`u8` — module" msgstr "`u8` — Modul" -#: src/api/reference.md:2363 +#: src/api/reference.md:2368 msgid "`usize` — module" msgstr "`usize` — Modul" -#: src/api/reference.md:2367 +#: src/api/reference.md:2372 msgid "`NativeArena`" msgstr "`NativeArena`" -#: src/api/reference.md:2369 +#: src/api/reference.md:2374 msgid "`perry/plugin`" msgstr "`perry/plugin`" -#: src/api/reference.md:2373 +#: src/api/reference.md:2378 msgid "`PluginApi`" msgstr "`PluginApi`" -#: src/api/reference.md:2377 +#: src/api/reference.md:2382 msgid "`discoverPlugins` — module" msgstr "`discoverPlugins` — Modul" -#: src/api/reference.md:2378 +#: src/api/reference.md:2383 msgid "`emitEvent` — module" msgstr "`emitEvent` — Modul" -#: src/api/reference.md:2379 +#: src/api/reference.md:2384 msgid "`emitHook` — module" msgstr "`emitHook` — Modul" -#: src/api/reference.md:2380 +#: src/api/reference.md:2385 msgid "`initPlugins` — module" msgstr "`initPlugins` — Modul" -#: src/api/reference.md:2381 +#: src/api/reference.md:2386 msgid "`invokeTool` — module" msgstr "`invokeTool` — Modul" -#: src/api/reference.md:2382 +#: src/api/reference.md:2387 msgid "`listHooks` — module" msgstr "`listHooks` — Modul" -#: src/api/reference.md:2383 +#: src/api/reference.md:2388 msgid "`listPlugins` — module" msgstr "`listPlugins` — Modul" -#: src/api/reference.md:2384 +#: src/api/reference.md:2389 msgid "`listTools` — module" msgstr "`listTools` — Modul" -#: src/api/reference.md:2385 +#: src/api/reference.md:2390 msgid "`loadPlugin` — module" msgstr "`loadPlugin` — Modul" -#: src/api/reference.md:2386 +#: src/api/reference.md:2391 msgid "`pluginCount` — module" msgstr "`pluginCount` — Modul" -#: src/api/reference.md:2387 +#: src/api/reference.md:2392 msgid "`setPluginConfig` — module" msgstr "`setPluginConfig` — Modul" -#: src/api/reference.md:2388 +#: src/api/reference.md:2393 msgid "`unloadPlugin` — module" msgstr "`unloadPlugin` — Modul" -#: src/api/reference.md:2390 +#: src/api/reference.md:2395 msgid "`perry/system`" msgstr "`perry/system`" -#: src/api/reference.md:2394 +#: src/api/reference.md:2399 msgid "`appGetLaunchUrl` — module" msgstr "`appGetLaunchUrl` — Modul" -#: src/api/reference.md:2395 +#: src/api/reference.md:2400 msgid "`appGroupDelete` — module" msgstr "`appGroupDelete` — Modul" -#: src/api/reference.md:2396 +#: src/api/reference.md:2401 msgid "`appGroupGet` — module" msgstr "`appGroupGet` — Modul" -#: src/api/reference.md:2397 +#: src/api/reference.md:2402 msgid "`appGroupSet` — module" msgstr "`appGroupSet` — Modul" -#: src/api/reference.md:2398 +#: src/api/reference.md:2403 msgid "`appOnOpenUrl` — module" msgstr "`appOnOpenUrl` — Modul" -#: src/api/reference.md:2399 +#: src/api/reference.md:2404 msgid "`audioGetLevel` — module" msgstr "`audioGetLevel` — Modul" -#: src/api/reference.md:2400 +#: src/api/reference.md:2405 msgid "`audioGetPeak` — module" msgstr "`audioGetPeak` — Modul" -#: src/api/reference.md:2401 +#: src/api/reference.md:2406 msgid "`audioGetWaveform` — module" msgstr "`audioGetWaveform` — Modul" -#: src/api/reference.md:2402 +#: src/api/reference.md:2407 msgid "`audioRegisterCallback` — module" msgstr "`audioRegisterCallback` — Modul" -#: src/api/reference.md:2403 +#: src/api/reference.md:2408 msgid "`audioSetOutputFilename` — module" msgstr "`audioSetOutputFilename` — Modul" -#: src/api/reference.md:2404 +#: src/api/reference.md:2409 msgid "`audioStart` — module" msgstr "`audioStart` — Modul" -#: src/api/reference.md:2405 +#: src/api/reference.md:2410 msgid "`audioStartRecording` — module" msgstr "`audioStartRecording` — Modul" -#: src/api/reference.md:2406 +#: src/api/reference.md:2411 msgid "`audioStop` — module" msgstr "`audioStop` — Modul" -#: src/api/reference.md:2407 +#: src/api/reference.md:2412 msgid "`audioStopRecording` — module" msgstr "`audioStopRecording` — Modul" -#: src/api/reference.md:2408 +#: src/api/reference.md:2413 msgid "`audioUnregisterCallback` — module" msgstr "`audioUnregisterCallback` — Modul" -#: src/api/reference.md:2409 +#: src/api/reference.md:2414 msgid "`geolocationGetCurrent` — module" msgstr "`geolocationGetCurrent` — Modul" -#: src/api/reference.md:2410 +#: src/api/reference.md:2415 msgid "`geolocationRequestPermission` — module" msgstr "`geolocationRequestPermission` — Modul" -#: src/api/reference.md:2411 +#: src/api/reference.md:2416 msgid "`geolocationStopWatch` — module" msgstr "`geolocationStopWatch` — Modul" -#: src/api/reference.md:2412 +#: src/api/reference.md:2417 msgid "`geolocationWatch` — module" msgstr "`geolocationWatch` — Modul" -#: src/api/reference.md:2413 +#: src/api/reference.md:2418 msgid "`getAppBuildNumber` — module" msgstr "`getAppBuildNumber` — Modul" -#: src/api/reference.md:2414 +#: src/api/reference.md:2419 msgid "`getAppIcon` — module" msgstr "`getAppIcon` — Modul" -#: src/api/reference.md:2415 +#: src/api/reference.md:2420 msgid "`getAppVersion` — module" msgstr "`getAppVersion` — Modul" -#: src/api/reference.md:2416 +#: src/api/reference.md:2421 msgid "`getBundleId` — module" msgstr "`getBundleId` — Modul" -#: src/api/reference.md:2417 +#: src/api/reference.md:2422 msgid "`getDeviceIdiom` — module" msgstr "`getDeviceIdiom` — Modul" -#: src/api/reference.md:2418 +#: src/api/reference.md:2423 msgid "`getDeviceModel` — module" msgstr "`getDeviceModel` — Modul" -#: src/api/reference.md:2419 +#: src/api/reference.md:2424 msgid "`getLocale` — module" msgstr "`getLocale` — Modul" -#: src/api/reference.md:2420 +#: src/api/reference.md:2425 msgid "`getOSVersion` — module" msgstr "`getOSVersion` — Modul" -#: src/api/reference.md:2421 +#: src/api/reference.md:2426 msgid "`getSafeAreaInsets` — module" msgstr "`getSafeAreaInsets` — Modul" -#: src/api/reference.md:2422 +#: src/api/reference.md:2427 msgid "`hapticPlay` — module" msgstr "`hapticPlay` — Modul" -#: src/api/reference.md:2423 +#: src/api/reference.md:2428 msgid "`imagePickerPick` — module" msgstr "`imagePickerPick` — Modul" -#: src/api/reference.md:2424 +#: src/api/reference.md:2429 msgid "`isDarkMode` — module" msgstr "`isDarkMode` — Modul" -#: src/api/reference.md:2425 +#: src/api/reference.md:2430 msgid "`keychainDelete` — module" msgstr "`keychainDelete` — Modul" -#: src/api/reference.md:2426 +#: src/api/reference.md:2431 msgid "`keychainGet` — module" msgstr "`keychainGet` — Modul" -#: src/api/reference.md:2427 +#: src/api/reference.md:2432 msgid "`keychainSave` — module" msgstr "`keychainSave` — Modul" -#: src/api/reference.md:2428 +#: src/api/reference.md:2433 msgid "`networkGetStatus` — module" msgstr "`networkGetStatus` — Modul" -#: src/api/reference.md:2429 +#: src/api/reference.md:2434 msgid "`networkOnChange` — module" msgstr "`networkOnChange` — Modul" -#: src/api/reference.md:2430 +#: src/api/reference.md:2435 msgid "`networkStopOnChange` — module" msgstr "`networkStopOnChange` — Modul" -#: src/api/reference.md:2431 +#: src/api/reference.md:2436 msgid "`notificationCancel` — module" msgstr "`notificationCancel` — Modul" -#: src/api/reference.md:2432 +#: src/api/reference.md:2437 msgid "`notificationOnBackgroundReceive` — module" msgstr "`notificationOnBackgroundReceive` — Modul" -#: src/api/reference.md:2433 +#: src/api/reference.md:2438 msgid "`notificationOnReceive` — module" msgstr "`notificationOnReceive` — Modul" -#: src/api/reference.md:2434 +#: src/api/reference.md:2439 msgid "`notificationOnTap` — module" msgstr "`notificationOnTap` — Modul" -#: src/api/reference.md:2435 +#: src/api/reference.md:2440 msgid "`notificationRegisterRemote` — module" msgstr "`notificationRegisterRemote` — Modul" -#: src/api/reference.md:2436 +#: src/api/reference.md:2441 msgid "`notificationSend` — module" msgstr "`notificationSend` — Modul" -#: src/api/reference.md:2437 +#: src/api/reference.md:2442 msgid "`openURL` — module" msgstr "`openURL` — Modul" -#: src/api/reference.md:2438 +#: src/api/reference.md:2443 msgid "`preferencesGet` — module" msgstr "`preferencesGet` — Modul" -#: src/api/reference.md:2439 +#: src/api/reference.md:2444 msgid "`preferencesSet` — module" msgstr "`preferencesSet` — Modul" -#: src/api/reference.md:2440 +#: src/api/reference.md:2445 msgid "`shareText` — module" msgstr "`shareText` — Modul" -#: src/api/reference.md:2441 +#: src/api/reference.md:2446 msgid "`shareUrl` — module" msgstr "`shareUrl` — Modul" -#: src/api/reference.md:2442 +#: src/api/reference.md:2447 msgid "`takeScreenshot` — module" msgstr "`takeScreenshot` — Modul" -#: src/api/reference.md:2444 +#: src/api/reference.md:2449 msgid "`perry/thread`" msgstr "`perry/thread`" -#: src/api/reference.md:2448 +#: src/api/reference.md:2453 msgid "`parallelFilter` — module" msgstr "`parallelFilter` — Modul" -#: src/api/reference.md:2449 +#: src/api/reference.md:2454 msgid "`parallelMap` — module" msgstr "`parallelMap` — Modul" -#: src/api/reference.md:2456 +#: src/api/reference.md:2461 msgid "`AnimatedSpinner` — module" msgstr "`AnimatedSpinner` — Modul" -#: src/api/reference.md:2457 +#: src/api/reference.md:2462 msgid "`Box` — module" msgstr "`Box` — Modul" -#: src/api/reference.md:2458 +#: src/api/reference.md:2463 msgid "`Input` — module" msgstr "`Input` — Modul" -#: src/api/reference.md:2459 +#: src/api/reference.md:2464 msgid "`InputAt` — module" msgstr "`InputAt` — Modul" -#: src/api/reference.md:2460 +#: src/api/reference.md:2465 msgid "`List` — module" msgstr "`List` — Modul" -#: src/api/reference.md:2461 +#: src/api/reference.md:2466 msgid "`ProgressBar` — module" msgstr "`ProgressBar` — Modul" -#: src/api/reference.md:2462 +#: src/api/reference.md:2467 msgid "`Select` — module" msgstr "`Select` — Modul" -#: src/api/reference.md:2463 src/api/reference.md:2544 +#: src/api/reference.md:2468 src/api/reference.md:2549 msgid "`Spacer` — module" msgstr "`Spacer` — Modul" -#: src/api/reference.md:2464 +#: src/api/reference.md:2469 msgid "`Spinner` — module" msgstr "`Spinner` — Modul" -#: src/api/reference.md:2465 src/api/reference.md:2548 +#: src/api/reference.md:2470 src/api/reference.md:2553 msgid "`Table` — module" msgstr "`Table` — Modul" -#: src/api/reference.md:2466 +#: src/api/reference.md:2471 msgid "`Tabs` — module" msgstr "`Tabs` — Modul" -#: src/api/reference.md:2467 src/api/reference.md:2549 +#: src/api/reference.md:2472 src/api/reference.md:2554 msgid "`Text` — module" msgstr "`Text` — Modul" -#: src/api/reference.md:2468 src/api/reference.md:2550 +#: src/api/reference.md:2473 src/api/reference.md:2555 msgid "`TextArea` — module" msgstr "`TextArea` — Modul" -#: src/api/reference.md:2469 +#: src/api/reference.md:2474 msgid "`TextStyled` — module" msgstr "`TextStyled` — Modul" -#: src/api/reference.md:2470 +#: src/api/reference.md:2475 msgid "`boxSetAlignItems` — module" msgstr "`boxSetAlignItems` — Modul" -#: src/api/reference.md:2471 +#: src/api/reference.md:2476 msgid "`boxSetFlexBasis` — module" msgstr "`boxSetFlexBasis` — Modul" -#: src/api/reference.md:2472 +#: src/api/reference.md:2477 msgid "`boxSetFlexBasisPct` — module" msgstr "`boxSetFlexBasisPct` — Modul" -#: src/api/reference.md:2473 +#: src/api/reference.md:2478 msgid "`boxSetFlexDirection` — module" msgstr "`boxSetFlexDirection` — Modul" -#: src/api/reference.md:2474 +#: src/api/reference.md:2479 msgid "`boxSetFlexGrow` — module" msgstr "`boxSetFlexGrow` — Modul" -#: src/api/reference.md:2475 +#: src/api/reference.md:2480 msgid "`boxSetFlexShrink` — module" msgstr "`boxSetFlexShrink` — Modul" -#: src/api/reference.md:2476 +#: src/api/reference.md:2481 msgid "`boxSetGap` — module" msgstr "`boxSetGap` — Modul" -#: src/api/reference.md:2477 +#: src/api/reference.md:2482 msgid "`boxSetHeight` — module" msgstr "`boxSetHeight` — Modul" -#: src/api/reference.md:2478 +#: src/api/reference.md:2483 msgid "`boxSetHeightPct` — module" msgstr "`boxSetHeightPct` — Modul" -#: src/api/reference.md:2479 +#: src/api/reference.md:2484 msgid "`boxSetJustifyContent` — module" msgstr "`boxSetJustifyContent` — Modul" -#: src/api/reference.md:2480 +#: src/api/reference.md:2485 msgid "`boxSetPadding` — module" msgstr "`boxSetPadding` — Modul" -#: src/api/reference.md:2481 +#: src/api/reference.md:2486 msgid "`boxSetPaddingEach` — module" msgstr "`boxSetPaddingEach` — Modul" -#: src/api/reference.md:2482 +#: src/api/reference.md:2487 msgid "`boxSetWidth` — module" msgstr "`boxSetWidth` — Modul" -#: src/api/reference.md:2483 +#: src/api/reference.md:2488 msgid "`boxSetWidthPct` — module" msgstr "`boxSetWidthPct` — Modul" -#: src/api/reference.md:2484 +#: src/api/reference.md:2489 msgid "`columns` — instance _(class: `TuiStdout`)_" msgstr "`columns` — Instanz _(Klasse: `TuiStdout`)_" -#: src/api/reference.md:2485 +#: src/api/reference.md:2490 msgid "`enter` — module" msgstr "`enter` — Modul" -#: src/api/reference.md:2486 src/api/reference.md:2735 +#: src/api/reference.md:2491 src/api/reference.md:2740 msgid "`exit` — module" msgstr "`exit` — Modul" -#: src/api/reference.md:2487 +#: src/api/reference.md:2492 msgid "`exit` — instance _(class: `TuiApp`)_" msgstr "`exit` — Instanz _(Klasse: `TuiApp`)_" -#: src/api/reference.md:2488 src/api/reference.md:2588 +#: src/api/reference.md:2493 src/api/reference.md:2593 msgid "`focus` — module" msgstr "`focus` — Modul" -#: src/api/reference.md:2489 +#: src/api/reference.md:2494 msgid "`focus` — instance _(class: `FocusManager`)_" msgstr "`focus` — Instanz _(Klasse: `FocusManager`)_" -#: src/api/reference.md:2490 +#: src/api/reference.md:2495 msgid "`focusNext` — module" msgstr "`focusNext` — Modul" -#: src/api/reference.md:2491 +#: src/api/reference.md:2496 msgid "`focusNext` — instance _(class: `FocusManager`)_" msgstr "`focusNext` — Instanz _(Klasse: `FocusManager`)_" -#: src/api/reference.md:2492 +#: src/api/reference.md:2497 msgid "`focusPrevious` — module" msgstr "`focusPrevious` — Modul" -#: src/api/reference.md:2493 +#: src/api/reference.md:2498 msgid "`focusPrevious` — instance _(class: `FocusManager`)_" msgstr "`focusPrevious` — Instanz _(Klasse: `FocusManager`)_" -#: src/api/reference.md:2494 +#: src/api/reference.md:2499 msgid "`get` — instance _(class: `State`)_" msgstr "`get` — Instanz _(Klasse: `State`)_" -#: src/api/reference.md:2495 +#: src/api/reference.md:2500 msgid "`get` — instance _(class: `RefBox`)_" msgstr "`get` — Instanz _(Klasse: `RefBox`)_" -#: src/api/reference.md:2496 +#: src/api/reference.md:2501 msgid "`render` — module" msgstr "`render` — Modul" -#: src/api/reference.md:2497 +#: src/api/reference.md:2502 msgid "`rows` — instance _(class: `TuiStdout`)_" msgstr "`rows` — Instanz _(Klasse: `TuiStdout`)_" -#: src/api/reference.md:2499 +#: src/api/reference.md:2504 msgid "`set` — instance _(class: `State`)_" msgstr "`set` — Instanz _(Klasse: `State`)_" -#: src/api/reference.md:2500 +#: src/api/reference.md:2505 msgid "`set` — instance _(class: `RefBox`)_" msgstr "`set` — Instanz _(Klasse: `RefBox`)_" -#: src/api/reference.md:2501 +#: src/api/reference.md:2506 msgid "`state` — module" msgstr "`state` — Modul" -#: src/api/reference.md:2502 +#: src/api/reference.md:2507 msgid "`useApp` — module" msgstr "`useApp` — Modul" -#: src/api/reference.md:2503 +#: src/api/reference.md:2508 msgid "`useEffect` — module" msgstr "`useEffect` — Modul" -#: src/api/reference.md:2504 +#: src/api/reference.md:2509 msgid "`useFocus` — module" msgstr "`useFocus` — Modul" -#: src/api/reference.md:2505 +#: src/api/reference.md:2510 msgid "`useFocusManager` — module" msgstr "`useFocusManager` — Modul" -#: src/api/reference.md:2506 +#: src/api/reference.md:2511 msgid "`useInput` — module" msgstr "`useInput` — Modul" -#: src/api/reference.md:2507 +#: src/api/reference.md:2512 msgid "`useMemo` — module" msgstr "`useMemo` — Modul" -#: src/api/reference.md:2508 +#: src/api/reference.md:2513 msgid "`useRef` — module" msgstr "`useRef` — Modul" -#: src/api/reference.md:2509 +#: src/api/reference.md:2514 msgid "`useState` — module" msgstr "`useState` — Modul" -#: src/api/reference.md:2510 +#: src/api/reference.md:2515 msgid "`useStateSet` — module" msgstr "`useStateSet` — Modul" -#: src/api/reference.md:2511 +#: src/api/reference.md:2516 msgid "`useStateTuple` — module" msgstr "`useStateTuple` — Modul" -#: src/api/reference.md:2512 +#: src/api/reference.md:2517 msgid "`useStdout` — module" msgstr "`useStdout` — Modul" -#: src/api/reference.md:2513 +#: src/api/reference.md:2518 msgid "`waitUntilExit` — module" msgstr "`waitUntilExit` — Modul" -#: src/api/reference.md:2514 +#: src/api/reference.md:2519 msgid "`waitUntilExit` — instance _(class: `TuiApp`)_" msgstr "`waitUntilExit` — Instanz _(Klasse: `TuiApp`)_" -#: src/api/reference.md:2515 +#: src/api/reference.md:2520 msgid "`write` — instance _(class: `TuiStdout`)_" msgstr "`write` — Instanz _(Klasse: `TuiStdout`)_" -#: src/api/reference.md:2517 +#: src/api/reference.md:2522 msgid "`perry/ui`" msgstr "`perry/ui`" -#: src/api/reference.md:2521 +#: src/api/reference.md:2526 msgid "`App` — module" msgstr "`App` — Modul" -#: src/api/reference.md:2522 +#: src/api/reference.md:2527 msgid "`AttributedText` — module" msgstr "`AttributedText` — Modul" -#: src/api/reference.md:2523 +#: src/api/reference.md:2528 msgid "`BloomView` — module" msgstr "`BloomView` — Modul" -#: src/api/reference.md:2524 +#: src/api/reference.md:2529 msgid "`BottomNavigation` — module" msgstr "`BottomNavigation` — Modul" -#: src/api/reference.md:2525 +#: src/api/reference.md:2530 msgid "`Button` — module" msgstr "`Button` — Modul" -#: src/api/reference.md:2526 +#: src/api/reference.md:2531 msgid "`CameraView` — module" msgstr "`CameraView` — Modul" -#: src/api/reference.md:2527 +#: src/api/reference.md:2532 msgid "`Canvas` — module" msgstr "`Canvas` — Modul" -#: src/api/reference.md:2528 +#: src/api/reference.md:2533 msgid "`Divider` — module" msgstr "`Divider` — Modul" -#: src/api/reference.md:2529 +#: src/api/reference.md:2534 msgid "`ForEach` — module" msgstr "`ForEach` — Modul" -#: src/api/reference.md:2530 +#: src/api/reference.md:2535 msgid "`HStack` — module" msgstr "`HStack` — Modul" -#: src/api/reference.md:2531 +#: src/api/reference.md:2536 msgid "`HStackWithInsets` — module" msgstr "`HStackWithInsets` — Modul" -#: src/api/reference.md:2532 +#: src/api/reference.md:2537 msgid "`Image` — module" msgstr "`Image` — Modul" -#: src/api/reference.md:2533 +#: src/api/reference.md:2538 msgid "`ImageFile` — module" msgstr "`ImageFile` — Modul" -#: src/api/reference.md:2534 +#: src/api/reference.md:2539 msgid "`ImageGallery` — module" msgstr "`ImageGallery` — Modul" -#: src/api/reference.md:2535 +#: src/api/reference.md:2540 msgid "`ImageSymbol` — module" msgstr "`ImageSymbol` — Modul" -#: src/api/reference.md:2536 +#: src/api/reference.md:2541 msgid "`LazyVStack` — module" msgstr "`LazyVStack` — Modul" -#: src/api/reference.md:2537 +#: src/api/reference.md:2542 msgid "`NavStack` — module" msgstr "`NavStack` — Modul" -#: src/api/reference.md:2538 +#: src/api/reference.md:2543 msgid "`Picker` — module" msgstr "`Picker` — Modul" -#: src/api/reference.md:2539 +#: src/api/reference.md:2544 msgid "`ProgressView` — module" msgstr "`ProgressView` — Modul" -#: src/api/reference.md:2540 +#: src/api/reference.md:2545 msgid "`ScrollView` — module" msgstr "`ScrollView` — Modul" -#: src/api/reference.md:2541 +#: src/api/reference.md:2546 msgid "`Section` — module" msgstr "`Section` — Modul" -#: src/api/reference.md:2542 +#: src/api/reference.md:2547 msgid "`SecureField` — module" msgstr "`SecureField` — Modul" -#: src/api/reference.md:2543 +#: src/api/reference.md:2548 msgid "`Slider` — module" msgstr "`Slider` — Modul" -#: src/api/reference.md:2545 +#: src/api/reference.md:2550 msgid "`SplitView` — module" msgstr "`SplitView` — Modul" -#: src/api/reference.md:2546 +#: src/api/reference.md:2551 msgid "`State` — module" msgstr "`State` — Modul" -#: src/api/reference.md:2547 +#: src/api/reference.md:2552 msgid "`TabBar` — module" msgstr "`TabBar` — Modul" -#: src/api/reference.md:2551 +#: src/api/reference.md:2556 msgid "`TextField` — module" msgstr "`TextField` — Modul" -#: src/api/reference.md:2552 +#: src/api/reference.md:2557 msgid "`Toggle` — module" msgstr "`Toggle` — Modul" -#: src/api/reference.md:2553 +#: src/api/reference.md:2558 msgid "`VStack` — module" msgstr "`VStack` — Modul" -#: src/api/reference.md:2554 +#: src/api/reference.md:2559 msgid "`VStackWithInsets` — module" msgstr "`VStackWithInsets` — Modul" -#: src/api/reference.md:2555 +#: src/api/reference.md:2560 msgid "`WebView` — module" msgstr "`WebView` — Modul" -#: src/api/reference.md:2556 +#: src/api/reference.md:2561 msgid "`WheelPicker` — module" msgstr "`WheelPicker` — Modul" -#: src/api/reference.md:2557 +#: src/api/reference.md:2562 msgid "`Window` — module" msgstr "`Window` — Modul" -#: src/api/reference.md:2558 +#: src/api/reference.md:2563 msgid "`ZStack` — module" msgstr "`ZStack` — Modul" -#: src/api/reference.md:2559 +#: src/api/reference.md:2564 msgid "`addKeyboardShortcut` — module" msgstr "`addKeyboardShortcut` — Modul" -#: src/api/reference.md:2560 +#: src/api/reference.md:2565 msgid "`alert` — module" msgstr "`alert` — Modul" -#: src/api/reference.md:2561 +#: src/api/reference.md:2566 msgid "`alertWithButtons` — module" msgstr "`alertWithButtons` — Modul" -#: src/api/reference.md:2562 +#: src/api/reference.md:2567 msgid "`appSetActivationPolicy` — module" msgstr "`appSetActivationPolicy` — Modul" -#: src/api/reference.md:2563 +#: src/api/reference.md:2568 msgid "`appSetMaxSize` — module" msgstr "`appSetMaxSize` — Modul" -#: src/api/reference.md:2564 +#: src/api/reference.md:2569 msgid "`appSetMinSize` — module" msgstr "`appSetMinSize` — Modul" -#: src/api/reference.md:2565 +#: src/api/reference.md:2570 msgid "`appSetTimer` — module" msgstr "`appSetTimer` — Modul" -#: src/api/reference.md:2566 +#: src/api/reference.md:2571 msgid "`attributedTextAppend` — module" msgstr "`attributedTextAppend` — Modul" -#: src/api/reference.md:2567 +#: src/api/reference.md:2572 msgid "`attributedTextClear` — module" msgstr "`attributedTextClear` — Modul" -#: src/api/reference.md:2568 +#: src/api/reference.md:2573 msgid "`bloomViewGetHwnd` — module" msgstr "`bloomViewGetHwnd` — Modul" -#: src/api/reference.md:2569 +#: src/api/reference.md:2574 msgid "`bloomViewGetNativeHandle` — module" msgstr "`bloomViewGetNativeHandle` — Modul" -#: src/api/reference.md:2570 +#: src/api/reference.md:2575 msgid "`blur` — module" msgstr "`blur` — Modul" -#: src/api/reference.md:2571 +#: src/api/reference.md:2576 msgid "`bottomNavAddItem` — module" msgstr "`bottomNavAddItem` — Modul" -#: src/api/reference.md:2572 +#: src/api/reference.md:2577 msgid "`bottomNavSetBadge` — module" msgstr "`bottomNavSetBadge` — Modul" -#: src/api/reference.md:2573 +#: src/api/reference.md:2578 msgid "`bottomNavSetSelected` — module" msgstr "`bottomNavSetSelected` — Modul" -#: src/api/reference.md:2574 +#: src/api/reference.md:2579 msgid "`bottomNavSetTintColor` — module" msgstr "`bottomNavSetTintColor` — Modul" -#: src/api/reference.md:2575 +#: src/api/reference.md:2580 msgid "`bottomNavSetUnselectedTintColor` — module" msgstr "`bottomNavSetUnselectedTintColor` — Modul" -#: src/api/reference.md:2576 +#: src/api/reference.md:2581 msgid "`cameraFreeze` — module" msgstr "`cameraFreeze` — Modul" -#: src/api/reference.md:2577 +#: src/api/reference.md:2582 msgid "`cameraRegisterFrameCallback` — module" msgstr "`cameraRegisterFrameCallback` — Modul" -#: src/api/reference.md:2578 +#: src/api/reference.md:2583 msgid "`cameraSampleColor` — module" msgstr "`cameraSampleColor` — Modul" -#: src/api/reference.md:2579 +#: src/api/reference.md:2584 msgid "`cameraSetOnTap` — module" msgstr "`cameraSetOnTap` — Modul" -#: src/api/reference.md:2580 +#: src/api/reference.md:2585 msgid "`cameraStart` — module" msgstr "`cameraStart` — Modul" -#: src/api/reference.md:2581 +#: src/api/reference.md:2586 msgid "`cameraStop` — module" msgstr "`cameraStop` — Modul" -#: src/api/reference.md:2582 +#: src/api/reference.md:2587 msgid "`cameraUnfreeze` — module" msgstr "`cameraUnfreeze` — Modul" -#: src/api/reference.md:2583 +#: src/api/reference.md:2588 msgid "`cameraUnregisterFrameCallback` — module" msgstr "`cameraUnregisterFrameCallback` — Modul" -#: src/api/reference.md:2584 +#: src/api/reference.md:2589 msgid "`clipboardRead` — module" msgstr "`clipboardRead` — Modul" -#: src/api/reference.md:2585 +#: src/api/reference.md:2590 msgid "`clipboardWrite` — module" msgstr "`clipboardWrite` — Modul" -#: src/api/reference.md:2586 +#: src/api/reference.md:2591 msgid "`currentModifiers` — module" msgstr "`currentModifiers` — Modul" -#: src/api/reference.md:2587 +#: src/api/reference.md:2592 msgid "`embedNSView` — module" msgstr "`embedNSView` — Modul" -#: src/api/reference.md:2589 +#: src/api/reference.md:2594 msgid "`frameSplitAddChild` — module" msgstr "`frameSplitAddChild` — Modul" -#: src/api/reference.md:2590 +#: src/api/reference.md:2595 msgid "`frameSplitCreate` — module" msgstr "`frameSplitCreate` — Modul" -#: src/api/reference.md:2591 +#: src/api/reference.md:2596 msgid "`imageGalleryAddImage` — module" msgstr "`imageGalleryAddImage` — Modul" -#: src/api/reference.md:2592 +#: src/api/reference.md:2597 msgid "`imageGallerySetIndex` — module" msgstr "`imageGallerySetIndex` — Modul" -#: src/api/reference.md:2593 +#: src/api/reference.md:2598 msgid "`isKeyDown` — module" msgstr "`isKeyDown` — Modul" -#: src/api/reference.md:2594 +#: src/api/reference.md:2599 msgid "`lazyvstackEndRefreshing` — module" msgstr "`lazyvstackEndRefreshing` — Modul" -#: src/api/reference.md:2595 +#: src/api/reference.md:2600 msgid "`lazyvstackSetRefreshControl` — module" msgstr "`lazyvstackSetRefreshControl` — Modul" -#: src/api/reference.md:2596 +#: src/api/reference.md:2601 msgid "`lazyvstackSetScrollEndCallback` — module" msgstr "`lazyvstackSetScrollEndCallback` — Modul" -#: src/api/reference.md:2597 +#: src/api/reference.md:2602 msgid "`loadImage` — module" msgstr "`loadImage` — Modul" -#: src/api/reference.md:2598 +#: src/api/reference.md:2603 msgid "`menuAddItem` — module" msgstr "`menuAddItem` — Modul" -#: src/api/reference.md:2599 +#: src/api/reference.md:2604 msgid "`menuAddItemWithShortcut` — module" msgstr "`menuAddItemWithShortcut` — Modul" -#: src/api/reference.md:2600 +#: src/api/reference.md:2605 msgid "`menuAddSeparator` — module" msgstr "`menuAddSeparator` — Modul" -#: src/api/reference.md:2601 +#: src/api/reference.md:2606 msgid "`menuAddStandardAction` — module" msgstr "`menuAddStandardAction` — Modul" -#: src/api/reference.md:2602 +#: src/api/reference.md:2607 msgid "`menuAddSubmenu` — module" msgstr "`menuAddSubmenu` — Modul" -#: src/api/reference.md:2603 +#: src/api/reference.md:2608 msgid "`menuBarAddMenu` — module" msgstr "`menuBarAddMenu` — Modul" -#: src/api/reference.md:2604 +#: src/api/reference.md:2609 msgid "`menuBarAttach` — module" msgstr "`menuBarAttach` — Modul" -#: src/api/reference.md:2605 +#: src/api/reference.md:2610 msgid "`menuBarCreate` — module" msgstr "`menuBarCreate` — Modul" -#: src/api/reference.md:2606 +#: src/api/reference.md:2611 msgid "`menuClear` — module" msgstr "`menuClear` — Modul" -#: src/api/reference.md:2607 +#: src/api/reference.md:2612 msgid "`menuCreate` — module" msgstr "`menuCreate` — Modul" -#: src/api/reference.md:2608 +#: src/api/reference.md:2613 msgid "`onActivate` — module" msgstr "`onActivate` — Modul" -#: src/api/reference.md:2609 +#: src/api/reference.md:2614 msgid "`onAppKeyDown` — module" msgstr "`onAppKeyDown` — Modul" -#: src/api/reference.md:2610 +#: src/api/reference.md:2615 msgid "`onAppKeyUp` — module" msgstr "`onAppKeyUp` — Modul" -#: src/api/reference.md:2611 +#: src/api/reference.md:2616 msgid "`onKeyDown` — module" msgstr "`onKeyDown` — Modul" -#: src/api/reference.md:2612 +#: src/api/reference.md:2617 msgid "`onKeyUp` — module" msgstr "`onKeyUp` — Modul" -#: src/api/reference.md:2613 +#: src/api/reference.md:2618 msgid "`onTerminate` — module" msgstr "`onTerminate` — Modul" -#: src/api/reference.md:2614 +#: src/api/reference.md:2619 msgid "`openFileDialog` — module" msgstr "`openFileDialog` — Modul" -#: src/api/reference.md:2615 +#: src/api/reference.md:2620 msgid "`openFolderDialog` — module" msgstr "`openFolderDialog` — Modul" -#: src/api/reference.md:2616 +#: src/api/reference.md:2621 msgid "`pollOpenFile` — module" msgstr "`pollOpenFile` — Modul" -#: src/api/reference.md:2617 +#: src/api/reference.md:2622 msgid "`registerGlobalHotkey` — module" msgstr "`registerGlobalHotkey` — Modul" -#: src/api/reference.md:2618 +#: src/api/reference.md:2623 msgid "`saveFileDialog` — module" msgstr "`saveFileDialog` — Modul" -#: src/api/reference.md:2619 +#: src/api/reference.md:2624 msgid "`scrollViewSetScrollEndCallback` — module" msgstr "`scrollViewSetScrollEndCallback` — Modul" -#: src/api/reference.md:2620 +#: src/api/reference.md:2625 msgid "`scrollviewSetScrollEndCallback` — module" msgstr "`scrollviewSetScrollEndCallback` — Modul" -#: src/api/reference.md:2621 +#: src/api/reference.md:2626 msgid "`setText` — module" msgstr "`setText` — Modul" -#: src/api/reference.md:2622 +#: src/api/reference.md:2627 msgid "`sheetCreate` — module" msgstr "`sheetCreate` — Modul" -#: src/api/reference.md:2623 +#: src/api/reference.md:2628 msgid "`sheetDismiss` — module" msgstr "`sheetDismiss` — Modul" -#: src/api/reference.md:2624 +#: src/api/reference.md:2629 msgid "`sheetPresent` — module" msgstr "`sheetPresent` — Modul" -#: src/api/reference.md:2625 +#: src/api/reference.md:2630 msgid "`showToast` — module" msgstr "`showToast` — Modul" -#: src/api/reference.md:2626 +#: src/api/reference.md:2631 msgid "`toolbarAddItem` — module" msgstr "`toolbarAddItem` — Modul" -#: src/api/reference.md:2627 +#: src/api/reference.md:2632 msgid "`toolbarAttach` — module" msgstr "`toolbarAttach` — Modul" -#: src/api/reference.md:2628 +#: src/api/reference.md:2633 msgid "`toolbarCreate` — module" msgstr "`toolbarCreate` — Modul" -#: src/api/reference.md:2629 +#: src/api/reference.md:2634 msgid "`trayAttachMenu` — module" msgstr "`trayAttachMenu` — Modul" -#: src/api/reference.md:2630 +#: src/api/reference.md:2635 msgid "`trayCreate` — module" msgstr "`trayCreate` — Modul" -#: src/api/reference.md:2631 +#: src/api/reference.md:2636 msgid "`trayDestroy` — module" msgstr "`trayDestroy` — Modul" -#: src/api/reference.md:2632 +#: src/api/reference.md:2637 msgid "`trayOnClick` — module" msgstr "`trayOnClick` — Modul" -#: src/api/reference.md:2633 +#: src/api/reference.md:2638 msgid "`traySetIcon` — module" msgstr "`traySetIcon` — Modul" -#: src/api/reference.md:2634 +#: src/api/reference.md:2639 msgid "`traySetTooltip` — module" msgstr "`traySetTooltip` — Modul" -#: src/api/reference.md:2635 +#: src/api/reference.md:2640 msgid "`webviewCanGoBack` — module" msgstr "`webviewCanGoBack` — Modul" -#: src/api/reference.md:2636 +#: src/api/reference.md:2641 msgid "`webviewClearCookies` — module" msgstr "`webviewClearCookies` — Modul" -#: src/api/reference.md:2637 +#: src/api/reference.md:2642 msgid "`webviewEvaluateJs` — module" msgstr "`webviewEvaluateJs` — Modul" -#: src/api/reference.md:2638 +#: src/api/reference.md:2643 msgid "`webviewGoBack` — module" msgstr "`webviewGoBack` — Modul" -#: src/api/reference.md:2639 +#: src/api/reference.md:2644 msgid "`webviewGoForward` — module" msgstr "`webviewGoForward` — Modul" -#: src/api/reference.md:2640 +#: src/api/reference.md:2645 msgid "`webviewLoadUrl` — module" msgstr "`webviewLoadUrl` — Modul" -#: src/api/reference.md:2641 +#: src/api/reference.md:2646 msgid "`webviewReload` — module" msgstr "`webviewReload` — Modul" -#: src/api/reference.md:2642 +#: src/api/reference.md:2647 msgid "`wheelPickerAddItem` — module" msgstr "`wheelPickerAddItem` — Modul" -#: src/api/reference.md:2643 +#: src/api/reference.md:2648 msgid "`wheelPickerGetSelected` — module" msgstr "`wheelPickerGetSelected` — Modul" -#: src/api/reference.md:2644 +#: src/api/reference.md:2649 msgid "`wheelPickerSetSelected` — module" msgstr "`wheelPickerSetSelected` — Modul" -#: src/api/reference.md:2646 +#: src/api/reference.md:2651 msgid "`perry/updater`" msgstr "`perry/updater`" -#: src/api/reference.md:2650 +#: src/api/reference.md:2655 msgid "`clearSentinel` — module" msgstr "`clearSentinel` — Modul" -#: src/api/reference.md:2651 +#: src/api/reference.md:2656 msgid "`compareVersions` — module" msgstr "`compareVersions` — Modul" -#: src/api/reference.md:2652 +#: src/api/reference.md:2657 msgid "`computeFileSha256` — module" msgstr "`computeFileSha256` — Modul" -#: src/api/reference.md:2653 +#: src/api/reference.md:2658 msgid "`embeddedCheckHeaders` — module" msgstr "`embeddedCheckHeaders` — Modul" -#: src/api/reference.md:2654 +#: src/api/reference.md:2659 msgid "`embeddedCheckUrl` — module" msgstr "`embeddedCheckUrl` — Modul" -#: src/api/reference.md:2655 +#: src/api/reference.md:2660 msgid "`embeddedRefreshDue` — module" msgstr "`embeddedRefreshDue` — Modul" -#: src/api/reference.md:2656 +#: src/api/reference.md:2661 msgid "`getBackupPath` — module" msgstr "`getBackupPath` — Modul" -#: src/api/reference.md:2657 +#: src/api/reference.md:2662 msgid "`getEmbeddedConfig` — module" msgstr "`getEmbeddedConfig` — Modul" -#: src/api/reference.md:2658 +#: src/api/reference.md:2663 msgid "`getExePath` — module" msgstr "`getExePath` — Modul" -#: src/api/reference.md:2659 +#: src/api/reference.md:2664 msgid "`getSentinelPath` — module" msgstr "`getSentinelPath` — Modul" -#: src/api/reference.md:2660 +#: src/api/reference.md:2665 msgid "`installUpdate` — module" msgstr "`installUpdate` — Modul" -#: src/api/reference.md:2661 +#: src/api/reference.md:2666 msgid "`performRollback` — module" msgstr "`performRollback` — Modul" -#: src/api/reference.md:2662 +#: src/api/reference.md:2667 msgid "`readSentinel` — module" msgstr "`readSentinel` — Modul" -#: src/api/reference.md:2663 +#: src/api/reference.md:2668 msgid "`recordEmbeddedResponse` — module" msgstr "`recordEmbeddedResponse` — Modul" -#: src/api/reference.md:2664 +#: src/api/reference.md:2669 msgid "`relaunch` — module" msgstr "`relaunch` — Modul" -#: src/api/reference.md:2665 +#: src/api/reference.md:2670 msgid "`verifyHash` — module" msgstr "`verifyHash` — Modul" -#: src/api/reference.md:2666 +#: src/api/reference.md:2671 msgid "`verifySignature` — module" msgstr "`verifySignature` — Modul" -#: src/api/reference.md:2667 +#: src/api/reference.md:2672 msgid "`verifySignatureV2` — module" msgstr "`verifySignatureV2` — Modul" -#: src/api/reference.md:2668 +#: src/api/reference.md:2673 msgid "`writeSentinel` — module" msgstr "`writeSentinel` — Modul" -#: src/api/reference.md:2670 +#: src/api/reference.md:2675 msgid "`perry/widget`" msgstr "`perry/widget`" -#: src/api/reference.md:2674 +#: src/api/reference.md:2679 msgid "`Widget` — module" msgstr "`Widget` — Modul" -#: src/api/reference.md:2676 +#: src/api/reference.md:2681 msgid "`perry/workloads`" msgstr "`perry/workloads`" -#: src/api/reference.md:2680 +#: src/api/reference.md:2685 msgid "`graph` — module" msgstr "`graph` — Modul" -#: src/api/reference.md:2681 +#: src/api/reference.md:2686 msgid "`inspectGraph` — module" msgstr "`inspectGraph` — Modul" -#: src/api/reference.md:2682 +#: src/api/reference.md:2687 msgid "`node` — module" msgstr "`node` — Modul" -#: src/api/reference.md:2683 +#: src/api/reference.md:2688 msgid "`runGraph` — module" msgstr "`runGraph` — Modul" -#: src/api/reference.md:2687 +#: src/api/reference.md:2692 msgid "`policy`" msgstr "`policy`" -#: src/api/reference.md:2688 +#: src/api/reference.md:2693 msgid "`runtime`" msgstr "`runtime`" -#: src/api/reference.md:2690 +#: src/api/reference.md:2695 msgid "`perry/yoga`" msgstr "`perry/yoga`" -#: src/api/reference.md:2694 +#: src/api/reference.md:2699 msgid "`calculateLayout` — module" msgstr "`calculateLayout` — Modul" -#: src/api/reference.md:2695 +#: src/api/reference.md:2700 msgid "`childCount` — module" msgstr "`childCount` — Modul" -#: src/api/reference.md:2696 +#: src/api/reference.md:2701 msgid "`getComputed` — module" msgstr "`getComputed` — Modul" -#: src/api/reference.md:2697 +#: src/api/reference.md:2702 msgid "`getComputedEdge` — module" msgstr "`getComputedEdge` — Modul" -#: src/api/reference.md:2698 +#: src/api/reference.md:2703 msgid "`insertChild` — module" msgstr "`insertChild` — Modul" -#: src/api/reference.md:2699 +#: src/api/reference.md:2704 msgid "`nodeFree` — module" msgstr "`nodeFree` — Modul" -#: src/api/reference.md:2700 +#: src/api/reference.md:2705 msgid "`nodeNew` — module" msgstr "`nodeNew` — Modul" -#: src/api/reference.md:2701 +#: src/api/reference.md:2706 msgid "`removeChild` — module" msgstr "`removeChild` — Modul" -#: src/api/reference.md:2702 +#: src/api/reference.md:2707 msgid "`setEdge` — module" msgstr "`setEdge` — Modul" -#: src/api/reference.md:2703 +#: src/api/reference.md:2708 msgid "`setEnum` — module" msgstr "`setEnum` — Modul" -#: src/api/reference.md:2704 +#: src/api/reference.md:2709 msgid "`setGap` — module" msgstr "`setGap` — Modul" -#: src/api/reference.md:2705 +#: src/api/reference.md:2710 msgid "`setMeasureFunc` — module" msgstr "`setMeasureFunc` — Modul" -#: src/api/reference.md:2706 +#: src/api/reference.md:2711 msgid "`setNumber` — module" msgstr "`setNumber` — Modul" -#: src/api/reference.md:2707 +#: src/api/reference.md:2712 msgid "`unsetMeasureFunc` — module" msgstr "`unsetMeasureFunc` — Modul" -#: src/api/reference.md:2709 +#: src/api/reference.md:2714 msgid "`process`" msgstr "`process`" -#: src/api/reference.md:2713 +#: src/api/reference.md:2718 msgid "`_debugEnd` — module" msgstr "`_debugEnd` — Modul" -#: src/api/reference.md:2714 +#: src/api/reference.md:2719 msgid "`_debugProcess` — module" msgstr "`_debugProcess` — Modul" -#: src/api/reference.md:2715 +#: src/api/reference.md:2720 msgid "`_fatalException` — module" msgstr "`_fatalException` — Modul" -#: src/api/reference.md:2716 +#: src/api/reference.md:2721 msgid "`_getActiveHandles` — module" msgstr "`_getActiveHandles` — Modul" -#: src/api/reference.md:2717 +#: src/api/reference.md:2722 msgid "`_getActiveRequests` — module" msgstr "`_getActiveRequests` — Modul" -#: src/api/reference.md:2718 +#: src/api/reference.md:2723 msgid "`_kill` — module" msgstr "`_kill` — Modul" -#: src/api/reference.md:2719 +#: src/api/reference.md:2724 msgid "`_linkedBinding` — module" msgstr "`_linkedBinding` — Modul" -#: src/api/reference.md:2720 +#: src/api/reference.md:2725 msgid "`_rawDebug` — module" msgstr "`_rawDebug` — Modul" -#: src/api/reference.md:2721 +#: src/api/reference.md:2726 msgid "`_startProfilerIdleNotifier` — module" msgstr "`_startProfilerIdleNotifier` — Modul" -#: src/api/reference.md:2722 +#: src/api/reference.md:2727 msgid "`_stopProfilerIdleNotifier` — module" msgstr "`_stopProfilerIdleNotifier` — Modul" -#: src/api/reference.md:2723 +#: src/api/reference.md:2728 msgid "`_tickCallback` — module" msgstr "`_tickCallback` — Modul" -#: src/api/reference.md:2724 +#: src/api/reference.md:2729 msgid "`abort` — module" msgstr "`abort` — Modul" -#: src/api/reference.md:2725 +#: src/api/reference.md:2730 msgid "`addUncaughtExceptionCaptureCallback` — module" msgstr "`addUncaughtExceptionCaptureCallback` — Modul" -#: src/api/reference.md:2726 +#: src/api/reference.md:2731 msgid "`availableMemory` — module" msgstr "`availableMemory` — Modul" -#: src/api/reference.md:2727 +#: src/api/reference.md:2732 msgid "`binding` — module" msgstr "`binding` — Modul" -#: src/api/reference.md:2728 +#: src/api/reference.md:2733 msgid "`chdir` — module" msgstr "`chdir` — Modul" -#: src/api/reference.md:2729 +#: src/api/reference.md:2734 msgid "`constrainedMemory` — module" msgstr "`constrainedMemory` — Modul" -#: src/api/reference.md:2730 +#: src/api/reference.md:2735 msgid "`cpuUsage` — module" msgstr "`cpuUsage` — Modul" -#: src/api/reference.md:2731 +#: src/api/reference.md:2736 msgid "`cwd` — module" msgstr "`cwd` — Modul" -#: src/api/reference.md:2733 +#: src/api/reference.md:2738 msgid "`emitWarning` — module" msgstr "`emitWarning` — Modul" -#: src/api/reference.md:2734 +#: src/api/reference.md:2739 msgid "`execve` — module" msgstr "`execve` — Modul" -#: src/api/reference.md:2736 +#: src/api/reference.md:2741 msgid "`getActiveResourcesInfo` — module" msgstr "`getActiveResourcesInfo` — Modul" -#: src/api/reference.md:2737 +#: src/api/reference.md:2742 msgid "`getBuiltinModule` — module" msgstr "`getBuiltinModule` — Modul" -#: src/api/reference.md:2738 +#: src/api/reference.md:2743 msgid "`getegid` — module" msgstr "`getegid` — Modul" -#: src/api/reference.md:2739 +#: src/api/reference.md:2744 msgid "`geteuid` — module" msgstr "`geteuid` — Modul" -#: src/api/reference.md:2740 +#: src/api/reference.md:2745 msgid "`getgid` — module" msgstr "`getgid` — Modul" -#: src/api/reference.md:2741 +#: src/api/reference.md:2746 msgid "`getgroups` — module" msgstr "`getgroups` — Modul" -#: src/api/reference.md:2742 +#: src/api/reference.md:2747 msgid "`getuid` — module" msgstr "`getuid` — Modul" -#: src/api/reference.md:2743 +#: src/api/reference.md:2748 msgid "`hasUncaughtExceptionCaptureCallback` — module" msgstr "`hasUncaughtExceptionCaptureCallback` — Modul" -#: src/api/reference.md:2744 +#: src/api/reference.md:2749 msgid "`hrtime` — module" msgstr "`hrtime` — Modul" -#: src/api/reference.md:2745 +#: src/api/reference.md:2750 msgid "`initgroups` — module" msgstr "`initgroups` — Modul" -#: src/api/reference.md:2746 +#: src/api/reference.md:2751 msgid "`kill` — module" msgstr "`kill` — Modul" -#: src/api/reference.md:2747 +#: src/api/reference.md:2752 msgid "`loadEnvFile` — module" msgstr "`loadEnvFile` — Modul" -#: src/api/reference.md:2748 +#: src/api/reference.md:2753 msgid "`memoryUsage` — module" msgstr "`memoryUsage` — Modul" -#: src/api/reference.md:2749 +#: src/api/reference.md:2754 msgid "`nextTick` — module" msgstr "`nextTick` — Modul" -#: src/api/reference.md:2750 +#: src/api/reference.md:2755 msgid "`openStdin` — module" msgstr "`openStdin` — Modul" -#: src/api/reference.md:2751 +#: src/api/reference.md:2756 msgid "`reallyExit` — module" msgstr "`reallyExit` — Modul" -#: src/api/reference.md:2752 +#: src/api/reference.md:2757 msgid "`ref` — module" msgstr "`ref` — Modul" -#: src/api/reference.md:2753 +#: src/api/reference.md:2758 msgid "`resourceUsage` — module" msgstr "`resourceUsage` — Modul" -#: src/api/reference.md:2754 src/api/reference.md:2755 +#: src/api/reference.md:2759 src/api/reference.md:2760 msgid "`setSourceMapsEnabled` — module" msgstr "`setSourceMapsEnabled` — Modul" -#: src/api/reference.md:2756 +#: src/api/reference.md:2761 msgid "`setUncaughtExceptionCaptureCallback` — module" msgstr "`setUncaughtExceptionCaptureCallback` — Modul" -#: src/api/reference.md:2757 +#: src/api/reference.md:2762 msgid "`setegid` — module" msgstr "`setegid` — Modul" -#: src/api/reference.md:2758 +#: src/api/reference.md:2763 msgid "`seteuid` — module" msgstr "`seteuid` — Modul" -#: src/api/reference.md:2759 +#: src/api/reference.md:2764 msgid "`setgid` — module" msgstr "`setgid` — Modul" -#: src/api/reference.md:2760 +#: src/api/reference.md:2765 msgid "`setgroups` — module" msgstr "`setgroups` — Modul" -#: src/api/reference.md:2761 +#: src/api/reference.md:2766 msgid "`setuid` — module" msgstr "`setuid` — Modul" -#: src/api/reference.md:2762 src/api/reference.md:2763 +#: src/api/reference.md:2767 src/api/reference.md:2768 msgid "`sourceMapsEnabled` — module" msgstr "`sourceMapsEnabled` — Modul" -#: src/api/reference.md:2764 +#: src/api/reference.md:2769 msgid "`threadCpuUsage` — module" msgstr "`threadCpuUsage` — Modul" -#: src/api/reference.md:2765 +#: src/api/reference.md:2770 msgid "`umask` — module" msgstr "`umask` — Modul" -#: src/api/reference.md:2766 +#: src/api/reference.md:2771 msgid "`unref` — module" msgstr "`unref` — Modul" -#: src/api/reference.md:2771 +#: src/api/reference.md:2776 msgid "`_eval`" msgstr "`_eval`" -#: src/api/reference.md:2772 +#: src/api/reference.md:2777 msgid "`_events`" msgstr "`_events`" -#: src/api/reference.md:2773 +#: src/api/reference.md:2778 msgid "`_eventsCount`" msgstr "`_eventsCount`" -#: src/api/reference.md:2774 +#: src/api/reference.md:2779 msgid "`_exiting`" msgstr "`_exiting`" -#: src/api/reference.md:2775 +#: src/api/reference.md:2780 msgid "`_maxListeners`" msgstr "`_maxListeners`" -#: src/api/reference.md:2776 +#: src/api/reference.md:2781 msgid "`_preload_modules`" msgstr "`_preload_modules`" -#: src/api/reference.md:2777 +#: src/api/reference.md:2782 msgid "`allowedNodeEnvironmentFlags`" msgstr "`allowedNodeEnvironmentFlags`" -#: src/api/reference.md:2778 +#: src/api/reference.md:2783 msgid "`arch`" msgstr "`arch`" -#: src/api/reference.md:2779 +#: src/api/reference.md:2784 msgid "`argv`" msgstr "`argv`" -#: src/api/reference.md:2780 +#: src/api/reference.md:2785 msgid "`argv0`" msgstr "`argv0`" -#: src/api/reference.md:2781 src/widgets/creating-widgets.md:55 +#: src/api/reference.md:2786 src/widgets/creating-widgets.md:55 msgid "`config`" msgstr "`config`" -#: src/api/reference.md:2782 +#: src/api/reference.md:2787 msgid "`debugPort`" msgstr "`debugPort`" -#: src/api/reference.md:2784 src/container/containers.md:52 +#: src/api/reference.md:2789 src/container/containers.md:52 msgid "`env`" msgstr "`env`" -#: src/api/reference.md:2785 +#: src/api/reference.md:2790 msgid "`execArgv`" msgstr "`execArgv`" -#: src/api/reference.md:2786 +#: src/api/reference.md:2791 msgid "`execPath`" msgstr "`execPath`" -#: src/api/reference.md:2787 +#: src/api/reference.md:2792 msgid "`features`" msgstr "`features`" -#: src/api/reference.md:2788 +#: src/api/reference.md:2793 msgid "`finalization`" msgstr "`finalization`" -#: src/api/reference.md:2789 +#: src/api/reference.md:2794 msgid "`moduleLoadList`" msgstr "`moduleLoadList`" -#: src/api/reference.md:2790 +#: src/api/reference.md:2795 msgid "`permission`" msgstr "`permission`" -#: src/api/reference.md:2791 +#: src/api/reference.md:2796 msgid "`pid`" msgstr "`pid`" -#: src/api/reference.md:2792 +#: src/api/reference.md:2797 msgid "`platform`" msgstr "`platform`" -#: src/api/reference.md:2793 +#: src/api/reference.md:2798 msgid "`ppid`" msgstr "`ppid`" -#: src/api/reference.md:2794 src/contributing/building.md:59 +#: src/api/reference.md:2799 src/contributing/building.md:59 msgid "`release`" msgstr "`release`" -#: src/api/reference.md:2795 +#: src/api/reference.md:2800 msgid "`report`" msgstr "`report`" -#: src/api/reference.md:2801 +#: src/api/reference.md:2806 msgid "`versions`" msgstr "`versions`" -#: src/api/reference.md:2803 +#: src/api/reference.md:2808 msgid "`punycode`" msgstr "`punycode`" -#: src/api/reference.md:2807 src/api/reference.md:2822 +#: src/api/reference.md:2812 src/api/reference.md:2827 msgid "`decode` — module" msgstr "`decode` — Modul" -#: src/api/reference.md:2808 src/api/reference.md:2823 +#: src/api/reference.md:2813 src/api/reference.md:2828 msgid "`encode` — module" msgstr "`encode` — Modul" -#: src/api/reference.md:2809 +#: src/api/reference.md:2814 msgid "`toASCII` — module" msgstr "`toASCII` — Modul" -#: src/api/reference.md:2810 +#: src/api/reference.md:2815 msgid "`toUnicode` — module" msgstr "`toUnicode` — Modul" -#: src/api/reference.md:2815 +#: src/api/reference.md:2820 msgid "`ucs2`" msgstr "`ucs2`" -#: src/api/reference.md:2818 +#: src/api/reference.md:2823 msgid "`querystring`" msgstr "`querystring`" -#: src/api/reference.md:2824 +#: src/api/reference.md:2829 msgid "`escape` — module" msgstr "`escape` — Modul" -#: src/api/reference.md:2826 +#: src/api/reference.md:2831 msgid "`stringify` — module" msgstr "`stringify` — Modul" -#: src/api/reference.md:2827 +#: src/api/reference.md:2832 msgid "`unescape` — module" msgstr "`unescape` — Modul" -#: src/api/reference.md:2828 +#: src/api/reference.md:2833 msgid "`unescapeBuffer` — module" msgstr "`unescapeBuffer` — Modul" -#: src/api/reference.md:2834 +#: src/api/reference.md:2839 msgid "`readline`" msgstr "`readline`" -#: src/api/reference.md:2838 +#: src/api/reference.md:2843 msgid "`clearLine` — module" msgstr "`clearLine` — Modul" -#: src/api/reference.md:2839 +#: src/api/reference.md:2844 msgid "`clearScreenDown` — module" msgstr "`clearScreenDown` — Modul" -#: src/api/reference.md:2841 src/api/reference.md:2868 +#: src/api/reference.md:2846 src/api/reference.md:2877 msgid "`createInterface` — module" msgstr "`createInterface` — Modul" -#: src/api/reference.md:2842 +#: src/api/reference.md:2847 msgid "`cursorTo` — module" msgstr "`cursorTo` — Modul" -#: src/api/reference.md:2843 +#: src/api/reference.md:2848 msgid "`emitKeypressEvents` — module" msgstr "`emitKeypressEvents` — Modul" -#: src/api/reference.md:2844 +#: src/api/reference.md:2849 msgid "`getCursorPos` — instance" msgstr "`getCursorPos` — Instanz" -#: src/api/reference.md:2845 +#: src/api/reference.md:2850 msgid "`getPrompt` — instance" msgstr "`getPrompt` — Instanz" -#: src/api/reference.md:2846 +#: src/api/reference.md:2851 msgid "`iterator` — instance" msgstr "`iterator` — Instanz" -#: src/api/reference.md:2847 +#: src/api/reference.md:2852 msgid "`line` — instance" msgstr "`line` — Instanz" -#: src/api/reference.md:2848 +#: src/api/reference.md:2853 msgid "`moveCursor` — module" msgstr "`moveCursor` — Modul" -#: src/api/reference.md:2850 src/api/reference.md:3048 +#: src/api/reference.md:2855 src/api/reference.md:3057 msgid "`pause` — instance" msgstr "`pause` — Instanz" -#: src/api/reference.md:2851 +#: src/api/reference.md:2856 msgid "`prompt` — instance" msgstr "`prompt` — Instanz" -#: src/api/reference.md:2852 src/api/reference.md:2869 +#: src/api/reference.md:2857 src/api/reference.md:2878 msgid "`question` — instance" msgstr "`question` — Instanz" -#: src/api/reference.md:2853 src/api/reference.md:3067 +#: src/api/reference.md:2858 src/api/reference.md:3076 msgid "`resume` — instance" msgstr "`resume` — Instanz" -#: src/api/reference.md:2854 +#: src/api/reference.md:2859 msgid "`setPrompt` — instance" msgstr "`setPrompt` — Instanz" -#: src/api/reference.md:2855 +#: src/api/reference.md:2860 msgid "`terminal` — instance" msgstr "`terminal` — Instanz" -#: src/api/reference.md:2856 src/api/reference.md:3082 +#: src/api/reference.md:2861 src/api/reference.md:3091 msgid "`write` — instance" msgstr "`write` — Instanz" -#: src/api/reference.md:2858 +#: src/api/reference.md:2867 msgid "`readline/promises`" msgstr "`readline/promises`" -#: src/api/reference.md:2862 +#: src/api/reference.md:2871 msgid "`Interface`" msgstr "`Interface`" -#: src/api/reference.md:2863 +#: src/api/reference.md:2872 msgid "`Readline`" msgstr "`Readline`" -#: src/api/reference.md:2871 +#: src/api/reference.md:2880 msgid "`repl`" msgstr "`repl`" -#: src/api/reference.md:2875 +#: src/api/reference.md:2884 msgid "`REPLServer`" msgstr "`REPLServer`" -#: src/api/reference.md:2876 +#: src/api/reference.md:2885 msgid "`Recoverable`" msgstr "`Recoverable`" -#: src/api/reference.md:2880 +#: src/api/reference.md:2889 msgid "" "`REPLServer` — module ⚠ **stub** — REPLServer shape only: never reads the " "input stream, and .write() evaluates just numeric literals, context lookups, " @@ -44197,43 +43948,43 @@ msgstr "" "nie und .write() wertet nur numerische Literatur, Kontext-Suchen und eine " "einzelne '+' aus; keine echte JS-Eval-Loop (#4916)" -#: src/api/reference.md:2881 +#: src/api/reference.md:2890 msgid "`Recoverable` — module" msgstr "`Recoverable` — Modul" -#: src/api/reference.md:2882 +#: src/api/reference.md:2891 msgid "`addListener` — instance _(class: `REPLServer`)_" msgstr "`addListener` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2883 +#: src/api/reference.md:2892 msgid "`clearBufferedCommand` — instance _(class: `REPLServer`)_" msgstr "`clearBufferedCommand` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2884 +#: src/api/reference.md:2893 msgid "`defineCommand` — instance _(class: `REPLServer`)_" msgstr "`defineCommand` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2885 +#: src/api/reference.md:2894 msgid "`displayPrompt` — instance _(class: `REPLServer`)_" msgstr "`displayPrompt` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2886 +#: src/api/reference.md:2895 msgid "`emit` — instance _(class: `REPLServer`)_" msgstr "`emit` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2887 +#: src/api/reference.md:2896 msgid "`on` — instance _(class: `REPLServer`)_" msgstr "`on` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2888 +#: src/api/reference.md:2897 msgid "`once` — instance _(class: `REPLServer`)_" msgstr "`once` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2889 +#: src/api/reference.md:2898 msgid "`setupHistory` — instance _(class: `REPLServer`)_" msgstr "`setupHistory` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2890 +#: src/api/reference.md:2899 msgid "" "`start` — module ⚠ **stub** — REPLServer shape only: never reads the input " "stream, and .write() evaluates just numeric literals, context lookups, and a " @@ -44243,1411 +43994,1426 @@ msgstr "" "und .write() wertet nur numerische Literatur, Kontext-Suchen und eine " "einzelne '+' aus; keine echte JS-Eval-Loop (#4916)" -#: src/api/reference.md:2891 +#: src/api/reference.md:2900 msgid "`write` — instance _(class: `REPLServer`)_" msgstr "`write` — Instanz _(Klasse: `REPLServer`)_" -#: src/api/reference.md:2895 +#: src/api/reference.md:2904 msgid "`REPL_MODE_SLOPPY`" msgstr "`REPL_MODE_SLOPPY`" -#: src/api/reference.md:2896 +#: src/api/reference.md:2905 msgid "`REPL_MODE_STRICT`" msgstr "`REPL_MODE_STRICT`" -#: src/api/reference.md:2900 +#: src/api/reference.md:2909 msgid "`sea`" msgstr "`sea`" -#: src/api/reference.md:2904 +#: src/api/reference.md:2913 msgid "`getAsset` — module" msgstr "`getAsset` — Modul" -#: src/api/reference.md:2905 +#: src/api/reference.md:2914 msgid "`getAssetAsBlob` — module" msgstr "`getAssetAsBlob` — Modul" -#: src/api/reference.md:2906 +#: src/api/reference.md:2915 msgid "`getAssetKeys` — module" msgstr "`getAssetKeys` — Modul" -#: src/api/reference.md:2907 +#: src/api/reference.md:2916 msgid "`getRawAsset` — module" msgstr "`getRawAsset` — Modul" -#: src/api/reference.md:2908 +#: src/api/reference.md:2917 msgid "`isSea` — module" msgstr "`isSea` — Modul" -#: src/api/reference.md:2918 +#: src/api/reference.md:2927 msgid "`autoOrient` — instance" msgstr "`autoOrient` — Instanz" -#: src/api/reference.md:2919 +#: src/api/reference.md:2928 msgid "`avif` — instance" msgstr "`avif` — Instanz" -#: src/api/reference.md:2920 +#: src/api/reference.md:2929 msgid "`blur` — instance" msgstr "`blur` — Instanz" -#: src/api/reference.md:2921 +#: src/api/reference.md:2930 msgid "`composite` — instance" msgstr "`composite` — Instanz" -#: src/api/reference.md:2923 +#: src/api/reference.md:2932 msgid "`extend` — instance" msgstr "`extend` — Instanz" -#: src/api/reference.md:2924 +#: src/api/reference.md:2933 msgid "`extract` — instance" msgstr "`extract` — Instanz" -#: src/api/reference.md:2925 +#: src/api/reference.md:2934 msgid "`flip` — instance" msgstr "`flip` — Instanz" -#: src/api/reference.md:2926 +#: src/api/reference.md:2935 msgid "`flop` — instance" msgstr "`flop` — Instanz" -#: src/api/reference.md:2927 +#: src/api/reference.md:2936 msgid "`grayscale` — instance" msgstr "`grayscale` — Instanz" -#: src/api/reference.md:2928 +#: src/api/reference.md:2937 msgid "`height` — instance" msgstr "`height` — Instanz" -#: src/api/reference.md:2929 +#: src/api/reference.md:2938 msgid "`jpeg` — instance" msgstr "`jpeg` — Instanz" -#: src/api/reference.md:2930 +#: src/api/reference.md:2939 msgid "`metadata` — instance" msgstr "`metadata` — Instanz" -#: src/api/reference.md:2931 +#: src/api/reference.md:2940 msgid "`png` — instance" msgstr "`png` — Instanz" -#: src/api/reference.md:2932 +#: src/api/reference.md:2941 msgid "`resize` — instance" msgstr "`resize` — Instanz" -#: src/api/reference.md:2933 +#: src/api/reference.md:2942 msgid "`rotate` — instance" msgstr "`rotate` — Instanz" -#: src/api/reference.md:2934 +#: src/api/reference.md:2943 msgid "`sharp` — module" msgstr "`sharp` — Modul" -#: src/api/reference.md:2935 +#: src/api/reference.md:2944 msgid "`sharpen` — instance" msgstr "`sharpen` — Instanz" -#: src/api/reference.md:2936 +#: src/api/reference.md:2945 msgid "`toBuffer` — instance" msgstr "`toBuffer` — Instanz" -#: src/api/reference.md:2937 +#: src/api/reference.md:2946 msgid "`toFile` — instance" msgstr "`toFile` — Instanz" -#: src/api/reference.md:2938 +#: src/api/reference.md:2947 msgid "`trim` — instance" msgstr "`trim` — Instanz" -#: src/api/reference.md:2939 +#: src/api/reference.md:2948 msgid "`webp` — instance" msgstr "`webp` — Instanz" -#: src/api/reference.md:2940 +#: src/api/reference.md:2949 msgid "`width` — instance" msgstr "`width` — Instanz" -#: src/api/reference.md:2942 +#: src/api/reference.md:2951 msgid "`sqlite`" msgstr "`sqlite`" -#: src/api/reference.md:2946 +#: src/api/reference.md:2955 msgid "`DatabaseSync`" msgstr "`DatabaseSync`" -#: src/api/reference.md:2947 +#: src/api/reference.md:2956 msgid "`SQLTagStore`" msgstr "`SQLTagStore`" -#: src/api/reference.md:2949 +#: src/api/reference.md:2958 msgid "`StatementSync`" msgstr "`StatementSync`" -#: src/api/reference.md:2953 +#: src/api/reference.md:2962 msgid "`@@__perry_wk_dispose` — instance" msgstr "`@@__perry_wk_dispose` — Instanz" -#: src/api/reference.md:2954 +#: src/api/reference.md:2963 msgid "`DatabaseSync` — module" msgstr "`DatabaseSync` — Modul" -#: src/api/reference.md:2956 +#: src/api/reference.md:2965 msgid "`StatementSync` — module" msgstr "`StatementSync` — Modul" -#: src/api/reference.md:2957 +#: src/api/reference.md:2966 msgid "`__perry_dispose__` — instance" msgstr "`__perry_dispose__` — Instanz" -#: src/api/reference.md:2958 +#: src/api/reference.md:2967 msgid "`aggregate` — instance _(class: `DatabaseSync`)_" msgstr "`aggregate` — Instanz _(Klasse: `DatabaseSync`)_" -#: src/api/reference.md:2959 +#: src/api/reference.md:2968 msgid "`all` — instance _(class: `SQLTagStore`)_" msgstr "`all` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2961 +#: src/api/reference.md:2970 msgid "`applyChangeset` — instance" msgstr "`applyChangeset` — Instanz" -#: src/api/reference.md:2962 +#: src/api/reference.md:2971 msgid "`backup` — module" msgstr "`backup` — Modul" -#: src/api/reference.md:2963 +#: src/api/reference.md:2972 msgid "`capacity` — instance _(class: `SQLTagStore`)_" msgstr "`capacity` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2964 +#: src/api/reference.md:2973 msgid "`changeset` — instance" msgstr "`changeset` — Instanz" -#: src/api/reference.md:2965 +#: src/api/reference.md:2974 msgid "`clear` — instance _(class: `SQLTagStore`)_" msgstr "`clear` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2968 +#: src/api/reference.md:2977 msgid "`createSession` — instance" msgstr "`createSession` — Instanz" -#: src/api/reference.md:2969 +#: src/api/reference.md:2978 msgid "`createTagStore` — instance _(class: `DatabaseSync`)_" msgstr "`createTagStore` — Instanz _(Klasse: `DatabaseSync`)_" -#: src/api/reference.md:2970 +#: src/api/reference.md:2979 msgid "`db` — instance _(class: `SQLTagStore`)_" msgstr "`db` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2971 +#: src/api/reference.md:2980 msgid "`deserialize` — instance" msgstr "`deserialize` — Instanz" -#: src/api/reference.md:2972 +#: src/api/reference.md:2981 msgid "`enableDefensive` — instance _(class: `DatabaseSync`)_" msgstr "`enableDefensive` — Instanz _(Klasse: `DatabaseSync`)_" -#: src/api/reference.md:2973 +#: src/api/reference.md:2982 msgid "`enableLoadExtension` — instance" msgstr "`enableLoadExtension` — Instanz" -#: src/api/reference.md:2975 +#: src/api/reference.md:2984 msgid "`expandedSQL` — instance" msgstr "`expandedSQL` — Instanz" -#: src/api/reference.md:2976 +#: src/api/reference.md:2985 msgid "`function` — instance _(class: `DatabaseSync`)_" msgstr "`function` — Instanz _(Klasse: `DatabaseSync`)_" -#: src/api/reference.md:2977 +#: src/api/reference.md:2986 msgid "`get` — instance _(class: `SQLTagStore`)_" msgstr "`get` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2979 +#: src/api/reference.md:2988 msgid "`isOpen` — instance" msgstr "`isOpen` — Instanz" -#: src/api/reference.md:2980 +#: src/api/reference.md:2989 msgid "`isTransaction` — instance" msgstr "`isTransaction` — Instanz" -#: src/api/reference.md:2981 +#: src/api/reference.md:2990 msgid "`iterate` — instance _(class: `SQLTagStore`)_" msgstr "`iterate` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2983 +#: src/api/reference.md:2992 msgid "`limits` — instance" msgstr "`limits` — Instanz" -#: src/api/reference.md:2984 +#: src/api/reference.md:2993 msgid "`loadExtension` — instance" msgstr "`loadExtension` — Instanz" -#: src/api/reference.md:2985 +#: src/api/reference.md:2994 msgid "`location` — instance" msgstr "`location` — Instanz" -#: src/api/reference.md:2986 +#: src/api/reference.md:2995 msgid "`open` — instance" msgstr "`open` — Instanz" -#: src/api/reference.md:2987 +#: src/api/reference.md:2996 msgid "`patchset` — instance" msgstr "`patchset` — Instanz" -#: src/api/reference.md:2989 +#: src/api/reference.md:2998 msgid "`run` — instance _(class: `SQLTagStore`)_" msgstr "`run` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2991 +#: src/api/reference.md:3000 msgid "`serialize` — instance" msgstr "`serialize` — Instanz" -#: src/api/reference.md:2992 +#: src/api/reference.md:3001 msgid "`setAllowBareNamedParameters` — instance" msgstr "`setAllowBareNamedParameters` — Instanz" -#: src/api/reference.md:2993 +#: src/api/reference.md:3002 msgid "`setAllowUnknownNamedParameters` — instance" msgstr "`setAllowUnknownNamedParameters` — Instanz" -#: src/api/reference.md:2994 +#: src/api/reference.md:3003 msgid "`setAuthorizer` — instance _(class: `DatabaseSync`)_" msgstr "`setAuthorizer` — Instanz _(Klasse: `DatabaseSync`)_" -#: src/api/reference.md:2995 +#: src/api/reference.md:3004 msgid "`setReadBigInts` — instance" msgstr "`setReadBigInts` — Instanz" -#: src/api/reference.md:2996 +#: src/api/reference.md:3005 msgid "`setReturnArrays` — instance" msgstr "`setReturnArrays` — Instanz" -#: src/api/reference.md:2997 +#: src/api/reference.md:3006 msgid "`size` — instance _(class: `SQLTagStore`)_" msgstr "`size` — Instanz _(Klasse: `SQLTagStore`)_" -#: src/api/reference.md:2998 +#: src/api/reference.md:3007 msgid "`sourceSQL` — instance" msgstr "`sourceSQL` — Instanz" -#: src/api/reference.md:3004 +#: src/api/reference.md:3013 msgid "`stream`" msgstr "`stream`" -#: src/api/reference.md:3008 +#: src/api/reference.md:3017 msgid "`Duplex`" msgstr "`Duplex`" -#: src/api/reference.md:3009 +#: src/api/reference.md:3018 msgid "`PassThrough`" msgstr "`PassThrough`" -#: src/api/reference.md:3010 +#: src/api/reference.md:3019 msgid "`Readable`" msgstr "`Readable`" -#: src/api/reference.md:3012 +#: src/api/reference.md:3021 msgid "`Transform`" msgstr "`Transform`" -#: src/api/reference.md:3013 +#: src/api/reference.md:3022 msgid "`Writable`" msgstr "`Writable`" -#: src/api/reference.md:3017 +#: src/api/reference.md:3026 msgid "`_isArrayBufferView` — module" msgstr "`_isArrayBufferView` — Modul" -#: src/api/reference.md:3018 +#: src/api/reference.md:3027 msgid "`_isUint8Array` — module" msgstr "`_isUint8Array` — Modul" -#: src/api/reference.md:3019 +#: src/api/reference.md:3028 msgid "`_uint8ArrayToBuffer` — module" msgstr "`_uint8ArrayToBuffer` — Modul" -#: src/api/reference.md:3020 +#: src/api/reference.md:3029 msgid "`addAbortSignal` — module" msgstr "`addAbortSignal` — Modul" -#: src/api/reference.md:3022 +#: src/api/reference.md:3031 msgid "`allowHalfOpen` — instance" msgstr "`allowHalfOpen` — Instanz" -#: src/api/reference.md:3023 +#: src/api/reference.md:3032 msgid "`closed` — instance" msgstr "`closed` — Instanz" -#: src/api/reference.md:3024 +#: src/api/reference.md:3033 msgid "`compose` — module" msgstr "`compose` — Modul" -#: src/api/reference.md:3025 +#: src/api/reference.md:3034 msgid "`cork` — instance" msgstr "`cork` — Instanz" -#: src/api/reference.md:3027 +#: src/api/reference.md:3036 msgid "`destroy` — instance" msgstr "`destroy` — Instanz" -#: src/api/reference.md:3028 +#: src/api/reference.md:3037 msgid "`destroyed` — instance" msgstr "`destroyed` — Instanz" -#: src/api/reference.md:3029 +#: src/api/reference.md:3038 msgid "`duplexPair` — module" msgstr "`duplexPair` — Modul" -#: src/api/reference.md:3031 +#: src/api/reference.md:3040 msgid "`end` — instance" msgstr "`end` — Instanz" -#: src/api/reference.md:3032 +#: src/api/reference.md:3041 msgid "`errored` — instance" msgstr "`errored` — Instanz" -#: src/api/reference.md:3034 src/api/reference.md:3108 -#: src/api/reference.md:3109 +#: src/api/reference.md:3043 src/api/reference.md:3117 +#: src/api/reference.md:3118 msgid "`finished` — module" msgstr "`finished` — Modul" -#: src/api/reference.md:3035 +#: src/api/reference.md:3044 msgid "`getDefaultHighWaterMark` — module" msgstr "`getDefaultHighWaterMark` — Modul" -#: src/api/reference.md:3037 +#: src/api/reference.md:3046 msgid "`isDestroyed` — module" msgstr "`isDestroyed` — Modul" -#: src/api/reference.md:3038 +#: src/api/reference.md:3047 msgid "`isDisturbed` — module" msgstr "`isDisturbed` — Modul" -#: src/api/reference.md:3039 +#: src/api/reference.md:3048 msgid "`isErrored` — module" msgstr "`isErrored` — Modul" -#: src/api/reference.md:3040 +#: src/api/reference.md:3049 msgid "`isPaused` — instance" msgstr "`isPaused` — Instanz" -#: src/api/reference.md:3041 +#: src/api/reference.md:3050 msgid "`isReadable` — module" msgstr "`isReadable` — Modul" -#: src/api/reference.md:3042 +#: src/api/reference.md:3051 msgid "`isWritable` — module" msgstr "`isWritable` — Modul" -#: src/api/reference.md:3049 +#: src/api/reference.md:3058 msgid "`pipe` — instance" msgstr "`pipe` — Instanz" -#: src/api/reference.md:3050 src/api/reference.md:3110 -#: src/api/reference.md:3111 +#: src/api/reference.md:3059 src/api/reference.md:3119 +#: src/api/reference.md:3120 msgid "`pipeline` — module" msgstr "`pipeline` — Modul" -#: src/api/reference.md:3053 +#: src/api/reference.md:3062 msgid "`push` — instance" msgstr "`push` — Instanz" -#: src/api/reference.md:3055 +#: src/api/reference.md:3064 msgid "`read` — instance" msgstr "`read` — Instanz" -#: src/api/reference.md:3057 +#: src/api/reference.md:3066 msgid "`readableAborted` — instance" msgstr "`readableAborted` — Instanz" -#: src/api/reference.md:3058 +#: src/api/reference.md:3067 msgid "`readableDidRead` — instance" msgstr "`readableDidRead` — Instanz" -#: src/api/reference.md:3059 +#: src/api/reference.md:3068 msgid "`readableEncoding` — instance" msgstr "`readableEncoding` — Instanz" -#: src/api/reference.md:3061 +#: src/api/reference.md:3070 msgid "`readableFlowing` — instance" msgstr "`readableFlowing` — Instanz" -#: src/api/reference.md:3062 +#: src/api/reference.md:3071 msgid "`readableHighWaterMark` — instance" msgstr "`readableHighWaterMark` — Instanz" -#: src/api/reference.md:3063 +#: src/api/reference.md:3072 msgid "`readableLength` — instance" msgstr "`readableLength` — Instanz" -#: src/api/reference.md:3064 +#: src/api/reference.md:3073 msgid "`readableObjectMode` — instance" msgstr "`readableObjectMode` — Instanz" -#: src/api/reference.md:3068 +#: src/api/reference.md:3077 msgid "`setDefaultHighWaterMark` — module" msgstr "`setDefaultHighWaterMark` — Modul" -#: src/api/reference.md:3069 +#: src/api/reference.md:3078 msgid "`setEncoding` — instance" msgstr "`setEncoding` — Instanz" -#: src/api/reference.md:3071 +#: src/api/reference.md:3080 msgid "`uncork` — instance" msgstr "`uncork` — Instanz" -#: src/api/reference.md:3072 +#: src/api/reference.md:3081 msgid "`unpipe` — instance" msgstr "`unpipe` — Instanz" -#: src/api/reference.md:3073 +#: src/api/reference.md:3082 msgid "`unshift` — instance" msgstr "`unshift` — Instanz" -#: src/api/reference.md:3075 +#: src/api/reference.md:3084 msgid "`writableCorked` — instance" msgstr "`writableCorked` — Instanz" -#: src/api/reference.md:3077 +#: src/api/reference.md:3086 msgid "`writableFinished` — instance" msgstr "`writableFinished` — Instanz" -#: src/api/reference.md:3078 +#: src/api/reference.md:3087 msgid "`writableHighWaterMark` — instance" msgstr "`writableHighWaterMark` — Instanz" -#: src/api/reference.md:3079 +#: src/api/reference.md:3088 msgid "`writableLength` — instance" msgstr "`writableLength` — Instanz" -#: src/api/reference.md:3080 +#: src/api/reference.md:3089 msgid "`writableNeedDrain` — instance" msgstr "`writableNeedDrain` — Instanz" -#: src/api/reference.md:3081 +#: src/api/reference.md:3090 msgid "`writableObjectMode` — instance" msgstr "`writableObjectMode` — Instanz" -#: src/api/reference.md:3089 +#: src/api/reference.md:3098 msgid "`stream/consumers`" msgstr "`stream/consumers`" -#: src/api/reference.md:3093 +#: src/api/reference.md:3102 msgid "`arrayBuffer` — module" msgstr "`arrayBuffer` — Modul" -#: src/api/reference.md:3094 +#: src/api/reference.md:3103 msgid "`blob` — module" msgstr "`blob` — Modul" -#: src/api/reference.md:3095 +#: src/api/reference.md:3104 msgid "`buffer` — module" msgstr "`buffer` — Modul" -#: src/api/reference.md:3096 +#: src/api/reference.md:3105 msgid "`bytes` — module" msgstr "`bytes` — Modul" -#: src/api/reference.md:3097 +#: src/api/reference.md:3106 msgid "`json` — module" msgstr "`json` — Modul" -#: src/api/reference.md:3098 +#: src/api/reference.md:3107 msgid "`text` — module" msgstr "`text` — Modul" -#: src/api/reference.md:3104 +#: src/api/reference.md:3113 msgid "`stream/promises`" msgstr "`stream/promises`" -#: src/api/reference.md:3113 +#: src/api/reference.md:3122 msgid "`stream/web`" msgstr "`stream/web`" -#: src/api/reference.md:3117 src/api/reference.md:3143 +#: src/api/reference.md:3126 src/api/reference.md:3152 msgid "`ByteLengthQueuingStrategy`" msgstr "`ByteLengthQueuingStrategy`" -#: src/api/reference.md:3118 +#: src/api/reference.md:3127 msgid "`CompressionStream`" msgstr "`CompressionStream`" -#: src/api/reference.md:3119 src/api/reference.md:3144 +#: src/api/reference.md:3128 src/api/reference.md:3153 msgid "`CountQueuingStrategy`" msgstr "`CountQueuingStrategy`" -#: src/api/reference.md:3120 src/api/reference.md:3145 +#: src/api/reference.md:3129 src/api/reference.md:3154 msgid "`DecompressionStream`" msgstr "`DecompressionStream`" -#: src/api/reference.md:3121 +#: src/api/reference.md:3130 msgid "`ReadableByteStreamController`" msgstr "`ReadableByteStreamController`" -#: src/api/reference.md:3122 src/api/reference.md:3146 +#: src/api/reference.md:3131 src/api/reference.md:3155 msgid "`ReadableStream`" msgstr "`ReadableStream`" -#: src/api/reference.md:3123 +#: src/api/reference.md:3132 msgid "`ReadableStreamBYOBReader`" msgstr "`ReadableStreamBYOBReader`" -#: src/api/reference.md:3124 +#: src/api/reference.md:3133 msgid "`ReadableStreamBYOBRequest`" msgstr "`ReadableStreamBYOBRequest`" -#: src/api/reference.md:3125 +#: src/api/reference.md:3134 msgid "`ReadableStreamDefaultController`" msgstr "`ReadableStreamDefaultController`" -#: src/api/reference.md:3126 +#: src/api/reference.md:3135 msgid "`ReadableStreamDefaultReader`" msgstr "`ReadableStreamDefaultReader`" -#: src/api/reference.md:3127 +#: src/api/reference.md:3136 msgid "`TextDecoderStream`" msgstr "`TextDecoderStream`" -#: src/api/reference.md:3128 +#: src/api/reference.md:3137 msgid "`TextEncoderStream`" msgstr "`TextEncoderStream`" -#: src/api/reference.md:3129 src/api/reference.md:3149 +#: src/api/reference.md:3138 src/api/reference.md:3158 msgid "`TransformStream`" msgstr "`TransformStream`" -#: src/api/reference.md:3130 +#: src/api/reference.md:3139 msgid "`TransformStreamDefaultController`" msgstr "`TransformStreamDefaultController`" -#: src/api/reference.md:3131 src/api/reference.md:3150 +#: src/api/reference.md:3140 src/api/reference.md:3159 msgid "`WritableStream`" msgstr "`WritableStream`" -#: src/api/reference.md:3132 +#: src/api/reference.md:3141 msgid "`WritableStreamDefaultController`" msgstr "`WritableStreamDefaultController`" -#: src/api/reference.md:3133 +#: src/api/reference.md:3142 msgid "`WritableStreamDefaultWriter`" msgstr "`WritableStreamDefaultWriter`" -#: src/api/reference.md:3139 +#: src/api/reference.md:3148 msgid "`streams`" msgstr "`streams`" -#: src/api/reference.md:3147 src/api/reference.md:3169 -#: src/api/reference.md:3431 +#: src/api/reference.md:3156 src/api/reference.md:3178 +#: src/api/reference.md:3451 msgid "`TextDecoder`" msgstr "`TextDecoder`" -#: src/api/reference.md:3148 src/api/reference.md:3170 -#: src/api/reference.md:3432 +#: src/api/reference.md:3157 src/api/reference.md:3179 +#: src/api/reference.md:3452 msgid "`TextEncoder`" msgstr "`TextEncoder`" -#: src/api/reference.md:3152 +#: src/api/reference.md:3161 msgid "`string_decoder`" msgstr "`string_decoder`" -#: src/api/reference.md:3156 +#: src/api/reference.md:3165 msgid "`StringDecoder`" msgstr "`StringDecoder`" -#: src/api/reference.md:3160 +#: src/api/reference.md:3169 msgid "`end` — instance _(class: `StringDecoder`)_" msgstr "`end` — Instanz _(Klasse: `StringDecoder`)_" -#: src/api/reference.md:3161 +#: src/api/reference.md:3170 msgid "`write` — instance _(class: `StringDecoder`)_" msgstr "`write` — Instanz _(Klasse: `StringDecoder`)_" -#: src/api/reference.md:3163 +#: src/api/reference.md:3172 msgid "`sys`" msgstr "`sys`" -#: src/api/reference.md:3167 src/api/reference.md:3429 +#: src/api/reference.md:3176 src/api/reference.md:3449 msgid "`MIMEParams`" msgstr "`MIMEParams`" -#: src/api/reference.md:3168 src/api/reference.md:3430 +#: src/api/reference.md:3177 src/api/reference.md:3450 msgid "`MIMEType`" msgstr "`MIMEType`" -#: src/api/reference.md:3174 src/api/reference.md:3436 +#: src/api/reference.md:3183 src/api/reference.md:3456 msgid "`MIMEParams` — module" msgstr "`MIMEParams` — Modul" -#: src/api/reference.md:3175 src/api/reference.md:3437 +#: src/api/reference.md:3184 src/api/reference.md:3457 msgid "`MIMEType` — module" msgstr "`MIMEType` — Modul" -#: src/api/reference.md:3176 src/api/reference.md:3438 +#: src/api/reference.md:3185 src/api/reference.md:3458 msgid "`_errnoException` — module" msgstr "`_errnoException` — Modul" -#: src/api/reference.md:3177 src/api/reference.md:3439 +#: src/api/reference.md:3186 src/api/reference.md:3459 msgid "`_exceptionWithHostPort` — module" msgstr "`_exceptionWithHostPort` — Modul" -#: src/api/reference.md:3178 src/api/reference.md:3440 +#: src/api/reference.md:3187 src/api/reference.md:3460 msgid "`_extend` — module" msgstr "`_extend` — Modul" -#: src/api/reference.md:3179 src/api/reference.md:3441 +#: src/api/reference.md:3188 src/api/reference.md:3461 msgid "`aborted` — module" msgstr "`aborted` — Modul" -#: src/api/reference.md:3180 src/api/reference.md:3442 +#: src/api/reference.md:3189 src/api/reference.md:3462 msgid "`callbackify` — module" msgstr "`callbackify` — Modul" -#: src/api/reference.md:3181 src/api/reference.md:3443 +#: src/api/reference.md:3190 src/api/reference.md:3463 msgid "`convertProcessSignalToExitCode` — module" msgstr "`convertProcessSignalToExitCode` — Modul" -#: src/api/reference.md:3183 src/api/reference.md:3445 +#: src/api/reference.md:3192 src/api/reference.md:3465 msgid "`debuglog` — module" msgstr "`debuglog` — Modul" -#: src/api/reference.md:3184 src/api/reference.md:3446 +#: src/api/reference.md:3193 src/api/reference.md:3466 msgid "`deprecate` — module" msgstr "`deprecate` — Modul" -#: src/api/reference.md:3185 src/api/reference.md:3447 +#: src/api/reference.md:3194 src/api/reference.md:3467 msgid "`diff` — module" msgstr "`diff` — Modul" -#: src/api/reference.md:3187 src/api/reference.md:3449 +#: src/api/reference.md:3196 src/api/reference.md:3469 msgid "`formatWithOptions` — module" msgstr "`formatWithOptions` — Modul" -#: src/api/reference.md:3188 src/api/reference.md:3450 +#: src/api/reference.md:3197 src/api/reference.md:3470 msgid "`getCallSites` — module" msgstr "`getCallSites` — Modul" -#: src/api/reference.md:3189 src/api/reference.md:3451 +#: src/api/reference.md:3198 src/api/reference.md:3471 msgid "`getSystemErrorMap` — module" msgstr "`getSystemErrorMap` — Modul" -#: src/api/reference.md:3190 src/api/reference.md:3452 +#: src/api/reference.md:3199 src/api/reference.md:3472 msgid "`getSystemErrorMessage` — module" msgstr "`getSystemErrorMessage` — Modul" -#: src/api/reference.md:3191 src/api/reference.md:3453 +#: src/api/reference.md:3200 src/api/reference.md:3473 msgid "`getSystemErrorName` — module" msgstr "`getSystemErrorName` — Modul" -#: src/api/reference.md:3192 src/api/reference.md:3454 +#: src/api/reference.md:3201 src/api/reference.md:3474 msgid "`inherits` — module" msgstr "`inherits` — Modul" -#: src/api/reference.md:3194 src/api/reference.md:3456 +#: src/api/reference.md:3203 src/api/reference.md:3476 msgid "`isArray` — module" msgstr "`isArray` — Modul" -#: src/api/reference.md:3195 src/api/reference.md:3457 +#: src/api/reference.md:3204 src/api/reference.md:3477 msgid "`isDeepStrictEqual` — module" msgstr "`isDeepStrictEqual` — Modul" -#: src/api/reference.md:3196 src/api/reference.md:3458 +#: src/api/reference.md:3205 src/api/reference.md:3478 msgid "`parseArgs` — module" msgstr "`parseArgs` — Modul" -#: src/api/reference.md:3197 src/api/reference.md:3459 +#: src/api/reference.md:3206 src/api/reference.md:3479 msgid "`parseEnv` — module" msgstr "`parseEnv` — Modul" -#: src/api/reference.md:3198 src/api/reference.md:3460 +#: src/api/reference.md:3207 src/api/reference.md:3480 msgid "`promisify` — module" msgstr "`promisify` — Modul" -#: src/api/reference.md:3199 src/api/reference.md:3461 +#: src/api/reference.md:3208 src/api/reference.md:3481 msgid "`setTraceSigInt` — module" msgstr "`setTraceSigInt` — Modul" -#: src/api/reference.md:3200 src/api/reference.md:3462 +#: src/api/reference.md:3209 src/api/reference.md:3482 msgid "`stripVTControlCharacters` — module" msgstr "`stripVTControlCharacters` — Modul" -#: src/api/reference.md:3201 src/api/reference.md:3463 +#: src/api/reference.md:3210 src/api/reference.md:3483 msgid "`styleText` — module" msgstr "`styleText` — Modul" -#: src/api/reference.md:3202 src/api/reference.md:3464 +#: src/api/reference.md:3211 src/api/reference.md:3484 msgid "`toUSVString` — module" msgstr "`toUSVString` — Modul" -#: src/api/reference.md:3203 src/api/reference.md:3465 +#: src/api/reference.md:3212 src/api/reference.md:3485 msgid "`transferableAbortController` — module" msgstr "`transferableAbortController` — Modul" -#: src/api/reference.md:3204 src/api/reference.md:3466 +#: src/api/reference.md:3213 src/api/reference.md:3486 msgid "`transferableAbortSignal` — module" msgstr "`transferableAbortSignal` — Modul" -#: src/api/reference.md:3209 src/api/reference.md:3471 src/cli/commands.md:26 +#: src/api/reference.md:3218 src/api/reference.md:3491 src/cli/commands.md:26 msgid "`types`" msgstr "`types`" -#: src/api/reference.md:3211 +#: src/api/reference.md:3220 msgid "`test`" msgstr "`test`" -#: src/api/reference.md:3215 +#: src/api/reference.md:3224 msgid "`after` — module" msgstr "`after` — Modul" -#: src/api/reference.md:3216 +#: src/api/reference.md:3225 msgid "`afterEach` — module" msgstr "`afterEach` — Modul" -#: src/api/reference.md:3217 +#: src/api/reference.md:3226 msgid "`before` — module" msgstr "`before` — Modul" -#: src/api/reference.md:3218 +#: src/api/reference.md:3227 msgid "`beforeEach` — module" msgstr "`beforeEach` — Modul" -#: src/api/reference.md:3220 +#: src/api/reference.md:3229 msgid "`describe` — module" msgstr "`describe` — Modul" -#: src/api/reference.md:3221 +#: src/api/reference.md:3230 msgid "`enable` — module _(class: `timers`)_" msgstr "`enable` — Modul _(Klasse: `timers`)_" -#: src/api/reference.md:3222 +#: src/api/reference.md:3231 msgid "`expectFailure` — module" msgstr "`expectFailure` — Modul" -#: src/api/reference.md:3223 +#: src/api/reference.md:3232 msgid "`fn` — module _(class: `mock`)_" msgstr "`fn` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3224 +#: src/api/reference.md:3233 msgid "`getter` — module _(class: `mock`)_" msgstr "`getter` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3225 +#: src/api/reference.md:3234 msgid "`it` — module" msgstr "`it` — Modul" -#: src/api/reference.md:3226 +#: src/api/reference.md:3235 msgid "`method` — module _(class: `mock`)_" msgstr "`method` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3227 +#: src/api/reference.md:3236 msgid "`only` — module" msgstr "`only` — Modul" -#: src/api/reference.md:3228 +#: src/api/reference.md:3237 msgid "`property` — module _(class: `mock`)_" msgstr "`property` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3229 +#: src/api/reference.md:3238 msgid "`reset` — module _(class: `mock`)_" msgstr "`reset` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3230 +#: src/api/reference.md:3239 msgid "`restoreAll` — module _(class: `mock`)_" msgstr "`restoreAll` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3232 +#: src/api/reference.md:3241 msgid "`runAll` — module _(class: `timers`)_" msgstr "`runAll` — Modul _(Klasse: `timers`)_" -#: src/api/reference.md:3233 +#: src/api/reference.md:3242 msgid "`setDefaultSnapshotSerializers` — module _(class: `snapshot`)_" msgstr "`setDefaultSnapshotSerializers` — Modul _(Klasse: `snapshot`)_" -#: src/api/reference.md:3234 +#: src/api/reference.md:3243 msgid "`setResolveSnapshotPath` — module _(class: `snapshot`)_" msgstr "`setResolveSnapshotPath` — Modul _(Klasse: `snapshot`)_" -#: src/api/reference.md:3235 +#: src/api/reference.md:3244 msgid "`setTime` — module _(class: `timers`)_" msgstr "`setTime` — Modul _(Klasse: `timers`)_" -#: src/api/reference.md:3236 +#: src/api/reference.md:3245 msgid "`setter` — module _(class: `mock`)_" msgstr "`setter` — Modul _(Klasse: `mock`)_" -#: src/api/reference.md:3237 +#: src/api/reference.md:3246 msgid "`skip` — module" msgstr "`skip` — Modul" -#: src/api/reference.md:3238 +#: src/api/reference.md:3247 msgid "`suite` — module" msgstr "`suite` — Modul" -#: src/api/reference.md:3239 +#: src/api/reference.md:3248 msgid "`test` — module" msgstr "`test` — Modul" -#: src/api/reference.md:3240 +#: src/api/reference.md:3249 msgid "`tick` — module _(class: `timers`)_" msgstr "`tick` — Modul _(Klasse: `timers`)_" -#: src/api/reference.md:3241 +#: src/api/reference.md:3250 msgid "`todo` — module" msgstr "`todo` — Modul" -#: src/api/reference.md:3246 +#: src/api/reference.md:3255 msgid "`mock`" msgstr "`mock`" -#: src/api/reference.md:3247 +#: src/api/reference.md:3256 msgid "`snapshot`" msgstr "`snapshot`" -#: src/api/reference.md:3249 +#: src/api/reference.md:3258 msgid "`test/reporters`" msgstr "`test/reporters`" -#: src/api/reference.md:3253 +#: src/api/reference.md:3262 msgid "`dot` — module" msgstr "`dot` — Modul" -#: src/api/reference.md:3254 +#: src/api/reference.md:3263 msgid "`junit` — module" msgstr "`junit` — Modul" -#: src/api/reference.md:3255 +#: src/api/reference.md:3264 msgid "`lcov` — module" msgstr "`lcov` — Modul" -#: src/api/reference.md:3256 +#: src/api/reference.md:3265 msgid "`spec` — module" msgstr "`spec` — Modul" -#: src/api/reference.md:3257 +#: src/api/reference.md:3266 msgid "`tap` — module" msgstr "`tap` — Modul" -#: src/api/reference.md:3263 +#: src/api/reference.md:3272 msgid "`timers`" msgstr "`timers`" -#: src/api/reference.md:3267 +#: src/api/reference.md:3276 msgid "`clearImmediate` — module" msgstr "`clearImmediate` — Modul" -#: src/api/reference.md:3268 +#: src/api/reference.md:3277 msgid "`clearInterval` — module" msgstr "`clearInterval` — Modul" -#: src/api/reference.md:3269 +#: src/api/reference.md:3278 msgid "`clearTimeout` — module" msgstr "`clearTimeout` — Modul" -#: src/api/reference.md:3270 src/api/reference.md:3282 +#: src/api/reference.md:3279 src/api/reference.md:3291 msgid "`setImmediate` — module" msgstr "`setImmediate` — Modul" -#: src/api/reference.md:3271 src/api/reference.md:3283 +#: src/api/reference.md:3280 src/api/reference.md:3292 msgid "`setInterval` — module" msgstr "`setInterval` — Modul" -#: src/api/reference.md:3272 src/api/reference.md:3284 +#: src/api/reference.md:3281 src/api/reference.md:3293 msgid "`setTimeout` — module" msgstr "`setTimeout` — Modul" -#: src/api/reference.md:3278 +#: src/api/reference.md:3287 msgid "`timers/promises`" msgstr "`timers/promises`" -#: src/api/reference.md:3288 +#: src/api/reference.md:3297 msgid "`scheduler`" msgstr "`scheduler`" -#: src/api/reference.md:3290 +#: src/api/reference.md:3299 msgid "`tls`" msgstr "`tls`" -#: src/api/reference.md:3294 +#: src/api/reference.md:3303 msgid "`SecureContext`" msgstr "`SecureContext`" -#: src/api/reference.md:3298 +#: src/api/reference.md:3307 msgid "`SecureContext` — module" msgstr "`SecureContext` — Modul" -#: src/api/reference.md:3300 +#: src/api/reference.md:3309 msgid "`TLSSocket` — module" msgstr "`TLSSocket` — Modul" -#: src/api/reference.md:3303 +#: src/api/reference.md:3312 msgid "`checkServerIdentity` — module" msgstr "`checkServerIdentity` — Modul" -#: src/api/reference.md:3306 +#: src/api/reference.md:3315 msgid "`convertALPNProtocols` — module" msgstr "`convertALPNProtocols` — Modul" -#: src/api/reference.md:3307 +#: src/api/reference.md:3316 msgid "`createSecureContext` — module" msgstr "`createSecureContext` — Modul" -#: src/api/reference.md:3310 +#: src/api/reference.md:3319 msgid "`getCACertificates` — module" msgstr "`getCACertificates` — Modul" -#: src/api/reference.md:3311 +#: src/api/reference.md:3320 msgid "`getCertificateCompressionAlgorithms` — module" msgstr "`getCertificateCompressionAlgorithms` — Modul" -#: src/api/reference.md:3313 +#: src/api/reference.md:3322 msgid "`getTicketKeys` — instance _(class: `Server`)_" msgstr "`getTicketKeys` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:3317 +#: src/api/reference.md:3326 msgid "`on` — instance _(class: `Server`)_" msgstr "`on` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:3321 +#: src/api/reference.md:3330 msgid "`setDefaultCACertificates` — module" msgstr "`setDefaultCACertificates` — Modul" -#: src/api/reference.md:3322 +#: src/api/reference.md:3331 msgid "`setSecureContext` — instance _(class: `Server`)_" msgstr "`setSecureContext` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:3323 +#: src/api/reference.md:3332 msgid "`setTicketKeys` — instance _(class: `Server`)_" msgstr "`setTicketKeys` — Instanz _(Klasse: `Server`)_" -#: src/api/reference.md:3327 +#: src/api/reference.md:3336 msgid "`CLIENT_RENEG_LIMIT`" msgstr "`CLIENT_RENEG_LIMIT`" -#: src/api/reference.md:3328 +#: src/api/reference.md:3337 msgid "`CLIENT_RENEG_WINDOW`" msgstr "`CLIENT_RENEG_WINDOW`" -#: src/api/reference.md:3329 +#: src/api/reference.md:3338 msgid "`DEFAULT_CIPHERS`" msgstr "`DEFAULT_CIPHERS`" -#: src/api/reference.md:3330 +#: src/api/reference.md:3339 msgid "`DEFAULT_ECDH_CURVE`" msgstr "`DEFAULT_ECDH_CURVE`" -#: src/api/reference.md:3331 +#: src/api/reference.md:3340 msgid "`DEFAULT_MAX_VERSION`" msgstr "`DEFAULT_MAX_VERSION`" -#: src/api/reference.md:3332 +#: src/api/reference.md:3341 msgid "`DEFAULT_MIN_VERSION`" msgstr "`DEFAULT_MIN_VERSION`" -#: src/api/reference.md:3333 +#: src/api/reference.md:3342 msgid "`rootCertificates`" msgstr "`rootCertificates`" -#: src/api/reference.md:3335 +#: src/api/reference.md:3344 +#, fuzzy +msgid "`trace_events`" +msgstr "`_events`" + +#: src/api/reference.md:3348 +#, fuzzy +msgid "`createTracing` — module" +msgstr "`createDomain` — Modul" + +#: src/api/reference.md:3349 +#, fuzzy +msgid "`getEnabledCategories` — module" +msgstr "`generatePrime` — Modul" + +#: src/api/reference.md:3355 msgid "`tty`" msgstr "`tty`" -#: src/api/reference.md:3344 +#: src/api/reference.md:3364 msgid "`ReadStream` — module" msgstr "`ReadStream` — Modul" -#: src/api/reference.md:3345 +#: src/api/reference.md:3365 msgid "`WriteStream` — module" msgstr "`WriteStream` — Modul" -#: src/api/reference.md:3346 +#: src/api/reference.md:3366 msgid "`_refreshSize` — instance _(class: `WriteStream`)_" msgstr "`_refreshSize` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3347 +#: src/api/reference.md:3367 msgid "`addListener` — instance _(class: `WriteStream`)_" msgstr "`addListener` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3348 +#: src/api/reference.md:3368 msgid "`clearLine` — instance _(class: `WriteStream`)_" msgstr "`clearLine` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3349 +#: src/api/reference.md:3369 msgid "`clearScreenDown` — instance _(class: `WriteStream`)_" msgstr "`clearScreenDown` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3350 +#: src/api/reference.md:3370 msgid "`cursorTo` — instance _(class: `WriteStream`)_" msgstr "`cursorTo` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3351 +#: src/api/reference.md:3371 msgid "`getColorDepth` — instance _(class: `WriteStream`)_" msgstr "`getColorDepth` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3352 +#: src/api/reference.md:3372 msgid "`getWindowSize` — instance _(class: `WriteStream`)_" msgstr "`getWindowSize` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3353 +#: src/api/reference.md:3373 msgid "`hasColors` — instance _(class: `WriteStream`)_" msgstr "`hasColors` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3354 +#: src/api/reference.md:3374 msgid "`isatty` — module" msgstr "`isatty` — Modul" -#: src/api/reference.md:3355 +#: src/api/reference.md:3375 msgid "`moveCursor` — instance _(class: `WriteStream`)_" msgstr "`moveCursor` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3356 +#: src/api/reference.md:3376 msgid "`off` — instance _(class: `WriteStream`)_" msgstr "`off` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3357 +#: src/api/reference.md:3377 msgid "`on` — instance _(class: `WriteStream`)_" msgstr "`on` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3358 +#: src/api/reference.md:3378 msgid "`once` — instance _(class: `WriteStream`)_" msgstr "`once` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3359 +#: src/api/reference.md:3379 msgid "`removeAllListeners` — instance _(class: `WriteStream`)_" msgstr "`removeAllListeners` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3360 +#: src/api/reference.md:3380 msgid "`removeListener` — instance _(class: `WriteStream`)_" msgstr "`removeListener` — Instanz _(Klasse: `WriteStream`)_" -#: src/api/reference.md:3361 +#: src/api/reference.md:3381 msgid "`setRawMode` — instance _(class: `ReadStream`)_" msgstr "`setRawMode` — Instanz _(Klasse: `ReadStream`)_" -#: src/api/reference.md:3367 +#: src/api/reference.md:3387 msgid "`flattenDiagnosticMessageText` — module" msgstr "`flattenDiagnosticMessageText` — Modul" -#: src/api/reference.md:3368 +#: src/api/reference.md:3388 msgid "`transpileModule` — module" msgstr "`transpileModule` — Modul" -#: src/api/reference.md:3372 +#: src/api/reference.md:3392 msgid "`DiagnosticCategory`" msgstr "`DiagnosticCategory`" -#: src/api/reference.md:3373 +#: src/api/reference.md:3393 msgid "`ModuleKind`" msgstr "`ModuleKind`" -#: src/api/reference.md:3374 +#: src/api/reference.md:3394 msgid "`ScriptTarget`" msgstr "`ScriptTarget`" -#: src/api/reference.md:3381 +#: src/api/reference.md:3401 msgid "`ProxyAgent`" msgstr "`ProxyAgent`" -#: src/api/reference.md:3386 +#: src/api/reference.md:3406 msgid "`ProxyAgent` — module" msgstr "`ProxyAgent` — Modul" -#: src/api/reference.md:3387 +#: src/api/reference.md:3407 msgid "`close` — instance _(class: `ProxyAgent`)_" msgstr "`close` — Instanz _(Klasse: `ProxyAgent`)_" -#: src/api/reference.md:3389 +#: src/api/reference.md:3409 msgid "`destroy` — instance _(class: `ProxyAgent`)_" msgstr "`destroy` — Instanz _(Klasse: `ProxyAgent`)_" -#: src/api/reference.md:3391 +#: src/api/reference.md:3411 msgid "`fetch` — module" msgstr "`fetch` — Modul" -#: src/api/reference.md:3392 +#: src/api/reference.md:3412 msgid "`getGlobalDispatcher` — module" msgstr "`getGlobalDispatcher` — Modul" -#: src/api/reference.md:3394 +#: src/api/reference.md:3414 msgid "`setGlobalDispatcher` — module" msgstr "`setGlobalDispatcher` — Modul" -#: src/api/reference.md:3400 +#: src/api/reference.md:3420 msgid "`URL`" msgstr "`URL`" -#: src/api/reference.md:3401 +#: src/api/reference.md:3421 msgid "`URLPattern`" msgstr "`URLPattern`" -#: src/api/reference.md:3402 +#: src/api/reference.md:3422 msgid "`URLSearchParams`" msgstr "`URLSearchParams`" -#: src/api/reference.md:3403 +#: src/api/reference.md:3423 msgid "`Url`" msgstr "`Url`" -#: src/api/reference.md:3407 +#: src/api/reference.md:3427 msgid "`Url` — module" msgstr "`Url` — Modul" -#: src/api/reference.md:3408 +#: src/api/reference.md:3428 msgid "`domainToASCII` — module" msgstr "`domainToASCII` — Modul" -#: src/api/reference.md:3409 +#: src/api/reference.md:3429 msgid "`domainToUnicode` — module" msgstr "`domainToUnicode` — Modul" -#: src/api/reference.md:3410 +#: src/api/reference.md:3430 msgid "`exec` — instance _(class: `URLPattern`)_" msgstr "`exec` — Instanz _(Klasse: `URLPattern`)_" -#: src/api/reference.md:3412 +#: src/api/reference.md:3432 msgid "`fileURLToPathBuffer` — module" msgstr "`fileURLToPathBuffer` — Modul" -#: src/api/reference.md:3417 +#: src/api/reference.md:3437 msgid "`resolveObject` — module" msgstr "`resolveObject` — Modul" -#: src/api/reference.md:3418 +#: src/api/reference.md:3438 msgid "`test` — instance _(class: `URLPattern`)_" msgstr "`test` — Instanz _(Klasse: `URLPattern`)_" -#: src/api/reference.md:3419 +#: src/api/reference.md:3439 msgid "`urlToHttpOptions` — module" msgstr "`urlToHttpOptions` — Modul" -#: src/api/reference.md:3425 +#: src/api/reference.md:3445 msgid "`util`" msgstr "`util`" -#: src/api/reference.md:3473 +#: src/api/reference.md:3493 msgid "`util/types`" msgstr "`util/types`" -#: src/api/reference.md:3477 +#: src/api/reference.md:3497 msgid "`isAnyArrayBuffer` — module" msgstr "`isAnyArrayBuffer` — Modul" -#: src/api/reference.md:3478 +#: src/api/reference.md:3498 msgid "`isArgumentsObject` — module" msgstr "`isArgumentsObject` — Modul" -#: src/api/reference.md:3479 +#: src/api/reference.md:3499 msgid "`isArrayBuffer` — module" msgstr "`isArrayBuffer` — Modul" -#: src/api/reference.md:3480 +#: src/api/reference.md:3500 msgid "`isArrayBufferView` — module" msgstr "`isArrayBufferView` — Modul" -#: src/api/reference.md:3481 +#: src/api/reference.md:3501 msgid "`isAsyncFunction` — module" msgstr "`isAsyncFunction` — Modul" -#: src/api/reference.md:3482 +#: src/api/reference.md:3502 msgid "`isBigInt64Array` — module" msgstr "`isBigInt64Array` — Modul" -#: src/api/reference.md:3483 +#: src/api/reference.md:3503 msgid "`isBigIntObject` — module" msgstr "`isBigIntObject` — Modul" -#: src/api/reference.md:3484 +#: src/api/reference.md:3504 msgid "`isBigUint64Array` — module" msgstr "`isBigUint64Array` — Modul" -#: src/api/reference.md:3485 +#: src/api/reference.md:3505 msgid "`isBooleanObject` — module" msgstr "`isBooleanObject` — Modul" -#: src/api/reference.md:3486 +#: src/api/reference.md:3506 msgid "`isBoxedPrimitive` — module" msgstr "`isBoxedPrimitive` — Modul" -#: src/api/reference.md:3487 +#: src/api/reference.md:3507 msgid "`isCryptoKey` — module" msgstr "`isCryptoKey` — Modul" -#: src/api/reference.md:3488 +#: src/api/reference.md:3508 msgid "`isDataView` — module" msgstr "`isDataView` — Modul" -#: src/api/reference.md:3489 +#: src/api/reference.md:3509 msgid "`isDate` — module" msgstr "`isDate` — Modul" -#: src/api/reference.md:3490 +#: src/api/reference.md:3510 msgid "`isExternal` — module" msgstr "`isExternal` — Modul" -#: src/api/reference.md:3491 +#: src/api/reference.md:3511 msgid "`isFloat16Array` — module" msgstr "`isFloat16Array` — Modul" -#: src/api/reference.md:3492 +#: src/api/reference.md:3512 msgid "`isFloat32Array` — module" msgstr "`isFloat32Array` — Modul" -#: src/api/reference.md:3493 +#: src/api/reference.md:3513 msgid "`isFloat64Array` — module" msgstr "`isFloat64Array` — Modul" -#: src/api/reference.md:3494 +#: src/api/reference.md:3514 msgid "`isGeneratorFunction` — module" msgstr "`isGeneratorFunction` — Modul" -#: src/api/reference.md:3495 +#: src/api/reference.md:3515 msgid "`isGeneratorObject` — module" msgstr "`isGeneratorObject` — Modul" -#: src/api/reference.md:3496 +#: src/api/reference.md:3516 msgid "`isInt16Array` — module" msgstr "`isInt16Array` — Modul" -#: src/api/reference.md:3497 +#: src/api/reference.md:3517 msgid "`isInt32Array` — module" msgstr "`isInt32Array` — Modul" -#: src/api/reference.md:3498 +#: src/api/reference.md:3518 msgid "`isInt8Array` — module" msgstr "`isInt8Array` — Modul" -#: src/api/reference.md:3499 +#: src/api/reference.md:3519 msgid "`isKeyObject` — module" msgstr "`isKeyObject` — Modul" -#: src/api/reference.md:3500 +#: src/api/reference.md:3520 msgid "`isMap` — module" msgstr "`isMap` — Modul" -#: src/api/reference.md:3501 +#: src/api/reference.md:3521 msgid "`isMapIterator` — module" msgstr "`isMapIterator` — Modul" -#: src/api/reference.md:3502 +#: src/api/reference.md:3522 msgid "`isModuleNamespaceObject` — module" msgstr "`isModuleNamespaceObject` — Modul" -#: src/api/reference.md:3503 +#: src/api/reference.md:3523 msgid "`isNativeError` — module" msgstr "`isNativeError` — Modul" -#: src/api/reference.md:3504 +#: src/api/reference.md:3524 msgid "`isNumberObject` — module" msgstr "`isNumberObject` — Modul" -#: src/api/reference.md:3505 +#: src/api/reference.md:3525 msgid "`isPromise` — module" msgstr "`isPromise` — Modul" -#: src/api/reference.md:3506 +#: src/api/reference.md:3526 msgid "`isProxy` — module" msgstr "`isProxy` — Modul" -#: src/api/reference.md:3507 +#: src/api/reference.md:3527 msgid "`isRegExp` — module" msgstr "`isRegExp` — Modul" -#: src/api/reference.md:3508 +#: src/api/reference.md:3528 msgid "`isSet` — module" msgstr "`isSet` — Modul" -#: src/api/reference.md:3509 +#: src/api/reference.md:3529 msgid "`isSetIterator` — module" msgstr "`isSetIterator` — Modul" -#: src/api/reference.md:3510 +#: src/api/reference.md:3530 msgid "`isSharedArrayBuffer` — module" msgstr "`isSharedArrayBuffer` — Modul" -#: src/api/reference.md:3511 +#: src/api/reference.md:3531 msgid "`isStringObject` — module" msgstr "`isStringObject` — Modul" -#: src/api/reference.md:3512 +#: src/api/reference.md:3532 msgid "`isSymbolObject` — module" msgstr "`isSymbolObject` — Modul" -#: src/api/reference.md:3513 +#: src/api/reference.md:3533 msgid "`isTypedArray` — module" msgstr "`isTypedArray` — Modul" -#: src/api/reference.md:3514 +#: src/api/reference.md:3534 msgid "`isUint16Array` — module" msgstr "`isUint16Array` — Modul" -#: src/api/reference.md:3515 +#: src/api/reference.md:3535 msgid "`isUint32Array` — module" msgstr "`isUint32Array` — Modul" -#: src/api/reference.md:3516 +#: src/api/reference.md:3536 msgid "`isUint8Array` — module" msgstr "`isUint8Array` — Modul" -#: src/api/reference.md:3517 +#: src/api/reference.md:3537 msgid "`isUint8ClampedArray` — module" msgstr "`isUint8ClampedArray` — Modul" -#: src/api/reference.md:3518 +#: src/api/reference.md:3538 msgid "`isWeakMap` — module" msgstr "`isWeakMap` — Modul" -#: src/api/reference.md:3519 +#: src/api/reference.md:3539 msgid "`isWeakSet` — module" msgstr "`isWeakSet` — Modul" -#: src/api/reference.md:3521 +#: src/api/reference.md:3541 msgid "`v8`" msgstr "`v8`" -#: src/api/reference.md:3525 +#: src/api/reference.md:3545 msgid "`DefaultDeserializer`" msgstr "`DefaultDeserializer`" -#: src/api/reference.md:3526 +#: src/api/reference.md:3546 msgid "`DefaultSerializer`" msgstr "`DefaultSerializer`" -#: src/api/reference.md:3527 +#: src/api/reference.md:3547 msgid "`Deserializer`" msgstr "`Deserializer`" -#: src/api/reference.md:3528 +#: src/api/reference.md:3548 msgid "`GCProfiler`" msgstr "`GCProfiler`" -#: src/api/reference.md:3529 +#: src/api/reference.md:3549 msgid "`Serializer`" msgstr "`Serializer`" -#: src/api/reference.md:3533 +#: src/api/reference.md:3553 msgid "`addDeserializeCallback` — instance _(class: `startupSnapshot`)_" msgstr "`addDeserializeCallback` — Instanz _(Klasse: `startupSnapshot`)_" -#: src/api/reference.md:3534 +#: src/api/reference.md:3554 msgid "`addSerializeCallback` — instance _(class: `startupSnapshot`)_" msgstr "`addSerializeCallback` — Instanz _(Klasse: `startupSnapshot`)_" -#: src/api/reference.md:3535 +#: src/api/reference.md:3555 msgid "`cachedDataVersionTag` — module" msgstr "`cachedDataVersionTag` — Modul" -#: src/api/reference.md:3536 +#: src/api/reference.md:3556 msgid "`createHook` — instance _(class: `promiseHooks`)_" msgstr "`createHook` — Instanz _(Klasse: `promiseHooks`)_" -#: src/api/reference.md:3537 +#: src/api/reference.md:3557 msgid "`deserialize` — module" msgstr "`deserialize` — Modul" -#: src/api/reference.md:3538 +#: src/api/reference.md:3558 msgid "`getCppHeapStatistics` — module" msgstr "`getCppHeapStatistics` — Modul" -#: src/api/reference.md:3539 +#: src/api/reference.md:3559 msgid "" "`getHeapCodeStatistics` — module ⚠ **stub** — all fields 0; Perry compiles " "AOT, there is no JIT code heap (#4916)" @@ -45655,11 +45421,11 @@ msgstr "" "`getHeapCodeStatistics` — Modul **Stub** — alle Felder 0; Perry kompiliert " "AOT, es gibt keinen JIT-Codehaufen (#4916)" -#: src/api/reference.md:3540 +#: src/api/reference.md:3560 msgid "`getHeapSnapshot` — module" msgstr "`getHeapSnapshot` — Modul" -#: src/api/reference.md:3541 +#: src/api/reference.md:3561 msgid "" "`getHeapSpaceStatistics` — module ⚠ **stub** — Node space names with all " "live usage attributed to old_space from Perry arenas; other spaces report 0 " @@ -45669,7 +45435,7 @@ msgstr "" "Nutzungen, die old_space von Perry-Arenen zugeschrieben werden; andere Räume " "melden 0 (#4916)" -#: src/api/reference.md:3542 +#: src/api/reference.md:3562 msgid "" "`getHeapStatistics` — module ⚠ **stub** — Node shape, Perry numbers: " "total_heap_size/used_heap_size/malloced_memory/total_allocated_bytes from " @@ -45683,87 +45449,87 @@ msgstr "" "durchgesetzt); \\*\\_ausführbar, external_memory, Global-Handles und Zap-" "Felder sind 0 (#4916)" -#: src/api/reference.md:3543 +#: src/api/reference.md:3563 msgid "`isBuildingSnapshot` — instance _(class: `startupSnapshot`)_" msgstr "`isBuildingSnapshot` — Instanz _(Klasse: `startupSnapshot`)_" -#: src/api/reference.md:3544 +#: src/api/reference.md:3564 msgid "`isStringOneByteRepresentation` — module" msgstr "`isStringOneByteRepresentation` — Modul" -#: src/api/reference.md:3545 +#: src/api/reference.md:3565 msgid "`onAfter` — instance _(class: `promiseHooks`)_" msgstr "`onAfter` — Instanz _(Klasse: `promiseHooks`)_" -#: src/api/reference.md:3546 +#: src/api/reference.md:3566 msgid "`onBefore` — instance _(class: `promiseHooks`)_" msgstr "`onBefore` — Instanz _(Klasse: `promiseHooks`)_" -#: src/api/reference.md:3547 +#: src/api/reference.md:3567 msgid "`onInit` — instance _(class: `promiseHooks`)_" msgstr "`onInit` — Instanz _(Klasse: `promiseHooks`)_" -#: src/api/reference.md:3548 +#: src/api/reference.md:3568 msgid "`onSettled` — instance _(class: `promiseHooks`)_" msgstr "`onSettled` — Instanz _(Klasse: `promiseHooks`)_" -#: src/api/reference.md:3549 +#: src/api/reference.md:3569 msgid "`queryObjects` — module" msgstr "`queryObjects` — Modul" -#: src/api/reference.md:3550 +#: src/api/reference.md:3570 msgid "`readDouble` — instance _(class: `Deserializer`)_" msgstr "`readDouble` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3551 +#: src/api/reference.md:3571 msgid "`readHeader` — instance _(class: `Deserializer`)_" msgstr "`readHeader` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3552 +#: src/api/reference.md:3572 msgid "`readRawBytes` — instance _(class: `Deserializer`)_" msgstr "`readRawBytes` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3553 +#: src/api/reference.md:3573 msgid "`readUint32` — instance _(class: `Deserializer`)_" msgstr "`readUint32` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3554 +#: src/api/reference.md:3574 msgid "`readUint64` — instance _(class: `Deserializer`)_" msgstr "`readUint64` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3555 +#: src/api/reference.md:3575 msgid "`readValue` — instance _(class: `Deserializer`)_" msgstr "`readValue` — Instanz _(Klasse: `Deserializer`)_" -#: src/api/reference.md:3556 +#: src/api/reference.md:3576 msgid "`releaseBuffer` — instance _(class: `Serializer`)_" msgstr "`releaseBuffer` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3557 +#: src/api/reference.md:3577 msgid "`serialize` — module" msgstr "`serialize` — Modul" -#: src/api/reference.md:3558 +#: src/api/reference.md:3578 msgid "`setDeserializeMainFunction` — instance _(class: `startupSnapshot`)_" msgstr "`setDeserializeMainFunction` — Instanz _(Klasse: `startupSnapshot`)_" -#: src/api/reference.md:3559 +#: src/api/reference.md:3579 msgid "`setFlagsFromString` — module" msgstr "`setFlagsFromString` — Modul" -#: src/api/reference.md:3560 +#: src/api/reference.md:3580 msgid "`setHeapSnapshotNearHeapLimit` — module" msgstr "`setHeapSnapshotNearHeapLimit` — Modul" -#: src/api/reference.md:3561 +#: src/api/reference.md:3581 msgid "`start` — instance _(class: `GCProfiler`)_" msgstr "`start` — Instanz _(Klasse: `GCProfiler`)_" -#: src/api/reference.md:3562 +#: src/api/reference.md:3582 msgid "`startCpuProfile` — module" msgstr "`startCpuProfile` — Modul" -#: src/api/reference.md:3563 +#: src/api/reference.md:3583 msgid "" "`stop` — instance _(class: `GCProfiler`)_ ⚠ **stub** — report has the Node " "shape but the statistics array is always empty (#4916)" @@ -45771,491 +45537,495 @@ msgstr "" "`stop` — Instanz _(Klasse: `GCProfiler`)_ ] **Stub** — Bericht hat die " "Node-Form, aber das Statistik-Array ist immer leer (#4916)" -#: src/api/reference.md:3564 +#: src/api/reference.md:3584 msgid "`stopCoverage` — module" msgstr "`stopCoverage` — Modul" -#: src/api/reference.md:3565 +#: src/api/reference.md:3585 msgid "`takeCoverage` — module" msgstr "`takeCoverage` — Modul" -#: src/api/reference.md:3566 +#: src/api/reference.md:3586 msgid "`writeDouble` — instance _(class: `Serializer`)_" msgstr "`writeDouble` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3567 +#: src/api/reference.md:3587 msgid "`writeHeader` — instance _(class: `Serializer`)_" msgstr "`writeHeader` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3568 +#: src/api/reference.md:3588 msgid "`writeHeapSnapshot` — module" msgstr "`writeHeapSnapshot` — Modul" -#: src/api/reference.md:3569 +#: src/api/reference.md:3589 msgid "`writeRawBytes` — instance _(class: `Serializer`)_" msgstr "`writeRawBytes` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3570 +#: src/api/reference.md:3590 msgid "`writeUint32` — instance _(class: `Serializer`)_" msgstr "`writeUint32` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3571 +#: src/api/reference.md:3591 msgid "`writeUint64` — instance _(class: `Serializer`)_" msgstr "`writeUint64` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3572 +#: src/api/reference.md:3592 msgid "`writeValue` — instance _(class: `Serializer`)_" msgstr "`writeValue` — Instanz _(Klasse: `Serializer`)_" -#: src/api/reference.md:3576 +#: src/api/reference.md:3596 msgid "`promiseHooks`" msgstr "`promiseHooks`" -#: src/api/reference.md:3577 +#: src/api/reference.md:3597 msgid "`startupSnapshot`" msgstr "`startupSnapshot`" -#: src/api/reference.md:3579 +#: src/api/reference.md:3599 msgid "`vm`" msgstr "`vm`" -#: src/api/reference.md:3583 +#: src/api/reference.md:3603 msgid "`Script`" msgstr "`Script`" -#: src/api/reference.md:3587 +#: src/api/reference.md:3607 msgid "`compileFunction` — module" msgstr "`compileFunction` — Modul" -#: src/api/reference.md:3588 +#: src/api/reference.md:3608 msgid "`createCachedData` — instance" msgstr "`createCachedData` — Instanz" -#: src/api/reference.md:3589 +#: src/api/reference.md:3609 msgid "`createContext` — module" msgstr "`createContext` — Modul" -#: src/api/reference.md:3590 +#: src/api/reference.md:3610 msgid "`createScript` — module" msgstr "`createScript` — Modul" -#: src/api/reference.md:3591 +#: src/api/reference.md:3611 msgid "`dependencySpecifiers` — instance" msgstr "`dependencySpecifiers` — Instanz" -#: src/api/reference.md:3592 +#: src/api/reference.md:3612 msgid "`error` — instance" msgstr "`error` — Instanz" -#: src/api/reference.md:3593 +#: src/api/reference.md:3613 msgid "`evaluate` — instance" msgstr "`evaluate` — Instanz" -#: src/api/reference.md:3594 +#: src/api/reference.md:3614 msgid "`hasAsyncGraph` — instance" msgstr "`hasAsyncGraph` — Instanz" -#: src/api/reference.md:3595 +#: src/api/reference.md:3615 msgid "`hasTopLevelAwait` — instance" msgstr "`hasTopLevelAwait` — Instanz" -#: src/api/reference.md:3596 +#: src/api/reference.md:3616 msgid "`identifier` — instance" msgstr "`identifier` — Instanz" -#: src/api/reference.md:3597 +#: src/api/reference.md:3617 msgid "`instantiate` — instance" msgstr "`instantiate` — Instanz" -#: src/api/reference.md:3598 +#: src/api/reference.md:3618 msgid "`isContext` — module" msgstr "`isContext` — Modul" -#: src/api/reference.md:3599 +#: src/api/reference.md:3619 msgid "`link` — instance" msgstr "`link` — Instanz" -#: src/api/reference.md:3600 +#: src/api/reference.md:3620 msgid "`linkRequests` — instance" msgstr "`linkRequests` — Instanz" -#: src/api/reference.md:3601 +#: src/api/reference.md:3621 msgid "`measureMemory` — module" msgstr "`measureMemory` — Modul" -#: src/api/reference.md:3602 +#: src/api/reference.md:3622 msgid "`moduleRequests` — instance" msgstr "`moduleRequests` — Instanz" -#: src/api/reference.md:3603 +#: src/api/reference.md:3623 msgid "`namespace` — instance" msgstr "`namespace` — Instanz" -#: src/api/reference.md:3604 +#: src/api/reference.md:3624 msgid "`runInContext` — module" msgstr "`runInContext` — Modul" -#: src/api/reference.md:3605 +#: src/api/reference.md:3625 msgid "`runInNewContext` — module" msgstr "`runInNewContext` — Modul" -#: src/api/reference.md:3606 +#: src/api/reference.md:3626 msgid "`runInThisContext` — module" msgstr "`runInThisContext` — Modul" -#: src/api/reference.md:3607 +#: src/api/reference.md:3627 msgid "`setExport` — instance" msgstr "`setExport` — Instanz" -#: src/api/reference.md:3608 +#: src/api/reference.md:3628 msgid "`status` — instance" msgstr "`status` — Instanz" -#: src/api/reference.md:3615 +#: src/api/reference.md:3635 msgid "`wasi`" msgstr "`wasi`" -#: src/api/reference.md:3619 +#: src/api/reference.md:3639 msgid "`WASI`" msgstr "`WASI`" -#: src/api/reference.md:3623 +#: src/api/reference.md:3643 msgid "`WASI` — module" msgstr "`WASI` — Modul" -#: src/api/reference.md:3624 +#: src/api/reference.md:3644 msgid "`finalizeBindings` — instance _(class: `WASI`)_" msgstr "`finalizeBindings` — Instanz _(Klasse: `WASI`)_" -#: src/api/reference.md:3625 +#: src/api/reference.md:3645 msgid "`getImportObject` — instance _(class: `WASI`)_" msgstr "`getImportObject` — Instanz _(Klasse: `WASI`)_" -#: src/api/reference.md:3626 +#: src/api/reference.md:3646 msgid "`initialize` — instance _(class: `WASI`)_" msgstr "`initialize` — Instanz _(Klasse: `WASI`)_" -#: src/api/reference.md:3627 +#: src/api/reference.md:3647 msgid "`start` — instance _(class: `WASI`)_" msgstr "`start` — Instanz _(Klasse: `WASI`)_" -#: src/api/reference.md:3631 +#: src/api/reference.md:3651 msgid "`wasiImport`" msgstr "`wasiImport`" -#: src/api/reference.md:3637 +#: src/api/reference.md:3653 +msgid "`worker_threads`" +msgstr "`worker_threads`" + +#: src/api/reference.md:3657 msgid "`BroadcastChannel`" msgstr "`BroadcastChannel`" -#: src/api/reference.md:3638 +#: src/api/reference.md:3658 msgid "`MessageChannel`" msgstr "`MessageChannel`" -#: src/api/reference.md:3639 +#: src/api/reference.md:3659 msgid "`MessagePort`" msgstr "`MessagePort`" -#: src/api/reference.md:3644 +#: src/api/reference.md:3664 msgid "`BroadcastChannel` — module" msgstr "`BroadcastChannel` — Modul" -#: src/api/reference.md:3645 +#: src/api/reference.md:3665 msgid "`MessageChannel` — module" msgstr "`MessageChannel` — Modul" -#: src/api/reference.md:3646 +#: src/api/reference.md:3666 msgid "`addEventListener` — instance" msgstr "`addEventListener` — Instanz" -#: src/api/reference.md:3647 +#: src/api/reference.md:3667 msgid "`cpuUsage` — instance _(class: `Worker`)_" msgstr "`cpuUsage` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3648 +#: src/api/reference.md:3668 msgid "`getEnvironmentData` — module" msgstr "`getEnvironmentData` — Modul" -#: src/api/reference.md:3649 +#: src/api/reference.md:3669 msgid "`getHeapSnapshot` — instance _(class: `Worker`)_" msgstr "`getHeapSnapshot` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3650 +#: src/api/reference.md:3670 msgid "`getHeapStatistics` — instance _(class: `Worker`)_" msgstr "`getHeapStatistics` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3651 +#: src/api/reference.md:3671 msgid "`isMarkedAsUntransferable` — module" msgstr "`isMarkedAsUntransferable` — Modul" -#: src/api/reference.md:3652 +#: src/api/reference.md:3672 msgid "`markAsUncloneable` — module" msgstr "`markAsUncloneable` — Modul" -#: src/api/reference.md:3653 +#: src/api/reference.md:3673 msgid "`markAsUntransferable` — module" msgstr "`markAsUntransferable` — Modul" -#: src/api/reference.md:3654 +#: src/api/reference.md:3674 msgid "`moveMessagePortToContext` — module" msgstr "`moveMessagePortToContext` — Modul" -#: src/api/reference.md:3655 +#: src/api/reference.md:3675 msgid "`off` — instance _(class: `Worker`)_" msgstr "`off` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3656 +#: src/api/reference.md:3676 msgid "`on` — instance _(class: `Worker`)_" msgstr "`on` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3657 +#: src/api/reference.md:3677 msgid "`once` — instance _(class: `Worker`)_" msgstr "`once` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3658 +#: src/api/reference.md:3678 msgid "`postMessageToThread` — module" msgstr "`postMessageToThread` — Modul" -#: src/api/reference.md:3659 +#: src/api/reference.md:3679 msgid "`receiveMessageOnPort` — module" msgstr "`receiveMessageOnPort` — Modul" -#: src/api/reference.md:3660 +#: src/api/reference.md:3680 msgid "`ref` — instance _(class: `Worker`)_" msgstr "`ref` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3661 +#: src/api/reference.md:3681 msgid "`reload` — instance _(class: `Worker`)_" msgstr "`reload` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3662 +#: src/api/reference.md:3682 msgid "`removeEventListener` — instance" msgstr "`removeEventListener` — Instanz" -#: src/api/reference.md:3663 +#: src/api/reference.md:3683 msgid "`setEnvironmentData` — module" msgstr "`setEnvironmentData` — Modul" -#: src/api/reference.md:3664 +#: src/api/reference.md:3684 msgid "`startCpuProfile` — instance _(class: `Worker`)_" msgstr "`startCpuProfile` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3665 +#: src/api/reference.md:3685 msgid "`startHeapProfile` — instance _(class: `Worker`)_" msgstr "`startHeapProfile` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3666 +#: src/api/reference.md:3686 msgid "`terminate` — instance _(class: `Worker`)_" msgstr "`terminate` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3667 +#: src/api/reference.md:3687 msgid "`unref` — instance _(class: `Worker`)_" msgstr "`unref` — Instanz _(Klasse: `Worker`)_" -#: src/api/reference.md:3671 +#: src/api/reference.md:3691 msgid "`SHARE_ENV`" msgstr "`SHARE_ENV`" -#: src/api/reference.md:3672 +#: src/api/reference.md:3692 msgid "`isInternalThread`" msgstr "`isInternalThread`" -#: src/api/reference.md:3673 +#: src/api/reference.md:3693 msgid "`isMainThread`" msgstr "`isMainThread`" -#: src/api/reference.md:3674 +#: src/api/reference.md:3694 msgid "`locks`" msgstr "`locks`" -#: src/api/reference.md:3675 +#: src/api/reference.md:3695 msgid "`parentPort`" msgstr "`parentPort`" -#: src/api/reference.md:3676 +#: src/api/reference.md:3696 msgid "`resourceLimits`" msgstr "`resourceLimits`" -#: src/api/reference.md:3677 +#: src/api/reference.md:3697 msgid "`threadId`" msgstr "`threadId`" -#: src/api/reference.md:3678 +#: src/api/reference.md:3698 msgid "`threadName`" msgstr "`threadName`" -#: src/api/reference.md:3679 +#: src/api/reference.md:3699 msgid "`workerData`" msgstr "`workerData`" -#: src/api/reference.md:3685 +#: src/api/reference.md:3705 msgid "`Client`" msgstr "`Client`" -#: src/api/reference.md:3687 +#: src/api/reference.md:3707 msgid "`WebSocketServer`" msgstr "`WebSocketServer`" -#: src/api/reference.md:3692 +#: src/api/reference.md:3712 msgid "`WebSocket` — module" msgstr "`WebSocket` — Modul" -#: src/api/reference.md:3693 +#: src/api/reference.md:3713 msgid "`addListener` — instance _(class: `Client`)_" msgstr "`addListener` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3694 +#: src/api/reference.md:3714 #, fuzzy msgid "`address` — instance" msgstr "`add` — Instanz" -#: src/api/reference.md:3695 +#: src/api/reference.md:3715 #, fuzzy msgid "`clients` — instance" msgstr "`limits` — Instanz" -#: src/api/reference.md:3697 +#: src/api/reference.md:3717 msgid "`close` — instance _(class: `Client`)_" msgstr "`close` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3698 +#: src/api/reference.md:3718 msgid "`closeClient` — module" msgstr "`closeClient` — Modul" -#: src/api/reference.md:3700 +#: src/api/reference.md:3720 msgid "`handleUpgrade` — instance" msgstr "`handleUpgrade` — Instanz" -#: src/api/reference.md:3702 +#: src/api/reference.md:3722 msgid "`on` — instance _(class: `Client`)_" msgstr "`on` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3703 +#: src/api/reference.md:3723 #, fuzzy msgid "`ping` — instance" msgstr "`png` — Instanz" -#: src/api/reference.md:3704 +#: src/api/reference.md:3724 #, fuzzy msgid "`ping` — instance _(class: `Client`)_" msgstr "`on` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3705 +#: src/api/reference.md:3725 #, fuzzy msgid "`pong` — instance" msgstr "`png` — Instanz" -#: src/api/reference.md:3706 +#: src/api/reference.md:3726 #, fuzzy msgid "`pong` — instance _(class: `Client`)_" msgstr "`on` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3707 +#: src/api/reference.md:3727 msgid "`readyState` — instance" msgstr "`readyState` — Instanz" -#: src/api/reference.md:3708 +#: src/api/reference.md:3728 msgid "`send` — instance" msgstr "`send` — Instanz" -#: src/api/reference.md:3709 +#: src/api/reference.md:3729 msgid "`send` — instance _(class: `Client`)_" msgstr "`send` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3710 +#: src/api/reference.md:3730 msgid "`sendToClient` — module" msgstr "`sendToClient` — Modul" -#: src/api/reference.md:3711 +#: src/api/reference.md:3731 #, fuzzy msgid "`terminate` — instance" msgstr "`terminal` — Instanz" -#: src/api/reference.md:3712 +#: src/api/reference.md:3732 #, fuzzy msgid "`terminate` — instance _(class: `Client`)_" msgstr "`send` — Instanz _(Klasse: `Client`)_" -#: src/api/reference.md:3716 +#: src/api/reference.md:3736 msgid "`CLOSED`" msgstr "`CLOSED`" -#: src/api/reference.md:3717 +#: src/api/reference.md:3737 msgid "`CLOSING`" msgstr "`CLOSING`" -#: src/api/reference.md:3718 +#: src/api/reference.md:3738 msgid "`CONNECTING`" msgstr "`CONNECTING`" -#: src/api/reference.md:3719 +#: src/api/reference.md:3739 msgid "`OPEN`" msgstr "`OPEN`" -#: src/api/reference.md:3725 src/api/reference.md:3726 +#: src/api/reference.md:3745 src/api/reference.md:3746 msgid "`BrotliCompress`" msgstr "`BrotliCompress`" -#: src/api/reference.md:3727 src/api/reference.md:3728 +#: src/api/reference.md:3747 src/api/reference.md:3748 msgid "`BrotliDecompress`" msgstr "`BrotliDecompress`" -#: src/api/reference.md:3729 src/api/reference.md:3730 +#: src/api/reference.md:3749 src/api/reference.md:3750 msgid "`Deflate`" msgstr "`Deflate`" -#: src/api/reference.md:3731 src/api/reference.md:3732 +#: src/api/reference.md:3751 src/api/reference.md:3752 msgid "`DeflateRaw`" msgstr "`DeflateRaw`" -#: src/api/reference.md:3733 src/api/reference.md:3734 +#: src/api/reference.md:3753 src/api/reference.md:3754 msgid "`Gunzip`" msgstr "`Gunzip`" -#: src/api/reference.md:3735 src/api/reference.md:3736 +#: src/api/reference.md:3755 src/api/reference.md:3756 msgid "`Gzip`" msgstr "`Gzip`" -#: src/api/reference.md:3737 src/api/reference.md:3738 +#: src/api/reference.md:3757 src/api/reference.md:3758 msgid "`Inflate`" msgstr "`Inflate`" -#: src/api/reference.md:3739 src/api/reference.md:3740 +#: src/api/reference.md:3759 src/api/reference.md:3760 msgid "`InflateRaw`" msgstr "`InflateRaw`" -#: src/api/reference.md:3741 src/api/reference.md:3742 +#: src/api/reference.md:3761 src/api/reference.md:3762 msgid "`Unzip`" msgstr "`Unzip`" -#: src/api/reference.md:3743 +#: src/api/reference.md:3763 msgid "`ZstdCompress`" msgstr "`ZstdCompress`" -#: src/api/reference.md:3744 +#: src/api/reference.md:3764 msgid "`ZstdDecompress`" msgstr "`ZstdDecompress`" -#: src/api/reference.md:3748 +#: src/api/reference.md:3768 msgid "`brotliCompress` — module" msgstr "`brotliCompress` — Modul" -#: src/api/reference.md:3749 +#: src/api/reference.md:3769 msgid "`brotliCompressSync` — module" msgstr "`brotliCompressSync` — Modul" -#: src/api/reference.md:3750 +#: src/api/reference.md:3770 msgid "`brotliDecompress` — module" msgstr "`brotliDecompress` — Modul" -#: src/api/reference.md:3751 +#: src/api/reference.md:3771 msgid "`brotliDecompressSync` — module" msgstr "`brotliDecompressSync` — Modul" -#: src/api/reference.md:3752 +#: src/api/reference.md:3772 msgid "`crc32` — module" msgstr "`crc32` — Modul" -#: src/api/reference.md:3753 +#: src/api/reference.md:3773 msgid "" "`createBrotliCompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46263,7 +46033,7 @@ msgstr "" "`createBrotliCompress` — Modul **Stub** — params/quality-Optionen " "akzeptiert, aber ignoriert, warnt einmal (#4917)" -#: src/api/reference.md:3754 +#: src/api/reference.md:3774 msgid "" "`createBrotliDecompress` — module ⚠ **stub** — params/quality options " "accepted but ignored, warns once (#4917)" @@ -46271,7 +46041,7 @@ msgstr "" "`createBrotliDecompress` — Modul **Stub** — params/quality-Optionen " "akzeptiert, aber ignoriert, warnt einmal (#4917)" -#: src/api/reference.md:3755 +#: src/api/reference.md:3775 msgid "" "`createDeflate` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46279,7 +46049,7 @@ msgstr "" "`createDeflate` — Modul **Stub** — Stufe ausgezeichnet; strategy/memLevel " "validiert, aber nicht angewendet (#4917)" -#: src/api/reference.md:3756 +#: src/api/reference.md:3776 msgid "" "`createDeflateRaw` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46287,11 +46057,11 @@ msgstr "" "`createDeflateRaw` — Modul **Stub** — Stufe ausgezeichnet; strategy/memLevel " "validiert, aber nicht angewendet (#4917)" -#: src/api/reference.md:3757 +#: src/api/reference.md:3777 msgid "`createGunzip` — module" msgstr "`createGunzip` — Modul" -#: src/api/reference.md:3758 +#: src/api/reference.md:3778 msgid "" "`createGzip` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46299,19 +46069,19 @@ msgstr "" "`createGzip` — Modul **Stub** — Stufe ausgezeichnet; strategy/memLevel " "validiert, aber nicht angewendet (#4917)" -#: src/api/reference.md:3759 +#: src/api/reference.md:3779 msgid "`createInflate` — module" msgstr "`createInflate` — Modul" -#: src/api/reference.md:3760 +#: src/api/reference.md:3780 msgid "`createInflateRaw` — module" msgstr "`createInflateRaw` — Modul" -#: src/api/reference.md:3761 +#: src/api/reference.md:3781 msgid "`createUnzip` — module" msgstr "`createUnzip` — Modul" -#: src/api/reference.md:3762 +#: src/api/reference.md:3782 msgid "" "`createZstdCompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46319,7 +46089,7 @@ msgstr "" "`createZstdCompress` — Modul **Stub** — params/quality-Optionen akzeptiert, " "aber ignoriert, warnt einmal (#4917)" -#: src/api/reference.md:3763 +#: src/api/reference.md:3783 msgid "" "`createZstdDecompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46327,71 +46097,71 @@ msgstr "" "`createZstdDecompress` — Modul **Stub** — params/quality-Optionen " "akzeptiert, aber ignoriert, warnt einmal (#4917)" -#: src/api/reference.md:3764 +#: src/api/reference.md:3784 msgid "`deflate` — module" msgstr "`deflate` — Modul" -#: src/api/reference.md:3765 +#: src/api/reference.md:3785 msgid "`deflateRaw` — module" msgstr "`deflateRaw` — Modul" -#: src/api/reference.md:3766 +#: src/api/reference.md:3786 msgid "`deflateRawSync` — module" msgstr "`deflateRawSync` — Modul" -#: src/api/reference.md:3767 +#: src/api/reference.md:3787 msgid "`deflateSync` — module" msgstr "`deflateSync` — Modul" -#: src/api/reference.md:3768 +#: src/api/reference.md:3788 msgid "`gunzip` — module" msgstr "`gunzip` — Modul" -#: src/api/reference.md:3769 +#: src/api/reference.md:3789 msgid "`gunzipSync` — module" msgstr "`gunzipSync` — Modul" -#: src/api/reference.md:3770 +#: src/api/reference.md:3790 msgid "`gzip` — module" msgstr "`gzip` — Modul" -#: src/api/reference.md:3771 +#: src/api/reference.md:3791 msgid "`gzipSync` — module" msgstr "`gzipSync` — Modul" -#: src/api/reference.md:3772 +#: src/api/reference.md:3792 msgid "`inflate` — module" msgstr "`inflate` — Modul" -#: src/api/reference.md:3773 +#: src/api/reference.md:3793 msgid "`inflateRaw` — module" msgstr "`inflateRaw` — Modul" -#: src/api/reference.md:3774 +#: src/api/reference.md:3794 msgid "`inflateRawSync` — module" msgstr "`inflateRawSync` — Modul" -#: src/api/reference.md:3775 +#: src/api/reference.md:3795 msgid "`inflateSync` — module" msgstr "`inflateSync` — Modul" -#: src/api/reference.md:3776 +#: src/api/reference.md:3796 msgid "`unzip` — module" msgstr "`unzip` — Modul" -#: src/api/reference.md:3777 +#: src/api/reference.md:3797 msgid "`unzipSync` — module" msgstr "`unzipSync` — Modul" -#: src/api/reference.md:3778 +#: src/api/reference.md:3798 msgid "`zstdCompress` — module" msgstr "`zstdCompress` — Modul" -#: src/api/reference.md:3779 +#: src/api/reference.md:3799 msgid "`zstdCompressSync` — module" msgstr "`zstdCompressSync` — Modul" -#: src/api/reference.md:3785 +#: src/api/reference.md:3805 msgid "`codes`" msgstr "`codes`" @@ -46604,7 +46374,7 @@ msgid "(default — none)" msgstr "(Standard — keine)" #: src/container/overview.md:72 src/container/determinism.md:60 -#: src/internals/memory-model.md:168 src/internals/memory-model.md:185 +#: src/internals/memory-model.md:153 src/internals/memory-model.md:170 msgid "Env var" msgstr "Umgebungsvariable" @@ -47143,8 +46913,8 @@ msgstr "`volumes`" #: src/container/containers.md:54 msgid "" -"Volume mounts in `\"host:container[:ro]\"` form, e.g. `[\"./data:/data:" -"ro\"]`." +"Volume mounts in `\"host:container[:ro]\"` form, e.g. `[\"./data:/data:ro" +"\"]`." msgstr "" "Volume-Mounts in der Form `\"host:container[:ro]\"`, z. B. `[\"./data:/data:" "ro\"]`." @@ -47597,13 +47367,13 @@ msgstr "Backend-Introspektion" #: src/container/containers.md:212 msgid "" -"`getBackend()` is synchronous and returns the canonical backend name " -"(`\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). It will perform a " +"`getBackend()` is synchronous and returns the canonical backend name (`" +"\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). It will perform a " "synchronous in-place probe on first call so the result is always the live " "name; calls after the first hit a cached `OnceLock` and return instantly." msgstr "" -"`getBackend()` ist synchron und gibt den kanonischen Backend-Namen zurück " -"(`\"docker\"`, `\"podman\"`, `\"apple/container\"` usw.). Beim ersten Aufruf " +"`getBackend()` ist synchron und gibt den kanonischen Backend-Namen zurück (`" +"\"docker\"`, `\"podman\"`, `\"apple/container\"` usw.). Beim ersten Aufruf " "wird eine synchrone Sondierung durchgeführt, sodass das Ergebnis stets der " "aktuelle Name ist; nachfolgende Aufrufe treffen einen gecachten `OnceLock` " "und kehren sofort zurück." @@ -47716,8 +47486,8 @@ msgid "" " image: \"myorg/api:1.0\",\n" " depends_on: { db: { condition: \"service_healthy\" } },\n" " environment: {\n" -" DATABASE_URL: \"postgres://app:changeme@app-db:5432/" -"app\",\n" +" DATABASE_URL: \"postgres://app:changeme@app-db:5432/app" +"\",\n" " },\n" " ports: [\"8080:8080\"],\n" " networks: [\"app-db-net\", \"app-web-net\"],\n" @@ -47770,8 +47540,8 @@ msgstr "" " image: \"myorg/api:1.0\",\n" " depends_on: { db: { condition: \"service_healthy\" } },\n" " environment: {\n" -" DATABASE_URL: \"postgres://app:changeme@app-db:5432/" -"app\",\n" +" DATABASE_URL: \"postgres://app:changeme@app-db:5432/app" +"\",\n" " },\n" " ports: [\"8080:8080\"],\n" " networks: [\"app-db-net\", \"app-web-net\"],\n" @@ -48904,11 +48674,11 @@ msgstr "**Masquerade / NAT deaktivieren** (Host-seitig)" #: src/container/networking.md:178 msgid "" -"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": " -"\"false\" }`" +"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": \"false" +"\" }`" msgstr "" -"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": " -"\"false\" }`" +"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": \"false" +"\" }`" #: src/container/networking.md:179 msgid "**Custom MTU** (matches host network)" @@ -49499,8 +49269,8 @@ msgid "" "Linux capabilities to add. Specific (e.g. `[\"NET_BIND_SERVICE\"]`), not " "blanket." msgstr "" -"Hinzuzufügende Linux-Capabilities. Spezifisch (z. B. " -"`[\"NET_BIND_SERVICE\"]`), nicht pauschal." +"Hinzuzufügende Linux-Capabilities. Spezifisch (z. B. `[\"NET_BIND_SERVICE" +"\"]`), nicht pauschal." #: src/container/security.md:21 msgid "" @@ -49606,8 +49376,8 @@ msgid "" "`nobody` on most distros)." msgstr "" "`user: \"nobody\"` — kein Root-Benutzer innerhalb des Containers. Falls das " -"Image keinen `nobody`-Benutzer hat, ersetzen Sie dies durch " -"`\"65534:65534\"` (die numerische UID/GID von `nobody` auf den meisten " +"Image keinen `nobody`-Benutzer hat, ersetzen Sie dies durch `" +"\"65534:65534\"` (die numerische UID/GID von `nobody` auf den meisten " "Distributionen)." #: src/container/security.md:78 @@ -50000,8 +49770,8 @@ msgid "" "**Drop all capabilities, add specific ones back** (`cap_drop: [\"ALL\"]` + " "minimal `cap_add`)." msgstr "" -"**Alle Funktionen fallen lassen, spezifische hinzufügen** (`cap_drop: " -"[\"ALL\"]` + minimal `cap_add`)." +"**Alle Funktionen fallen lassen, spezifische hinzufügen** (`cap_drop: [\"ALL" +"\"]` + minimal `cap_add`)." #: src/container/security.md:209 msgid "**Read-only root filesystem** (`read_only: true`)." @@ -50093,10 +49863,10 @@ msgid "" "down` invocation:" msgstr "" "Perrys Laufzeit liefert `process.on('SIGINT', ...)` derzeit nicht an Ihren " -"TypeScript-Code. Daher ist das kanonische Muster \"Strg-C beendet den " -"Stack\" heute nicht umsetzbar. Folgen Sie stattdessen dem Modell `docker " -"compose up -d` / `docker compose down`: Deploy + Verifizierung + exit 0, mit " -"dem Abbau hinter einem separaten `--down`-Aufruf:" +"TypeScript-Code. Daher ist das kanonische Muster \"Strg-C beendet den Stack" +"\" heute nicht umsetzbar. Folgen Sie stattdessen dem Modell `docker compose " +"up -d` / `docker compose down`: Deploy + Verifizierung + exit 0, mit dem " +"Abbau hinter einem separaten `--down`-Aufruf:" #: src/container/production-patterns.md:20 msgid "" @@ -51311,8 +51081,8 @@ msgstr "// Beide produzieren ContainerInfo mit der gleichen Feldsemantik:" #: src/container/determinism.md:301 msgid "" -"User code reading `info.status` sees `\"running\"` from any backend — not " -"`\"Up 5 seconds\"` from docker vs `\"running\"` from apple." +"User code reading `info.status` sees `\"running\"` from any backend — not `" +"\"Up 5 seconds\"` from docker vs `\"running\"` from apple." msgstr "" "Benutzercode lesen `info.status` sieht `\"running\"` von jedem Backend — " "nicht `\"Up 5 seconds\"` von Docker vs `\"running\"` von Apfel." @@ -52716,18 +52486,18 @@ msgid "" "code determines the **symbol** (`USD` → `$`, `EUR` → `€`, `GBP` → `£`, …; " "codes without a well-known symbol render as the code itself), while the " "locale keeps controlling the **layout** — separators and symbol placement. " -"With `en = \"GBP\"`, English output becomes `£1,234.50`; with `de = " -"\"USD\"`, German output becomes `1.234,50 $`. Locales without an entry " -"(exact locale first, then base language) use their built-in default symbol." +"With `en = \"GBP\"`, English output becomes `£1,234.50`; with `de = \"USD" +"\"`, German output becomes `1.234,50 $`. Locales without an entry (exact " +"locale first, then base language) use their built-in default symbol." msgstr "" "Wenn `Currency(value)` aufgerufen wird, bestimmt der konfigurierte ISO 4217-" -"Code des aktiven Locales den **Symbol** (`USD` → `$`, `EUR` → `€`, `GBP` → " -"`£`, ...; Codes ohne ein bekanntes Symbol, das als Code selbst rendern " -"wird), während die Lokalität die **Layout** — Abscheider und " -"Symbolplatzierung. Mit `en = \"GBP\"` wird die englische Ausgabe " -"`£1,234.50`; mit `de = \"USD\"` wird die deutsche Ausgabe `1.234,50 $`. " -"Lokale ohne Eintrag (genaue Lokalisierung zuerst, dann Basissprache) " -"verwenden ihr eingebautes Standardsymbol." +"Code des aktiven Locales den **Symbol** (`USD` → `$`, `EUR` → `€`, `GBP` → `" +"£`, ...; Codes ohne ein bekanntes Symbol, das als Code selbst rendern wird), " +"während die Lokalität die **Layout** — Abscheider und Symbolplatzierung. Mit " +"`en = \"GBP\"` wird die englische Ausgabe `£1,234.50`; mit `de = \"USD\"` " +"wird die deutsche Ausgabe `1.234,50 $`. Lokale ohne Eintrag (genaue " +"Lokalisierung zuerst, dann Basissprache) verwenden ihr eingebautes " +"Standardsymbol." #: src/i18n/cli.md:1 msgid "i18n CLI Tools" @@ -52791,7 +52561,7 @@ msgstr "" msgid "Example Output" msgstr "Beispielausgabe" -#: src/i18n/cli.md:32 src/testing/ci-tiers.md:267 +#: src/i18n/cli.md:32 src/testing/ci-tiers.md:281 msgid "Workflow" msgstr "Workflow" @@ -53006,13 +52776,13 @@ msgstr "" #: src/updater/overview.md:28 msgid "" "```typescript\n" -"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/" -"updater\"\n" +"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/updater" +"\"\n" "```" msgstr "" "```typescript\n" -"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/" -"updater\"\n" +"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/updater" +"\"\n" "```" #: src/updater/overview.md:32 @@ -53028,8 +52798,8 @@ msgstr "" #: src/updater/overview.md:36 msgid "" "```typescript\n" -"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for " -"updates\"\n" +"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for updates" +"\"\n" "// menu item or a periodic timer. The manifest URL must serve over HTTPS.\n" "//\n" "// const update = await checkForUpdate({\n" @@ -53053,8 +52823,8 @@ msgid "" "```" msgstr "" "```typescript\n" -"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for " -"updates\"\n" +"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for updates" +"\"\n" "// menu item or a periodic timer. The manifest URL must serve over HTTPS.\n" "//\n" "// const update = await checkForUpdate({\n" @@ -53153,8 +52923,8 @@ msgid "" "// \"notes\": \"Bug fixes and performance improvements\",\n" "// \"platforms\": {\n" "// \"darwin-aarch64\": {\n" -"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64." -"bin\",\n" +"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64.bin" +"\",\n" "// \"sha256\": \"0123456789abcdef...\",\n" "// \"signature\": \"base64sig==\",\n" "// \"size\": 12345678\n" @@ -53177,8 +52947,8 @@ msgstr "" "// \"notes\": \"Bug fixes and performance improvements\",\n" "// \"platforms\": {\n" "// \"darwin-aarch64\": {\n" -"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64." -"bin\",\n" +"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64.bin" +"\",\n" "// \"sha256\": \"0123456789abcdef...\",\n" "// \"signature\": \"base64sig==\",\n" "// \"size\": 12345678\n" @@ -53293,7 +53063,7 @@ msgstr "`darwin-x86_64`" msgid "Windows 10/11 64-bit" msgstr "Windows 10/11 64-Bit" -#: src/updater/overview.md:123 src/cli/flags.md:122 +#: src/updater/overview.md:123 src/cli/flags.md:144 msgid "`windows-x86_64`" msgstr "`windows-x86_64`" @@ -53301,7 +53071,7 @@ msgstr "`windows-x86_64`" msgid "Windows 11 ARM64" msgstr "Windows 11 ARM64" -#: src/updater/overview.md:124 src/cli/flags.md:123 +#: src/updater/overview.md:124 src/cli/flags.md:145 msgid "`windows-aarch64`" msgstr "`windows-aarch64`" @@ -53309,7 +53079,7 @@ msgstr "`windows-aarch64`" msgid "Linux 64-bit" msgstr "Linux 64-Bit" -#: src/updater/overview.md:125 src/cli/flags.md:126 +#: src/updater/overview.md:125 src/cli/flags.md:148 msgid "`linux-x86_64`" msgstr "`linux-x86_64`" @@ -53982,8 +53752,8 @@ msgid "" msgstr "" "**Privilegierte Installation** für systemweite Speicherorte (`/" "Applications`, `Program Files`). Der aktuelle Install-Pfad behandelt nur " -"benutzerschreibbare Speicherorte (`~/Applications`, `~/.local/bin`, " -"`%LOCALAPPDATA%`). UAC / `SMJobBless` ist eine separate Angelegenheit." +"benutzerschreibbare Speicherorte (`~/Applications`, `~/.local/bin`, `" +"%LOCALAPPDATA%`). UAC / `SMJobBless` ist eine separate Angelegenheit." #: src/updater/overview.md:427 msgid "" @@ -54132,11 +53902,11 @@ msgstr "`getDeviceIdiom()`" #: src/system/overview.md:25 msgid "" -"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, " -"`\"desktop\"`" +"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, `" +"\"desktop\"`" msgstr "" -"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, " -"`\"desktop\"`" +"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, `" +"\"desktop\"`" #: src/system/overview.md:26 msgid "`getDeviceModel()`" @@ -55547,7 +55317,7 @@ msgstr "" "Deklarationen](../../../types/perry/audio/index.d.ts). Zusammenfassung:" #: src/system/audio_module.md:152 src/plugins/native-extensions.md:202 -#: src/cli/commands.md:8 src/contributing/architecture.md:24 +#: src/cli/commands.md:8 src/contributing/architecture.md:25 msgid "Purpose" msgstr "Zweck" @@ -56033,8 +55803,8 @@ msgstr "" msgid "" "Stub platforms link cleanly against the same FFI surface — code that imports " "`perry/media` compiles on every target. `createPlayer` returns `0` on a stub " -"backend so `if (player === 0)` is the canonical \"feature not available " -"here\" check." +"backend so `if (player === 0)` is the canonical \"feature not available here" +"\" check." msgstr "" "Stub-Plattformen linken sauber gegen dieselbe FFI-Oberfläche — Code, der " "`perry/media` importiert, kompiliert auf jedem Target. `createPlayer` gibt " @@ -56343,8 +56113,8 @@ msgid "" " return `${serverUrl}/rest/stream?${params.toString()}`;\n" "}\n" "\n" -"const player = createPlayer(streamUrl(\"https://music.example.com\", " -"\"alice\", \"secret\", \"12345\"));\n" +"const player = createPlayer(streamUrl(\"https://music.example.com\", \"alice" +"\", \"secret\", \"12345\"));\n" "setNowPlaying(player, \"All These Things That I've Done\", \"The Killers\", " "\"Hot Fuss\",\n" " \"https://music.example.com/rest/getCoverArt?" @@ -56370,8 +56140,8 @@ msgstr "" " return `${serverUrl}/rest/stream?${params.toString()}`;\n" "}\n" "\n" -"const player = createPlayer(streamUrl(\"https://music.example.com\", " -"\"alice\", \"secret\", \"12345\"));\n" +"const player = createPlayer(streamUrl(\"https://music.example.com\", \"alice" +"\", \"secret\", \"12345\"));\n" "setNowPlaying(player, \"All These Things That I've Done\", \"The Killers\", " "\"Hot Fuss\",\n" " \"https://music.example.com/rest/getCoverArt?" @@ -56478,8 +56248,8 @@ msgid "" "`\"no-provider-available\"`, `\"unsupported-platform\"`." msgstr "" "`onError(message)` — feuert bei Berechtigungs-Verweigerung, Timeout oder " -"Plattform-Unverfügbarkeit. Häufige Nachrichten: `\"permission-denied\"`, " -"`\"no-location\"`, `\"no-provider-available\"`, `\"unsupported-platform\"`." +"Plattform-Unverfügbarkeit. Häufige Nachrichten: `\"permission-denied\"`, `" +"\"no-location\"`, `\"no-provider-available\"`, `\"unsupported-platform\"`." #: src/system/geolocation.md:42 msgid "`geolocationWatch(callback): number`" @@ -56513,8 +56283,8 @@ msgstr "`geolocationRequestPermission(callback)`" #: src/system/geolocation.md:54 msgid "" "Request location permission. Calls `callback(status)` where status is one of " -"`\"granted\"`, `\"denied\"`, `\"restricted\"`, or `\"unsupported-" -"platform\"`. Safe to call repeatedly — already-granted permissions return " +"`\"granted\"`, `\"denied\"`, `\"restricted\"`, or `\"unsupported-platform" +"\"`. Safe to call repeatedly — already-granted permissions return " "immediately." msgstr "" "Fordert Standort-Berechtigung an. Ruft `callback(status)` auf, wobei status " @@ -56563,11 +56333,11 @@ msgstr "**tvOS / watchOS / visionOS / GTK4 / Windows / Web**" #: src/system/geolocation.md:66 msgid "" -"No-op stub — `geolocationGetCurrent` invokes `onError` immediately with " -"`\"unsupported-platform\"`." +"No-op stub — `geolocationGetCurrent` invokes `onError` immediately with `" +"\"unsupported-platform\"`." msgstr "" -"No-op-Stub — `geolocationGetCurrent` ruft `onError` sofort mit " -"`\"unsupported-platform\"` auf." +"No-op-Stub — `geolocationGetCurrent` ruft `onError` sofort mit `" +"\"unsupported-platform\"` auf." #: src/system/geolocation.md:68 msgid "Promise wrapper" @@ -57280,12 +57050,12 @@ msgid "" "identifier (`\"iPhone15,2\"`, `\"MacBookPro18,3\"`, etc. — on macOS this is " "sysctl `hw.model`)." msgstr "" -"`getDeviceIdiom()` gibt den breiten Formfaktor als String zurück: " -"`\"phone\"` oder `\"pad\"` auf iOS (und `\"phone\"` auf Android), `\"mac\"` " -"auf macOS, `\"tv\"` auf tvOS, `\"watch\"` auf watchOS, `\"vision\"` auf " -"visionOS und `\"desktop\"` auf Windows und Linux. `getDeviceModel()` gibt " -"die plattformspezifische Modellkennung (`\"iPhone15,2\"`, " -"`\"MacBookPro18,3\"`, etc.) zurück — auf macOS ist dies sysctl `hw.model`." +"`getDeviceIdiom()` gibt den breiten Formfaktor als String zurück: `\"phone" +"\"` oder `\"pad\"` auf iOS (und `\"phone\"` auf Android), `\"mac\"` auf " +"macOS, `\"tv\"` auf tvOS, `\"watch\"` auf watchOS, `\"vision\"` auf visionOS " +"und `\"desktop\"` auf Windows und Linux. `getDeviceModel()` gibt die " +"plattformspezifische Modellkennung (`\"iPhone15,2\"`, `\"MacBookPro18,3\"`, " +"etc.) zurück — auf macOS ist dies sysctl `hw.model`." #: src/system/other.md:74 msgid "[UI Overview](../ui/overview.md) — Building UIs" @@ -57743,8 +57513,8 @@ msgid "" "Widget({\n" " kind: \"TemplateLiteralWidget\",\n" " displayName: \"Template Literal\",\n" -" description: \"Template literals compile to Swift string " -"interpolation\",\n" +" description: \"Template literals compile to Swift string interpolation" +"\",\n" " entryFields: {\n" " name: \"string\",\n" " score: \"number\",\n" @@ -57760,8 +57530,8 @@ msgstr "" "Widget({\n" " kind: \"TemplateLiteralWidget\",\n" " displayName: \"Template Literal\",\n" -" description: \"Template literals compile to Swift string " -"interpolation\",\n" +" description: \"Template literals compile to Swift string interpolation" +"\",\n" " entryFields: {\n" " name: \"string\",\n" " score: \"number\",\n" @@ -57793,8 +57563,8 @@ msgid "" " displayName: \"City Weather\",\n" " description: \"Weather for a chosen city\",\n" " config: {\n" -" city: { type: \"string\", displayName: \"City\", default: \"New " -"York\" },\n" +" city: { type: \"string\", displayName: \"City\", default: \"New York" +"\" },\n" " units: {\n" " type: \"enum\",\n" " displayName: \"Units\",\n" @@ -57813,8 +57583,8 @@ msgstr "" " displayName: \"City Weather\",\n" " description: \"Weather for a chosen city\",\n" " config: {\n" -" city: { type: \"string\", displayName: \"City\", default: \"New " -"York\" },\n" +" city: { type: \"string\", displayName: \"City\", default: \"New York" +"\" },\n" " units: {\n" " type: \"enum\",\n" " displayName: \"Units\",\n" @@ -57847,8 +57617,8 @@ msgid "" " displayName: \"Stock Price\",\n" " description: \"Shows current stock price\",\n" " config: {\n" -" symbol: { type: \"string\", displayName: \"Symbol\", default: " -"\"AAPL\" },\n" +" symbol: { type: \"string\", displayName: \"Symbol\", default: \"AAPL" +"\" },\n" " },\n" " entryFields: { price: \"number\", change: \"string\" },\n" " provider: async (config) => {\n" @@ -57874,8 +57644,8 @@ msgstr "" " displayName: \"Stock Price\",\n" " description: \"Shows current stock price\",\n" " config: {\n" -" symbol: { type: \"string\", displayName: \"Symbol\", default: " -"\"AAPL\" },\n" +" symbol: { type: \"string\", displayName: \"Symbol\", default: \"AAPL" +"\" },\n" " },\n" " entryFields: { price: \"number\", change: \"string\" },\n" " provider: async (config) => {\n" @@ -57903,12 +57673,11 @@ msgid "" "issues/195). The verified extract uses the supported inline-options form " "`Text(\"...\", { font: \"title\" })`." msgstr "" -"Hinweis: Die Modifikatoren im Kettenstil (`.font(\"title\")." -"color(\"green\")`) werden mit einer Compil-Time-Diagnose abgelehnt, sodass " -"Styling nicht still verschwinden kann; dieses Verhalten schloss [\\#195]" -"(https://github.com/PerryTS/perry/issues/195). Der verifizierte Extrakt " -"verwendet die unterstützten Inline-Optionen von `Text(\"...\", { font: " -"\"title\" })`." +"Hinweis: Die Modifikatoren im Kettenstil (`.font(\"title\").color(\"green" +"\")`) werden mit einer Compil-Time-Diagnose abgelehnt, sodass Styling nicht " +"still verschwinden kann; dieses Verhalten schloss [\\#195](https://github." +"com/PerryTS/perry/issues/195). Der verifizierte Extrakt verwendet die " +"unterstützten Inline-Optionen von `Text(\"...\", { font: \"title\" })`." #: src/widgets/creating-widgets.md:192 msgid "Placeholder Data" @@ -57984,13 +57753,13 @@ msgstr "" #: src/widgets/creating-widgets.md:222 msgid "" -"Supported families: `\"systemSmall\"`, `\"systemMedium\"`, " -"`\"systemLarge\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, " -"`\"accessoryInline\"`." +"Supported families: `\"systemSmall\"`, `\"systemMedium\"`, `\"systemLarge" +"\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, `\"accessoryInline" +"\"`." msgstr "" -"Unterstützte Familien: `\"systemSmall\"`, `\"systemMedium\"`, " -"`\"systemLarge\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, " -"`\"accessoryInline\"`." +"Unterstützte Familien: `\"systemSmall\"`, `\"systemMedium\"`, `\"systemLarge" +"\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, `\"accessoryInline" +"\"`." #: src/widgets/creating-widgets.md:224 msgid "App Group" @@ -58104,8 +57873,8 @@ msgstr "" "id` von einem `// widget-bundle-id:`-Banner ([\\#194](https://github.com/" "PerryTS/perry/issues/194)) übergeben; End-to-End-Targets erfordern auch ihre " "Plattform SDK. Die Syntax des Modifiers ist bewusst auf **Inline-Option-" -"Objekt-Argumente** — e.g. `Text(\"hi\", { font: \"title\", color: " -"\"red\" })` und `VStack([...], { padding: 16 })`. Methoden-Stil Ketten wie " +"Objekt-Argumente** — e.g. `Text(\"hi\", { font: \"title\", color: \"red" +"\" })` und `VStack([...], { padding: 16 })`. Methoden-Stil Ketten wie " "`Text(\"hi\").font(\"title\")` werden mit einer umsetzbaren Compil-Time-" "Diagnose abgelehnt, anstatt stillschweigend ignoriert zu werden; dieses " "Verhalten schloss [\\#195](https://github.com/PerryTS/perry/issues/195). Die " @@ -58486,8 +58255,8 @@ msgid "" " VStack([\n" " Text(`${entry.steps}`, { font: \"title\", fontWeight: " "\"bold\" }),\n" -" Text(\"steps\", { font: \"caption\", color: " -"\"gray\" }),\n" +" Text(\"steps\", { font: \"caption\", color: \"gray" +"\" }),\n" " ]),\n" " Spacer(),\n" " VStack([\n" @@ -58530,8 +58299,8 @@ msgstr "" " VStack([\n" " Text(`${entry.steps}`, { font: \"title\", fontWeight: " "\"bold\" }),\n" -" Text(\"steps\", { font: \"caption\", color: " -"\"gray\" }),\n" +" Text(\"steps\", { font: \"caption\", color: \"gray" +"\" }),\n" " ]),\n" " Spacer(),\n" " VStack([\n" @@ -58641,8 +58410,8 @@ msgid "" "\n" " provider: async (config: { sortBy: string; dateRange: string }) => {\n" " const res = await fetch(\n" -" `https://api.example.com/stats?sort=${config.sortBy}" -"&range=${config.dateRange}`,\n" +" `https://api.example.com/stats?sort=${config.sortBy}&range=" +"${config.dateRange}`,\n" " )\n" " const data = await res.json()\n" " return {\n" @@ -58653,8 +58422,8 @@ msgid "" "\n" " render: (entry) =>\n" " VStack([\n" -" Text(`${entry.total}`, { font: \"title\", fontWeight: " -"\"bold\" }),\n" +" Text(`${entry.total}`, { font: \"title\", fontWeight: \"bold" +"\" }),\n" " Text(entry.label, { font: \"caption\", color: \"secondary\" }),\n" " ]),\n" "})\n" @@ -58690,8 +58459,8 @@ msgstr "" "\n" " provider: async (config: { sortBy: string; dateRange: string }) => {\n" " const res = await fetch(\n" -" `https://api.example.com/stats?sort=${config.sortBy}" -"&range=${config.dateRange}`,\n" +" `https://api.example.com/stats?sort=${config.sortBy}&range=" +"${config.dateRange}`,\n" " )\n" " const data = await res.json()\n" " return {\n" @@ -58702,8 +58471,8 @@ msgstr "" "\n" " render: (entry) =>\n" " VStack([\n" -" Text(`${entry.total}`, { font: \"title\", fontWeight: " -"\"bold\" }),\n" +" Text(`${entry.total}`, { font: \"title\", fontWeight: \"bold" +"\" }),\n" " Text(entry.label, { font: \"caption\", color: \"secondary\" }),\n" " ]),\n" "})\n" @@ -58741,7 +58510,7 @@ msgstr "`{ type: \"bool\", default: true, title: \"...\" }`" msgid "Toggle switch" msgstr "Umschalter" -#: src/widgets/configuration.md:74 src/internals/memory-model.md:26 +#: src/widgets/configuration.md:74 src/internals/memory-model.md:29 msgid "String" msgstr "String" @@ -58769,8 +58538,8 @@ msgstr "" msgid "" "```text\n" "provider: async (config: { sortBy: string; dateRange: string }) => {\n" -" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | " -"\"position\"\n" +" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | \"position" +"\"\n" " // config.dateRange === \"7d\" | \"28d\" | \"90d\"\n" " const url = `https://api.example.com/data?sort=${config.sortBy}`;\n" " const res = await fetch(url);\n" @@ -58781,8 +58550,8 @@ msgid "" msgstr "" "```text\n" "provider: async (config: { sortBy: string; dateRange: string }) => {\n" -" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | " -"\"position\"\n" +" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | \"position" +"\"\n" " // config.dateRange === \"7d\" | \"28d\" | \"90d\"\n" " const url = `https://api.example.com/data?sort=${config.sortBy}`;\n" " const res = await fetch(url);\n" @@ -58874,8 +58643,8 @@ msgid "" "Users configure the widget by long-pressing and selecting \"Edit Widget\", " "which presents the system-generated AppIntent UI." msgstr "" -"Nutzer konfigurieren das Widget per Long-Press und Auswahl von \"Edit " -"Widget\", was die systemgenerierte AppIntent-UI präsentiert." +"Nutzer konfigurieren das Widget per Long-Press und Auswahl von \"Edit Widget" +"\", was die systemgenerierte AppIntent-UI präsentiert." #: src/widgets/configuration.md:118 msgid "Android / Wear OS (Configuration Activity)" @@ -59062,8 +58831,8 @@ msgid "" " },\n" "\n" " provider: async () => {\n" -" const res = await fetch(\"https://api.weather.example.com/" -"current\")\n" +" const res = await fetch(\"https://api.weather.example.com/current" +"\")\n" " const data = await res.json()\n" " return {\n" " entries: [\n" @@ -59094,8 +58863,8 @@ msgstr "" " },\n" "\n" " provider: async () => {\n" -" const res = await fetch(\"https://api.weather.example.com/" -"current\")\n" +" const res = await fetch(\"https://api.weather.example.com/current" +"\")\n" " const data = await res.json()\n" " return {\n" " entries: [\n" @@ -59284,14 +59053,14 @@ msgstr "`{ after: { minutes: N } }`" #: src/widgets/data-fetching.md:136 msgid "" "Re-fetch after N minutes. Compiles to `.after(Date()." -"addingTimeInterval(N*60))` on iOS/watchOS, `android:" -"updatePeriodMillis=\"N*60000\"` on Android, and " -"`setFreshnessIntervalMillis(N*60000)` on Wear OS." +"addingTimeInterval(N*60))` on iOS/watchOS, `android:updatePeriodMillis=" +"\"N*60000\"` on Android, and `setFreshnessIntervalMillis(N*60000)` on Wear " +"OS." msgstr "" "Nach N-Minuten wieder zurückholen. Kompiliert mit `.after(Date()." -"addingTimeInterval(N*60))` auf iOS/watchOS, `android:" -"updatePeriodMillis=\"N*60000\"` auf Android und " -"`setFreshnessIntervalMillis(N*60000)` auf Wear OS." +"addingTimeInterval(N*60))` auf iOS/watchOS, `android:updatePeriodMillis=" +"\"N*60000\"` auf Android und `setFreshnessIntervalMillis(N*60000)` auf Wear " +"OS." #: src/widgets/data-fetching.md:137 msgid "_(omitted)_" @@ -59605,7 +59374,7 @@ msgid "SwiftUI `.swift` + Info.plist" msgstr "SwiftUI `.swift` + Info.plist" #: src/widgets/platforms.md:17 src/plugins/native-extensions.md:301 -#: src/testing/geisterhand.md:341 src/cli/flags.md:99 +#: src/testing/geisterhand.md:341 src/cli/flags.md:121 msgid "iOS Simulator" msgstr "iOS-Simulator" @@ -59633,7 +59402,7 @@ msgstr "`--target watchos-widget`" msgid "SwiftUI `.swift` (accessory families)" msgstr "SwiftUI `.swift` (Accessory-Familien)" -#: src/widgets/platforms.md:20 src/cli/flags.md:107 +#: src/widgets/platforms.md:20 src/cli/flags.md:129 msgid "watchOS Simulator" msgstr "watchOS-Simulator" @@ -61407,11 +61176,11 @@ msgstr "" #: src/plugins/native-extensions.md:95 msgid "" -"Array of LLVM types: `\"i64\"` for pointers/strings, `\"f64\"` for numbers, " -"`\"i32\"` for integers" +"Array of LLVM types: `\"i64\"` for pointers/strings, `\"f64\"` for numbers, `" +"\"i32\"` for integers" msgstr "" -"Array von LLVM-Typen: `\"i64\"` für Zeiger/Strings, `\"f64\"` für Zahlen, " -"`\"i32\"` für Integer" +"Array von LLVM-Typen: `\"i64\"` für Zeiger/Strings, `\"f64\"` für Zahlen, `" +"\"i32\"` für Integer" #: src/plugins/native-extensions.md:96 msgid "Return type — typically `\"f64\"` (NaN-boxed value or promise handle)" @@ -63364,8 +63133,8 @@ msgid "" msgstr "" "Findet einen registrierten Menüeintrag, dessen Shortcut passt, und löst " "dessen Callback aus. Shortcut-Strings sind case-insensitive und matchen den " -"Key-String, der an `menuAddItem` übergeben wurde (z. B. `\"s\"` für Cmd+S, " -"`\"S\"` für Cmd+Umschalt+S, `\"n\"` für Cmd+N)." +"Key-String, der an `menuAddItem` übergeben wurde (z. B. `\"s\"` für Cmd+S, `" +"\"S\"` für Cmd+Umschalt+S, `\"n\"` für Cmd+N)." #: src/testing/geisterhand.md:221 msgid "" @@ -63630,7 +63399,7 @@ msgstr "" "Der iOS-Simulator teilt sich den Netzwerk-Stack mit dem Host — greifen Sie " "direkt über `localhost` auf den Server zu:" -#: src/testing/geisterhand.md:352 src/cli/flags.md:100 +#: src/testing/geisterhand.md:352 src/cli/flags.md:122 msgid "iOS Device" msgstr "iOS-Gerät" @@ -64193,8 +63962,8 @@ msgstr "\"Inkremente\"" #: src/testing/geisterhand.md:657 #, fuzzy msgid "" -"# {\"handle\":4,\"widget_type\":0,\"callback_kind\":0,\"label\":" -"\"Reset\"},\n" +"# {\"handle\":4,\"widget_type\":0,\"callback_kind\":0,\"label\":\"Reset" +"\"},\n" msgstr "" "# ‚Begriff‘:4,‚widget_type‘:0,‚callback_kind‘:0,‚Beschriftung‘:" "‚Zurücksetzen‘}," @@ -64282,8 +64051,8 @@ msgid "" "**Widget Registration**: As UI widgets are created (Button, TextField, " "Slider, etc.), each one calls `perry_geisterhand_register(handle, " "widget_type, callback_kind, closure_f64, label)` to register its callback in " -"the global registry. This is gated behind `#[cfg(feature = " -"\"geisterhand\")]` so normal builds have zero overhead." +"the global registry. This is gated behind `#[cfg(feature = \"geisterhand" +"\")]` so normal builds have zero overhead." msgstr "" "**Widget-Registrierung**: Während UI-Widgets erstellt werden (Button, " "TextField, Slider usw.), ruft jedes `perry_geisterhand_register(handle, " @@ -64425,7 +64194,7 @@ msgid "Platform crate selection is automatic based on `--target`:" msgstr "" "Die Plattform-Crate-Auswahl erfolgt automatisch basierend auf `--target`:" -#: src/testing/geisterhand.md:774 src/cli/flags.md:96 +#: src/testing/geisterhand.md:774 src/cli/flags.md:118 msgid "Target" msgstr "Target" @@ -64437,7 +64206,7 @@ msgstr "UI-Crate" msgid "(default/macOS)" msgstr "(Standard/macOS)" -#: src/testing/geisterhand.md:776 src/contributing/architecture.md:39 +#: src/testing/geisterhand.md:776 src/contributing/architecture.md:42 msgid "`perry-ui-macos`" msgstr "`perry-ui-macos`" @@ -64445,12 +64214,12 @@ msgstr "`perry-ui-macos`" msgid "`ios` / `ios-simulator`" msgstr "`ios` / `ios-simulator`" -#: src/testing/geisterhand.md:777 src/contributing/architecture.md:40 +#: src/testing/geisterhand.md:777 src/contributing/architecture.md:43 msgid "`perry-ui-ios`" msgstr "`perry-ui-ios`" #: src/testing/geisterhand.md:778 src/cli/commands.md:98 -#: src/cli/commands.md:273 src/cli/flags.md:108 +#: src/cli/commands.md:273 src/cli/flags.md:130 msgid "`android`" msgstr "`android`" @@ -64458,7 +64227,7 @@ msgstr "`android`" msgid "`perry-ui-android`" msgstr "`perry-ui-android`" -#: src/testing/geisterhand.md:779 src/cli/commands.md:274 src/cli/flags.md:125 +#: src/testing/geisterhand.md:779 src/cli/commands.md:274 src/cli/flags.md:147 msgid "`linux`" msgstr "`linux`" @@ -64500,8 +64269,8 @@ msgid "" "All geisterhand code is behind `#[cfg(feature = \"geisterhand\")]` feature " "gates:" msgstr "" -"Der gesamte Geisterhand-Code steht hinter `#[cfg(feature = " -"\"geisterhand\")]`-Feature-Gates:" +"Der gesamte Geisterhand-Code steht hinter `#[cfg(feature = \"geisterhand" +"\")]`-Feature-Gates:" #: src/testing/geisterhand.md:790 msgid "" @@ -65737,8 +65506,8 @@ msgstr "" #: src/testing/http2-conformance.md:232 msgid "" -"`RST_STREAM stream=5 code=7`, `RST_STREAM stream=7 code=7` — " -"REFUSED\\_STREAM, session survives" +"`RST_STREAM stream=5 code=7`, `RST_STREAM stream=7 code=7` — REFUSED" +"\\_STREAM, session survives" msgstr "" #: src/testing/http2-conformance.md:233 @@ -65747,8 +65516,8 @@ msgstr "" #: src/testing/http2-conformance.md:233 msgid "" -"`RST_STREAM stream=1 code=2`, `GOAWAY stream=0 last=3 code=2` — " -"INTERNAL\\_ERROR, connection torn down, `ERR_HTTP2_ERROR errno=-505`" +"`RST_STREAM stream=1 code=2`, `GOAWAY stream=0 last=3 code=2` — INTERNAL" +"\\_ERROR, connection torn down, `ERR_HTTP2_ERROR errno=-505`" msgstr "" #: src/testing/http2-conformance.md:235 @@ -66046,8 +65815,8 @@ msgstr "" #: src/testing/http2-conformance.md:325 msgid "" -"trailers: event order is exactly `[\"response\",\"data\",\"trailers\"," -"\"end\"]`;" +"trailers: event order is exactly `[\"response\",\"data\",\"trailers\",\"end" +"\"]`;" msgstr "" #: src/testing/http2-conformance.md:326 @@ -66362,12 +66131,17 @@ msgstr "" "Freigabe läuft" #: src/testing/ci-tiers.md:3 +#, fuzzy msgid "" "Perry's CI is one workflow — `.github/workflows/test.yml` — with **three " "tiers**. The tier a run belongs to, and the exact set of jobs it executes, " "is decided by one script, `scripts/ci_plan.py`, in the workflow's first job " -"(`plan`). Every other job is `needs: plan` and gated on `fromJSON(needs.plan." -"outputs.plan).jobs.`." +"(`plan`). Planned jobs use `needs: plan` and `fromJSON(needs.plan.outputs." +"plan).jobs.`. The `tokio-ban` preflight runs independently on every PR " +"and core CI run. It checks repository Cargo manifests, lockfiles and Rust " +"source without a Rust toolchain. The gate requires its result to be " +"`success`; a missing, skipped, failed or cancelled preflight cannot pass `pr-" +"gate` or a release gate." msgstr "" "Perry's CI ist ein Workflow — `.github/workflows/test.yml` — mit **drei " "Ebenen**. Die Ebene, zu der ein Lauf gehört, und die genaue Anzahl an " @@ -66376,47 +66150,47 @@ msgstr "" "ist `needs: plan` und ist auf `fromJSON(needs.plan.outputs.plan).jobs." "` gegated." -#: src/testing/ci-tiers.md:13 src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:17 src/testing/ci-tiers.md:194 msgid "tier" msgstr "tier" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "trigger" msgstr "trigger" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "what it is for" msgstr "wofür es ist" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "fan-in job" msgstr "fan-in Arbeit" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "**pr**" msgstr "**pr**" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "every `pull_request` push" msgstr "jedes `pull_request`-Push" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "the required gate. Small, fast, must be green on `main`." msgstr "das erforderliche Tor. Klein, schnell, muss grün auf `main` sein." -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "`pr-gate` — **the only required status context**" msgstr "`pr-gate` — **der einzige erforderliche Statuskontext**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "**sweep**" msgstr "**Sweep**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "every `push` to `main` (coalesced) **+ a two-hourly cron backstop**" msgstr "alle `push` bis `main` (gekohlt) **+ ein zweistündiger Cron Backstop**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 #, fuzzy msgid "" "post-merge truth for `main`: unscoped unit/package tests plus medium-weight " @@ -66426,15 +66200,15 @@ msgstr "" "Post-Merge Wahrheit für `main`: die PR-Stufe unscoped plus die " "mittelschweren Jobs, die nicht in das PR-Budget passen" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "`main-gate`" msgstr "`main-gate`" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "**full**" msgstr "**vollständig**" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "" "nightly `schedule`, `v*` tags, `workflow_dispatch`, PRs labelled `run-" "extended-tests`" @@ -66442,7 +66216,7 @@ msgstr "" "Nachts `schedule`, `v*`-Tags, `workflow_dispatch`, PRs mit der Bezeichnung " "`run-extended-tests`" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 #, fuzzy msgid "" "everything, incl. parity, compile-smoke, doc-tests, package smokes, the 24-" @@ -66451,15 +66225,15 @@ msgstr "" "alles, inkl. Parität, Compil-Smoke, Doc-Tests, Paketrauche, die 8-harte Auto-" "Optimierungs-Lücke-Suite" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "`full-suite-gate` — what `release-packages.yml` waits for" msgstr "`full-suite-gate` — worauf `release-packages.yml` wartet" -#: src/testing/ci-tiers.md:19 +#: src/testing/ci-tiers.md:23 msgid "The job × tier matrix" msgstr "Der Auftrag × Ebenenmatrix" -#: src/testing/ci-tiers.md:21 +#: src/testing/ci-tiers.md:25 msgid "" "Generated by `python3 scripts/ci_plan.py --table`; the `lint` job checks " "that this copy is current." @@ -66467,7 +66241,7 @@ msgstr "" "Erzeugt von `python3 scripts/ci_plan.py --table`; der `lint`-Job überprüft, " "ob diese Kopie aktuell ist." -#: src/testing/ci-tiers.md:24 +#: src/testing/ci-tiers.md:28 msgid "" "The PR gap tier uses twelve fast-mode shards after a six-way shard exhausted " "the 110-minute job bound. The full gap tier uses twenty-four shards with " @@ -66483,151 +66257,161 @@ msgid "" "CI durations must still establish the new margin." msgstr "" -#: src/testing/ci-tiers.md:37 +#: src/testing/ci-tiers.md:41 msgid "job" msgstr "job" -#: src/testing/ci-tiers.md:37 +#: src/testing/ci-tiers.md:41 msgid "pr" msgstr "pr" -#: src/testing/ci-tiers.md:37 src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:198 msgid "sweep" msgstr "sweep" -#: src/testing/ci-tiers.md:37 src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:199 msgid "full" msgstr "full" -#: src/testing/ci-tiers.md:39 +#: src/testing/ci-tiers.md:43 msgid "`lint`" msgstr "`lint`" -#: src/testing/ci-tiers.md:39 +#: src/testing/ci-tiers.md:43 msgid "always" msgstr "always" -#: src/testing/ci-tiers.md:40 src/cli/commands.md:11 +#: src/testing/ci-tiers.md:44 src/cli/commands.md:11 msgid "`check`" msgstr "`check`" -#: src/testing/ci-tiers.md:41 +#: src/testing/ci-tiers.md:45 msgid "`warnings`" msgstr "`warnings`" -#: src/testing/ci-tiers.md:42 +#: src/testing/ci-tiers.md:46 msgid "`cargo-test`" msgstr "`cargo-test`" -#: src/testing/ci-tiers.md:43 +#: src/testing/ci-tiers.md:47 #, fuzzy msgid "`cargo-test-perry`" msgstr "`cargo-test`" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 msgid "`gap-suite`" msgstr "`gap-suite`" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 #, fuzzy msgid "12x fast" msgstr "6x schnell" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 msgid "3x fast" msgstr "3x schnell" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 #, fuzzy msgid "24x full" msgstr "8x vollständig" -#: src/testing/ci-tiers.md:45 +#: src/testing/ci-tiers.md:49 #, fuzzy msgid "`wasi-execution`" msgstr "`flexDirection`" -#: src/testing/ci-tiers.md:46 +#: src/testing/ci-tiers.md:50 msgid "`gc-call-effects`" msgstr "" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "`gc-stress`" msgstr "`gc-stress`" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "1x pr" msgstr "" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "4x all" msgstr "" -#: src/testing/ci-tiers.md:48 +#: src/testing/ci-tiers.md:52 msgid "`e2e-scoped`" msgstr "`e2e-scoped`" -#: src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:53 msgid "`security-audit`" msgstr "`security-audit`" -#: src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:53 msgid "deps only" msgstr "nur Deps" -#: src/testing/ci-tiers.md:50 +#: src/testing/ci-tiers.md:54 msgid "`windows-build`" msgstr "`windows-build`" -#: src/testing/ci-tiers.md:51 +#: src/testing/ci-tiers.md:55 msgid "`windows-arm64-build`" msgstr "`windows-arm64-build`" -#: src/testing/ci-tiers.md:52 +#: src/testing/ci-tiers.md:56 msgid "`compiler-output-regression`" msgstr "`compiler-output-regression`" -#: src/testing/ci-tiers.md:53 +#: src/testing/ci-tiers.md:57 msgid "`repsel-census`" msgstr "`repsel-census`" -#: src/testing/ci-tiers.md:54 +#: src/testing/ci-tiers.md:58 msgid "`harmonyos-smoke`" msgstr "`harmonyos-smoke`" -#: src/testing/ci-tiers.md:55 +#: src/testing/ci-tiers.md:59 msgid "`binary-size`" msgstr "`binary-size`" -#: src/testing/ci-tiers.md:56 +#: src/testing/ci-tiers.md:60 msgid "`parity`" msgstr "`parity`" -#: src/testing/ci-tiers.md:57 +#: src/testing/ci-tiers.md:61 +#, fuzzy +msgid "`node-suite-guard`" +msgstr "`nodemailer`" + +#: src/testing/ci-tiers.md:62 +#, fuzzy +msgid "`test262`" +msgstr "`test`" + +#: src/testing/ci-tiers.md:63 msgid "`compile-smoke`" msgstr "`compile-smoke`" -#: src/testing/ci-tiers.md:58 +#: src/testing/ci-tiers.md:64 msgid "`native-abi-evidence-packet`" msgstr "`native-abi-evidence-packet`" -#: src/testing/ci-tiers.md:59 +#: src/testing/ci-tiers.md:65 msgid "`drizzle-mysql-smoke`" msgstr "`drizzle-mysql-smoke`" -#: src/testing/ci-tiers.md:60 +#: src/testing/ci-tiers.md:66 msgid "`ink-link-smoke`" msgstr "`ink-link-smoke`" -#: src/testing/ci-tiers.md:61 +#: src/testing/ci-tiers.md:67 msgid "`effect-basic-smoke`" msgstr "`effect-basic-smoke`" -#: src/testing/ci-tiers.md:62 +#: src/testing/ci-tiers.md:68 msgid "`doc-tests`" msgstr "`doc-tests`" -#: src/testing/ci-tiers.md:64 +#: src/testing/ci-tiers.md:70 msgid "" "`windows-build` also runs `python scripts/run_windows_runtime_regressions." "py` after building the compiler and both static archives. It compares the " @@ -66640,19 +66424,21 @@ msgid "" "tier (`run-extended-tests`) when reviewing changes to this behavior." msgstr "" -#: src/testing/ci-tiers.md:74 +#: src/testing/ci-tiers.md:80 msgid "Within the **pr** tier the changed-file list narrows the plan further:" msgstr "In der **pr** Ebene die geänderte Dateiliste verengt den Plan weiter:" -#: src/testing/ci-tiers.md:76 +#: src/testing/ci-tiers.md:82 +#, fuzzy msgid "" "**docs-only** (only `docs/`, `*.md`, `benchmarks/`, `npm/`, `packaging/`, `." -"claude/`, … — see `NON_CORE_GLOBS` in `ci_plan.py`) → only `lint` runs." +"claude/`, … — see `NON_CORE_GLOBS` in `ci_plan.py`) → `lint` and the " +"mandatory `tokio-ban` preflight run." msgstr "" "**Nur für** (nur `docs/`, `*.md`, `benchmarks/`, `npm/`, `packaging/`, `." "claude/`, ... — siehe `NON_CORE_GLOBS` in `ci_plan.py`) → nur `lint` läuft." -#: src/testing/ci-tiers.md:78 +#: src/testing/ci-tiers.md:85 msgid "" "**core** (anything that can change the compiler, the runtime, or a test " "outcome) → the whole PR tier." @@ -66660,7 +66446,7 @@ msgstr "" "**Kern** (alles, was den Compiler, die Laufzeit oder ein Testergebnis ändern " "kann) → die gesamte PR-Ebene." -#: src/testing/ci-tiers.md:80 +#: src/testing/ci-tiers.md:87 #, fuzzy msgid "" "**deps** (a lockfile, manifest, `deny.toml`, `package.json`, `.claude/`, " @@ -66669,7 +66455,7 @@ msgstr "" "**Depens** (ein Lockfile, Manifest, `deny.toml`, `package.json`, `.claude/`, " "`skills/`, ...) → zusätzlich der wiederverwendbare Workflow `security-audit`." -#: src/testing/ci-tiers.md:83 +#: src/testing/ci-tiers.md:90 msgid "" "Within `cargo-test`, runtime changes also select `perry-stdlib` unit tests: " "those tests exercise runtime GC, arenas, closures, promises and the event " @@ -66678,11 +66464,11 @@ msgid "" "regression tests in `lint`." msgstr "" -#: src/testing/ci-tiers.md:89 +#: src/testing/ci-tiers.md:96 msgid "Rust test lanes and timeboxes" msgstr "" -#: src/testing/ci-tiers.md:91 +#: src/testing/ci-tiers.md:98 msgid "" "Perry's Cargo suites use the same fast / mid / slow idea as socket-" "wheelhouse, but their wall times are measured in minutes because Rust " @@ -66691,84 +66477,84 @@ msgid "" "should not be copied onto Cargo." msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Lane" msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 #, fuzzy msgid "What runs" msgstr "Was zu laufen ist" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Expected time" msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Enforced timebox" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "**Fast**" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "" "PR-scoped crate unit tests (`--lib --bins`), plus the always-on FFI ABI unit " "check" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "`<10 min` for the scoped Cargo loop" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "" "`30 min` Cargo job cap; the PR gate targets `≤30 min` wall clock once queued." msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 #, fuzzy msgid "**Mid**" msgstr "**Android**" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "" "Full workspace package tests on main sweeps, including Perry bin/unit tests; " "Perry integration tests are left to the slow lane" msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "" "`<60 min` target; the prior full Cargo job measured `50–60 min` warm and `90–" "103 min` cold before removing Perry's serial integration inventory" msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "`120 min` Cargo job cap." msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "**Slow**" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "" "Complete Perry integration inventory on nightly, release tags, and opt-in " "full runs; changed suites run through `e2e-scoped` on PRs" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "Eight round-robin shards" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "" "`120 min` per shard. The separate scoped e2e job applies `25 min` subprocess " "bounds where configured." msgstr "" -#: src/testing/ci-tiers.md:102 +#: src/testing/ci-tiers.md:109 msgid "" "The hard caps are failure cutoffs, not performance goals. Although fast and " "mid share the `cargo-test` job, its timeout comes from the planner tier: 30 " @@ -66790,7 +66576,7 @@ msgid "" "crates while sharing unchanged compilations." msgstr "" -#: src/testing/ci-tiers.md:121 +#: src/testing/ci-tiers.md:128 msgid "" "`e2e-scoped` runs integration suites selected by the diff, but its " "`SUITE_EXCLUSIONS` check is deliberately not scoped: whenever that list is " @@ -66805,7 +66591,7 @@ msgstr "" "HIR, transformieren, oder eine andere Abhängigkeit, ohne Rust-Setup zu docs-" "only PRs hinzuzufügen." -#: src/testing/ci-tiers.md:127 +#: src/testing/ci-tiers.md:134 msgid "" "An empty or failed file listing is treated as **core** and a failed `plan` " "job fails the gate outright — a broken planner must never turn into " @@ -66815,11 +66601,11 @@ msgstr "" "fehlgeschlagener `plan`-Job versagt das Tor direkt – ein kaputter Planer " "darf sich nie in \"alles übersprungen, also grün\" verwandeln." -#: src/testing/ci-tiers.md:131 +#: src/testing/ci-tiers.md:138 msgid "Why it looks like this (measured 2026-08-16)" msgstr "Warum es so aussieht (gemessen 2026-08-16)" -#: src/testing/ci-tiers.md:133 +#: src/testing/ci-tiers.md:140 msgid "" "The organisation runs on GitHub's Free plan: **20 concurrent hosted jobs, 5 " "of them macOS**, for the whole org. Before this shape, every PR push fanned " @@ -66832,13 +66618,13 @@ msgstr "" "sah ~66 PR drückt und ~58 verschmilzt pro Tag. Das ist 1.5–2× " "Gesamtkapazität, also:" -#: src/testing/ci-tiers.md:138 +#: src/testing/ci-tiers.md:145 msgid "job queue waits were 3–7 h (`conformance-smoke` shards: median 4 h);" msgstr "" "Die Wartezeiten für die Job-Warteschlange betrugen 3–7 h (`conformance-" "smoke`-Scherben: mediane 4 h);" -#: src/testing/ci-tiers.md:139 +#: src/testing/ci-tiers.md:146 msgid "" "**0 of 66** PR runs of the `Tests` workflow reached a conclusion in the " "sample window — 8 failed, 56 were cancelled by the next push;" @@ -66847,7 +66633,7 @@ msgstr "" "im Beispielfenster — 8 fehlgeschlagen, 56 wurden durch den nächsten Push " "abgesagt;" -#: src/testing/ci-tiers.md:141 +#: src/testing/ci-tiers.md:148 msgid "" "the required contexts included two jobs (`parity`, `compile-smoke`) that " "never ran on a PR and one (`conformance-smoke-complete`) that had been red " @@ -66860,7 +66646,7 @@ msgstr "" "Zusammenführungen war ein Admin Bypass** mit `lint` und `cargo-test` stehen " "noch an." -#: src/testing/ci-tiers.md:146 +#: src/testing/ci-tiers.md:153 msgid "" "The always-red required-gate incident was tracked in [\\#8092](https://" "github.com/PerryTS/perry/issues/8092). #8087 restored `lint`, \\#8095 made " @@ -66875,11 +66661,11 @@ msgstr "" "ersetzt die fragmentierte erforderliche-Kontext-Liste durch die einzige `pr-" "gate`-Fan-In oben beschrieben." -#: src/testing/ci-tiers.md:152 +#: src/testing/ci-tiers.md:159 msgid "Two specific costs dominated:" msgstr "Zwei spezifische Kosten dominierten:" -#: src/testing/ci-tiers.md:154 +#: src/testing/ci-tiers.md:161 #, fuzzy msgid "" "`conformance-smoke` was 8 shards × ~60 min = **480 job-minutes per push**, " @@ -66901,7 +66687,7 @@ msgstr "" "Optimierungsmodus wird in der ganzen Ebene gehalten, weil er der einzige Arm " "ist, der nur auto-Optimierungs-Verbindungsfehler sieht." -#: src/testing/ci-tiers.md:161 +#: src/testing/ci-tiers.md:168 msgid "" "Every job saved a fresh ~0.5–1.3 GB sccache tarball on every PR push (~200 " "GB/day into a 10 GB repo cache budget), evicting every useful entry within " @@ -66917,7 +66703,7 @@ msgstr "" "`cache-warm.yml` ist weg: der Sweep ist der Cache-produzierende Build auf " "`main`." -#: src/testing/ci-tiers.md:167 +#: src/testing/ci-tiers.md:174 msgid "" "A later release run exposed a separate serial bottleneck: its 246 `perry` " "integration-test binaries had not reached their midpoint when `cargo-test` " @@ -66929,7 +66715,7 @@ msgid "" "result is a direct dependency of `full-suite-gate`." msgstr "" -#: src/testing/ci-tiers.md:175 +#: src/testing/ci-tiers.md:182 msgid "" "The **satellite gates** (`gc-ratchet`, `gc-root-dominance`, `gc-native-" "roots`, `gc-moving-witnesses`, `gc-parse-churn-gate`, `gc-ptr-shape-off-" @@ -66952,75 +66738,75 @@ msgstr "" "bekommt immer noch eine Runde von jedem – mit jedem Job übersprungen, was " "keinen Läufer-Slot kostet." -#: src/testing/ci-tiers.md:185 +#: src/testing/ci-tiers.md:192 msgid "Budgets" msgstr "Budgets" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "typical jobs" msgstr "typische Arbeitsplätze" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "typical runner-minutes" msgstr "typische Runner-Minuten" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "target wall clock" msgstr "Zielwanduhr" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "pr (core)" msgstr "pr (Kern)" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "~13 (6 gap shards)" msgstr "~13 (6 Spaltscheren)" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "~200" msgstr "~200" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "≤ 30 min once queued" msgstr "≤ 30 min einmal in der Warteschlange" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "pr (docs-only)" msgstr "pr (nur für Ärzte)" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "~5" msgstr "~5" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "≤ 5 min" msgstr "≤ 5 min" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "~19" msgstr "~19" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "~300" msgstr "~300" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "not a target — it coalesces" msgstr "kein Ziel — es verschmolzen" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "~30" msgstr "~30" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "~1500" msgstr "~1500" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "not a target" msgstr "kein Ziel" -#: src/testing/ci-tiers.md:194 +#: src/testing/ci-tiers.md:201 msgid "" "`push` to `main` uses ONE constant concurrency group with `cancel-in-" "progress: false`: GitHub keeps at most one running + one pending run per " @@ -67050,11 +66836,11 @@ msgstr "" "eines Sweep-Ausfalles erfolgt durch Fenster (`previous sweep SHA .. this " "sweep SHA`), genau wie bei den sechsstündigen Toren." -#: src/testing/ci-tiers.md:205 +#: src/testing/ci-tiers.md:212 msgid "Opting a PR into more" msgstr "Eine PR in mehr entscheiden" -#: src/testing/ci-tiers.md:207 +#: src/testing/ci-tiers.md:214 msgid "" "**`run-cc-parity` label** — runs the [Claude Code bundle parity gate](cc-" "parity.md) on changes to crates, build inputs, or the gate. This " @@ -67063,7 +66849,7 @@ msgid "" "goldens on one macOS runner." msgstr "" -#: src/testing/ci-tiers.md:211 +#: src/testing/ci-tiers.md:218 msgid "" "**`run-extended-tests` label** — promotes the PR's `test.yml` run to the " "`full` tier AND enables the PR arm of every satellite gate. Use it for GC / " @@ -67077,7 +66863,7 @@ msgstr "" "werden sollten, und für alles, was eine voll-tier-only Suite berührt. Das " "Anwenden des Etiketts feuert die Runs erneut (`labeled` Trigger)." -#: src/testing/ci-tiers.md:215 +#: src/testing/ci-tiers.md:222 msgid "" "**`skip-changelog` label** — skips the changeset step in `lint` (a `crates/` " "change must otherwise add a `changelog.d/-.md` fragment)." @@ -67086,7 +66872,7 @@ msgstr "" "`lint` (eine `crates/`-Änderung muss ansonsten ein `changelog.d/-." "md`-Fragment hinzufügen)." -#: src/testing/ci-tiers.md:217 +#: src/testing/ci-tiers.md:224 msgid "" "**`workflow_dispatch`** on any branch: `tier` (`pr` / `sweep` / `full`) and " "`update_gap_snapshot`." @@ -67094,11 +66880,11 @@ msgstr "" "**`workflow_dispatch`** auf jedem Zweig: `tier` (`pr` / `sweep` / `full`) " "und `update_gap_snapshot`." -#: src/testing/ci-tiers.md:220 +#: src/testing/ci-tiers.md:227 msgid "Re-baselining the gap snapshot" msgstr "Den Lücken-Snapshot neu basieren" -#: src/testing/ci-tiers.md:222 +#: src/testing/ci-tiers.md:229 msgid "" "`test-parity/gap_snapshot.json` is a both-direction snapshot: any divergence " "— a test that started failing OR started passing — is red. When `main` has " @@ -67111,17 +66897,17 @@ msgstr "" "Orakeländerung), regenerieren Sie es **von CI**, nicht von einem Laptop (der " "erforderliche Ausgangswert ist Linux, `fast`-Modus):" -#: src/testing/ci-tiers.md:228 +#: src/testing/ci-tiers.md:235 #, fuzzy msgid "# wait for the run, then:\n" msgstr "# warten Sie auf den Lauf, dann:" -#: src/testing/ci-tiers.md:231 +#: src/testing/ci-tiers.md:238 #, fuzzy msgid "# fill in `issue` / `reason` for every new entry, then commit\n" msgstr "# `issue` / `reason` für jeden neuen Eintrag ausfüllen, dann übergeben" -#: src/testing/ci-tiers.md:235 +#: src/testing/ci-tiers.md:242 msgid "" "New entries land as `category: untriaged`; give each an issue and a reason " "before merging. A crash may never be parked in the snapshot (`run_gap_tests." @@ -67131,11 +66917,11 @@ msgstr "" "Problem und einen Grund vor dem Zusammenführen. Ein Absturz darf niemals im " "Snapshot geparkt werden (`run_gap_tests.sh` verweigert)." -#: src/testing/ci-tiers.md:238 +#: src/testing/ci-tiers.md:245 msgid "Branch protection" msgstr "Zweigschutz" -#: src/testing/ci-tiers.md:240 +#: src/testing/ci-tiers.md:247 msgid "" "Required status checks on `main`: **`pr-gate`** only. Adding, removing or " "renaming a job in `test.yml` never needs a branch-protection edit again; the " @@ -67152,11 +66938,11 @@ msgstr "" "der eigene Lauf des Gates trägt, ob das Ergebnis passiert oder " "fehlgeschlagen ist." -#: src/testing/ci-tiers.md:246 +#: src/testing/ci-tiers.md:253 msgid "Release" msgstr "Release" -#: src/testing/ci-tiers.md:248 +#: src/testing/ci-tiers.md:255 msgid "" "`release-packages.yml`'s `await-tests` dispatches `test.yml` with " "`tier=full` on the pinned release branch and polls the SHA for a run whose " @@ -67168,7 +66954,7 @@ msgstr "" "suite-gate`** job succeeded — ein grüner Sweep- oder PR-Tierlauf auf " "demselben SHA zählt nicht. Siehe [Releasing](../contributing/releasing.md)." -#: src/testing/ci-tiers.md:253 +#: src/testing/ci-tiers.md:260 msgid "" "`parity` runs as 8 shards (`run_parity_tests.sh --shard N/8`) after the " "unsharded job was killed by GitHub's 6-hour job cap on 2026-08-16. " @@ -67185,11 +66971,22 @@ msgstr "" "parity_report_merge.py`, die einen fehlenden Shard ablehnt, anstatt die " "Suite zu schrumpfen)." -#: src/testing/ci-tiers.md:261 +#: src/testing/ci-tiers.md:267 +msgid "" +"`test262` runs every Test262 case (`scripts/test262_subset.py --full-" +"corpus`) as 16 shards at the SHA in `test-compat/test262/pinned-sha.txt`. " +"`test262-aggregate` merges them with `scripts/test262_ratchet.py`, prints " +"the pass rate over the whole corpus in the run summary, and fails on a new " +"failure, on a known failure that now passes (remove it from `test-compat/" +"test262/baseline.json`), or on a pass count below the baseline. Re-baseline " +"with `scripts/test262_ratchet.py --update `." +msgstr "" + +#: src/testing/ci-tiers.md:275 msgid "Workflow suite selection" msgstr "" -#: src/testing/ci-tiers.md:263 +#: src/testing/ci-tiers.md:277 msgid "" "The **CI** Actions workflow defaults to `suite=core`, preserving the tiered " "`pr-gate`, `main-gate`, and `full-suite-gate`. Select `suite=coverage`, " @@ -67198,76 +66995,76 @@ msgid "" "`tier` selector continues to control core CI." msgstr "" -#: src/testing/ci-tiers.md:265 +#: src/testing/ci-tiers.md:279 msgid "" "The Actions sidebar has six repository workflows. GitHub also registers four " "independent entries for Dependabot Updates, Dependency Graph, Copilot, and " "Pages, making ten entries once retired workflow history is cleaned up." msgstr "" -#: src/testing/ci-tiers.md:267 +#: src/testing/ci-tiers.md:281 #, fuzzy msgid "Categories and suite selection" msgstr "Kategorien und Migrationstore" -#: src/testing/ci-tiers.md:269 +#: src/testing/ci-tiers.md:283 msgid "**CI**" msgstr "" -#: src/testing/ci-tiers.md:269 +#: src/testing/ci-tiers.md:283 msgid "" "Required core CI and auxiliary security/coverage checks; defaults to " "`suite=core`" msgstr "" -#: src/testing/ci-tiers.md:270 +#: src/testing/ci-tiers.md:284 msgid "**Extended Tests** (`compiler-runtime.yml`)" msgstr "" -#: src/testing/ci-tiers.md:270 +#: src/testing/ci-tiers.md:284 msgid "" "Compiler/runtime, compatibility, integration, and performance; choose a " "category such as `suite=compatibility`, one suite such as `suite=simctl-" "tests`, or `suite=all`" msgstr "" -#: src/testing/ci-tiers.md:271 +#: src/testing/ci-tiers.md:285 msgid "**GC**" msgstr "" -#: src/testing/ci-tiers.md:271 +#: src/testing/ci-tiers.md:285 msgid "Six collector suites; choose an individual suite or `suite=all`" msgstr "" -#: src/testing/ci-tiers.md:272 +#: src/testing/ci-tiers.md:286 msgid "**Repository** (`maintenance.yml`)" msgstr "" -#: src/testing/ci-tiers.md:272 +#: src/testing/ci-tiers.md:286 msgid "" "Documentation and repository maintenance; defaults to offline " "`suite=validate`; select `suite=docs-check` for docs checks or `suite=docs` " "for deployment" msgstr "" -#: src/testing/ci-tiers.md:273 +#: src/testing/ci-tiers.md:287 msgid "**Publish to npm / GitHub Release**" msgstr "" -#: src/testing/ci-tiers.md:273 +#: src/testing/ci-tiers.md:287 msgid "Compiler packages, platform archives, and GitHub releases" msgstr "" -#: src/testing/ci-tiers.md:274 +#: src/testing/ci-tiers.md:288 #, fuzzy msgid "**Publish to npm / @perryts/hono-server**" msgstr "`@perryts/hono-server`" -#: src/testing/ci-tiers.md:274 +#: src/testing/ci-tiers.md:288 msgid "Independent adapter package publication" msgstr "" -#: src/testing/ci-tiers.md:276 +#: src/testing/ci-tiers.md:290 msgid "" "All 38 original suites remain represented. Schedules select their original " "suites even when several categories share a parent. Category and `all` " @@ -67278,7 +67075,7 @@ msgid "" "filenames for npm trusted publisher authorization." msgstr "" -#: src/testing/ci-tiers.md:278 +#: src/testing/ci-tiers.md:292 msgid "" "Job display names use lowercase. Nested suite jobs use the stable suite ID " "as their prefix, for example `docs-check / build-and-validation` and `docs-" @@ -67288,7 +67085,7 @@ msgid "" "check_actions_topology.py` enforces lowercase labels and suite prefixes." msgstr "" -#: src/testing/ci-tiers.md:285 +#: src/testing/ci-tiers.md:299 msgid "" "For example, `gh workflow run compiler-runtime.yml -f suite=performance` " "runs the performance category; `gh workflow run compiler-runtime.yml -f " @@ -67297,7 +67094,7 @@ msgid "" "actual job result, independently of other Extended Tests jobs." msgstr "" -#: src/testing/ci-tiers.md:287 +#: src/testing/ci-tiers.md:301 msgid "" "After a consolidation lands, Repository automatically deletes completed runs " "whose repository workflow file no longer exists. It allows queued/running " @@ -67306,11 +67103,11 @@ msgid "" "last runs are cleaned up." msgstr "" -#: src/testing/ci-tiers.md:289 +#: src/testing/ci-tiers.md:303 msgid "Required WASI command and component execution" msgstr "" -#: src/testing/ci-tiers.md:291 +#: src/testing/ci-tiers.md:305 msgid "" "`wasi-execution` joins the core PR, sweep and full tiers. On Linux it builds " "Perry with `target-wasi` and a matching wasm32-wasip2 runtime, checks the IR " @@ -67705,8 +67502,8 @@ msgid "" msgstr "" "**Es ist nicht der Concurrency-Fehler, und es ist nicht macOS Läufer " "Verfügbarkeit.** Beides war die offensichtliche Lektüre, und beide liegen " -"falsch. Das Richtige zu bekommen zählt, weil jede falsche Lektüre ein " -"\"Fix\" hat, das die Dinge noch schlimmer machen würde." +"falsch. Das Richtige zu bekommen zählt, weil jede falsche Lektüre ein \"Fix" +"\" hat, das die Dinge noch schlimmer machen würde." #: src/testing/ci-gate-scheduling.md:58 msgid "" @@ -67746,8 +67543,8 @@ msgid "" "The `github.sha` arm is guarded on `github.event_name == 'push'`. #7856 — " "the change this very document describes — moved the main-line arm from " "`push: branches: [main]` to `schedule:`. The guard stopped matching, the " -"expression fell through to `github.ref` (constant `refs/heads/main`), and " -"**\\#7205 came straight back on the arm \\#7856 created.** Measured " +"expression fell through to `github.ref` (constant `refs/heads/main`), and **" +"\\#7205 came straight back on the arm \\#7856 created.** Measured " "2026-08-12, identically on all ten gates: oldest run `queued` holding the " "group, the next two `cancelled` with `jobs: 0`, newest `pending`. `gate-" "freshness` itself was cancelled the same way." @@ -67778,9 +67575,9 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:85 msgid "" -"**The lesson is about the sentence, not the YAML.** \"Do not fix this " -"again\" is a claim about the future, and a scheduling change three days " -"later invalidated it. A repaired invariant should be written down as an " +"**The lesson is about the sentence, not the YAML.** \"Do not fix this again" +"\" is a claim about the future, and a scheduling change three days later " +"invalidated it. A repaired invariant should be written down as an " "_executable check_, not as an instruction to the next human to stop looking." msgstr "" "**Die Lektion ist über den Satz, nicht über die YAML.** \"Fixieren Sie dies " @@ -68376,8 +68173,8 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:275 msgid "" -"Why \"the gate was dark\" is not the same as \"the gate would have caught " -"it\"" +"Why \"the gate was dark\" is not the same as \"the gate would have caught it" +"\"" msgstr "" "Warum \"das Tor dunkel war\" ist nicht dasselbe wie \"das Tor hätte es " "gefangen\"" @@ -68409,8 +68206,8 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:285 msgid "" -"The two dimensions that did move — wall time and RSS — are explicitly " -"`\"gating\": false` in the `shared_ci` profile CI runs, with the rationale " +"The two dimensions that did move — wall time and RSS — are explicitly `" +"\"gating\": false` in the `shared_ci` profile CI runs, with the rationale " "recorded in `tolerances.json`. They gate only under `pinned_host`, which CI " "never uses." msgstr "" @@ -68773,14 +68570,14 @@ msgstr "# Oder Shorthand (auto-detects compilieren):" #: src/cli/commands.md:48 src/cli/commands.md:142 src/cli/commands.md:183 #: src/cli/commands.md:216 src/cli/commands.md:230 src/cli/commands.md:283 -#: src/cli/commands.md:295 src/cli/flags.md:80 src/cli/flags.md:143 -#: src/cli/flags.md:191 src/cli/flags.md:291 src/cli/flags.md:379 -#: src/cli/flags.md:412 src/cli/flags.md:419 +#: src/cli/commands.md:295 src/cli/flags.md:102 src/cli/flags.md:165 +#: src/cli/flags.md:213 src/cli/flags.md:313 src/cli/flags.md:401 +#: src/cli/flags.md:434 src/cli/flags.md:441 msgid "Flag" msgstr "Flag" #: src/cli/commands.md:50 src/cli/commands.md:144 src/cli/commands.md:278 -#: src/cli/flags.md:145 +#: src/cli/flags.md:167 msgid "`-o, --output `" msgstr "`-o, --output `" @@ -68804,7 +68601,7 @@ msgstr "`--output-type `" msgid "`executable` (default) or `dylib` (plugin)" msgstr "`executable` (Standard) oder `dylib` (Plugin)" -#: src/cli/commands.md:53 src/cli/flags.md:293 +#: src/cli/commands.md:53 src/cli/flags.md:315 msgid "`--print-hir`" msgstr "`--print-hir`" @@ -68812,7 +68609,7 @@ msgstr "`--print-hir`" msgid "Print HIR intermediate representation" msgstr "HIR-Zwischendarstellung ausgeben" -#: src/cli/commands.md:54 src/cli/flags.md:296 +#: src/cli/commands.md:54 src/cli/flags.md:318 msgid "`--no-link`" msgstr "`--no-link`" @@ -68824,7 +68621,7 @@ msgstr "" "Nur Objekt file(s) herstellen, Verknüpfung überspringen; in `-o` geschrieben " "(siehe [Compiler-Flaggen](flags.md))" -#: src/cli/commands.md:55 src/cli/flags.md:298 +#: src/cli/commands.md:55 src/cli/flags.md:320 msgid "`--keep-intermediates`" msgstr "`--keep-intermediates`" @@ -68832,7 +68629,7 @@ msgstr "`--keep-intermediates`" msgid "Keep `.o` and `.asm` files" msgstr "`.o`- und `.asm`-Dateien behalten" -#: src/cli/commands.md:56 src/cli/commands.md:103 src/cli/flags.md:421 +#: src/cli/commands.md:56 src/cli/commands.md:103 src/cli/flags.md:443 msgid "`--enable-js-runtime`" msgstr "`--enable-js-runtime`" @@ -68840,7 +68637,7 @@ msgstr "`--enable-js-runtime`" msgid "Enable V8 JavaScript runtime fallback" msgstr "V8-JavaScript-Runtime-Fallback aktivieren" -#: src/cli/commands.md:57 src/cli/commands.md:104 src/cli/flags.md:422 +#: src/cli/commands.md:57 src/cli/commands.md:104 src/cli/flags.md:444 msgid "`--enable-wasm-runtime`" msgstr "`--enable-wasm-runtime`" @@ -68852,8 +68649,8 @@ msgstr "" "Erzwingt die Einbindung der wasmi WebAssembly-Host-Runtime (wird bei " "Verwendung von `WebAssembly.*` automatisch erkannt)" -#: src/cli/commands.md:58 src/cli/commands.md:105 src/cli/flags.md:150 -#: src/cli/flags.md:423 +#: src/cli/commands.md:58 src/cli/commands.md:105 src/cli/flags.md:172 +#: src/cli/flags.md:445 msgid "`--type-check`" msgstr "`--type-check`" @@ -68861,17 +68658,17 @@ msgstr "`--type-check`" msgid "Enable type checking via tsgo" msgstr "Typprüfung über tsgo aktivieren" -#: src/cli/commands.md:59 src/cli/flags.md:381 +#: src/cli/commands.md:59 src/cli/flags.md:403 msgid "`--minify`" msgstr "`--minify`" -#: src/cli/commands.md:59 src/cli/flags.md:381 +#: src/cli/commands.md:59 src/cli/flags.md:403 msgid "Minify and obfuscate output (auto-enabled for `--target web`)" msgstr "" "Ausgabe minifizieren und obfuskieren (automatisch aktiviert für `--target " "web`)" -#: src/cli/commands.md:60 src/cli/flags.md:148 +#: src/cli/commands.md:60 src/cli/flags.md:170 msgid "`--app-bundle-id `" msgstr "`--app-bundle-id `" @@ -68879,7 +68676,7 @@ msgstr "`--app-bundle-id `" msgid "Bundle ID (required for widget targets)" msgstr "Bundle-ID (erforderlich für Widget-Targets)" -#: src/cli/commands.md:61 src/cli/flags.md:149 +#: src/cli/commands.md:61 src/cli/flags.md:171 msgid "`--bundle-extensions

`" msgstr "`--bundle-extensions `" @@ -68945,7 +68742,7 @@ msgstr "# Args an Ihr Programm weiterleiten" msgid "Argument / Flag" msgstr "Argument / Flag" -#: src/cli/commands.md:94 src/cli/commands.md:271 src/cli/flags.md:100 +#: src/cli/commands.md:94 src/cli/commands.md:271 src/cli/flags.md:122 msgid "`ios`" msgstr "`ios`" @@ -68953,7 +68750,7 @@ msgstr "`ios`" msgid "Target iOS (device or simulator)" msgstr "iOS als Target (Gerät oder Simulator)" -#: src/cli/commands.md:95 src/cli/commands.md:272 src/cli/flags.md:102 +#: src/cli/commands.md:95 src/cli/commands.md:272 src/cli/flags.md:124 msgid "`visionos`" msgstr "`visionos`" @@ -68961,7 +68758,7 @@ msgstr "`visionos`" msgid "Target visionOS (device or simulator)" msgstr "visionOS als Target (Gerät oder Simulator)" -#: src/cli/commands.md:96 src/cli/commands.md:270 src/cli/flags.md:103 +#: src/cli/commands.md:96 src/cli/commands.md:270 src/cli/flags.md:125 msgid "`macos`" msgstr "`macos`" @@ -68969,7 +68766,7 @@ msgstr "`macos`" msgid "Target macOS (default on macOS host)" msgstr "macOS als Target (Standard auf macOS-Host)" -#: src/cli/commands.md:97 src/cli/flags.md:120 +#: src/cli/commands.md:97 src/cli/flags.md:142 msgid "`web`" msgstr "`web`" @@ -69165,8 +68962,8 @@ msgstr "" #: src/cli/commands.md:152 msgid "" "Watches the project root (plus any `--watch` dirs) recursively using the " -"`notify` crate. A 300 ms **debounce** window collapses editor \"save " -"storms\" into one rebuild." +"`notify` crate. A 300 ms **debounce** window collapses editor \"save storms" +"\" into one rebuild." msgstr "" "Beobachtet das Projekt-Root (plus alle `--watch`-Verzeichnisse) rekursiv mit " "der `notify`-Crate. Ein 300-ms-**Debounce**-Fenster kollabiert Editor-" @@ -69260,7 +69057,7 @@ msgstr "" "State-Erhaltung über Rebuilds / HMR — \"schneller Restart\" ist das ehrliche " "Ziel." -#: src/cli/commands.md:175 src/internals/garbage-collector.md:448 +#: src/cli/commands.md:175 src/internals/garbage-collector.md:444 msgid "check" msgstr "check" @@ -69679,9 +69476,9 @@ msgstr "" "(~1,5 GB) herunter, damit Perry ohne Visual Studio Build Tools linken kann. " "Erfordert LLVM (`winget install LLVM.LLVM`) und fragt nach der Akzeptanz der " "Microsoft-Redistributable-Lizenz — übergeben Sie `--accept-license`, um den " -"Prompt für CI zu überspringen. Die Ausgabe landet in `%LOCALAPPDATA%" -"\\perry\\windows-sdk`. Siehe den [Windows-Plattform-Leitfaden](../platforms/" -"windows.md) für den vollständigen Toolchain-Vergleich." +"Prompt für CI zu überspringen. Die Ausgabe landet in `%LOCALAPPDATA%\\perry" +"\\windows-sdk`. Siehe den [Windows-Plattform-Leitfaden](../platforms/windows." +"md) für den vollständigen Toolchain-Vergleich." #: src/cli/commands.md:320 msgid "Credential wizards store their output in `~/.perry/config.toml`." @@ -69978,10 +69775,41 @@ msgstr "" "Bau zu überprüfen." #: src/cli/flags.md:7 +#, fuzzy +msgid "Build-location independent paths" +msgstr "Mehrere unabhängige Berechnungen" + +#: src/cli/flags.md:9 +msgid "" +"`--source-root ` (or `PERRY_SOURCE_ROOT=`) bakes module source " +"paths relative to `` instead of the directory the program was built in. " +"A module at `/a/b.js` sees `/a/b.js` in `__dirname`, `__filename` and " +"`import.meta.{url,dirname,filename,resolve}`, as the base of `require` / " +"`createRequire`, in `require.cache` keys, `require.resolve` results, stack-" +"frame labels and the text of runtime error messages. `--source-root-target " +"` (or `PERRY_SOURCE_ROOT_TARGET`) replaces the default `/` with " +"another absolute path, so `--source-root /build/app --source-root-target /" +"srv/app` bakes `/srv/app/a/b.js`." +msgstr "" + +#: src/cli/flags.md:20 +msgid "\"$PWD\"" +msgstr "" + +#: src/cli/flags.md:23 +msgid "" +"Compile-time file access, diagnostics and `--debug-symbols` are unchanged; " +"only values the program can observe are rebased. A module outside `` (a " +"linked package, say) keeps its real path and the compiler warns once per " +"such module. Without the option the output is byte-identical to before. Both " +"build and object caches include the effective root and target." +msgstr "" + +#: src/cli/flags.md:29 msgid "Build-time defines" msgstr "" -#: src/cli/flags.md:9 +#: src/cli/flags.md:31 msgid "" "`--define NAME=EXPR` is repeatable. It replaces unbound identifier reads and " "dotted names before lowering, including `typeof` guards, object shorthand, " @@ -69990,44 +69818,44 @@ msgid "" "quote a JavaScript string inside the shell argument:" msgstr "" -#: src/cli/flags.md:16 +#: src/cli/flags.md:38 msgid "'OPENCODE_VERSION=\"1.18.30\"'" msgstr "" -#: src/cli/flags.md:20 +#: src/cli/flags.md:42 msgid "The nearest `perry.json` above the entry can hold the same expressions:" msgstr "" -#: src/cli/flags.md:24 +#: src/cli/flags.md:46 #, fuzzy msgid "\"define\"" msgstr "`undefined`" -#: src/cli/flags.md:25 +#: src/cli/flags.md:47 msgid "\"OPENCODE_VERSION\"" msgstr "" -#: src/cli/flags.md:25 +#: src/cli/flags.md:47 msgid "\"\\\"1.18.30\\\"\"" msgstr "" -#: src/cli/flags.md:26 +#: src/cli/flags.md:48 msgid "\"OPENCODE_CHANNEL\"" msgstr "" -#: src/cli/flags.md:26 +#: src/cli/flags.md:48 msgid "\"\\\"latest\\\"\"" msgstr "" -#: src/cli/flags.md:27 +#: src/cli/flags.md:49 msgid "\"OPENCODE_MODELS_DEV\"" msgstr "" -#: src/cli/flags.md:27 +#: src/cli/flags.md:49 msgid "\"{\\\"provider\\\":{\\\"models\\\":{}}}\"" msgstr "" -#: src/cli/flags.md:32 +#: src/cli/flags.md:54 msgid "" "Strings in `perry.json` are JavaScript expressions, as in Bun's `define` " "map. Existing `package.json` `perry.define` strings keep their literal-" @@ -70036,7 +69864,7 @@ msgid "" "effective map." msgstr "" -#: src/cli/flags.md:37 +#: src/cli/flags.md:59 msgid "" "Large JSON-compatible literals (including defines) can stay serialized in " "the executable and use the built-in JSON parser when evaluated. Two " @@ -70044,19 +69872,19 @@ msgid "" "budgets:" msgstr "" -#: src/cli/flags.md:41 +#: src/cli/flags.md:63 msgid "" "Flat arrays of numbers, strings, booleans and null use this path at 1,024 " "value nodes or 64 KiB of string data." msgstr "" -#: src/cli/flags.md:43 +#: src/cli/flags.md:65 msgid "" "All other JSON-compatible object/array literals use it at 24,576 value nodes " "or 1 MiB of key/string data, to avoid excessive LLVM code generation." msgstr "" -#: src/cli/flags.md:46 +#: src/cli/flags.md:68 msgid "" "Nodes include the root and nested values; bytes count UTF-8 key/string " "content, excluding whitespace. Mid-size records keep ordinary lowering and " @@ -70066,7 +69894,7 @@ msgid "" "expression lowering." msgstr "" -#: src/cli/flags.md:52 +#: src/cli/flags.md:74 msgid "" "`import.meta.resolve(specifier[, parent])` returns a `file://` URL for " "filesystem targets and a `node:` specifier for supported builtins. Literal " @@ -70079,15 +69907,15 @@ msgid "" "their contents." msgstr "" -#: src/cli/flags.md:61 +#: src/cli/flags.md:83 msgid "For OpenCode v1.18.30, after installing its dependencies, run:" msgstr "" -#: src/cli/flags.md:64 +#: src/cli/flags.md:86 msgid "/path/to/models-snapshot.json" msgstr "" -#: src/cli/flags.md:69 +#: src/cli/flags.md:91 msgid "" "The harness merges release defines into `packages/opencode/perry.json`, " "reads the version from OpenCode's package, uses channel `latest`, imports " @@ -70098,47 +69926,47 @@ msgid "" "in issue #10107." msgstr "" -#: src/cli/flags.md:76 +#: src/cli/flags.md:98 msgid "Global Flags" msgstr "Globale Flags" -#: src/cli/flags.md:78 +#: src/cli/flags.md:100 msgid "Available on all commands:" msgstr "Auf allen Befehlen verfügbar:" -#: src/cli/flags.md:82 +#: src/cli/flags.md:104 msgid "`--format text\\|json`" msgstr "`--format text\\|json`" -#: src/cli/flags.md:82 +#: src/cli/flags.md:104 msgid "Output format (default: `text`)" msgstr "Ausgabeformat (Standard: `text`)" -#: src/cli/flags.md:83 +#: src/cli/flags.md:105 msgid "`-v, --verbose`" msgstr "`-v, --verbose`" -#: src/cli/flags.md:83 +#: src/cli/flags.md:105 msgid "Increase verbosity (repeatable: `-v`, `-vv`, `-vvv`)" msgstr "Ausführlichkeit erhöhen (wiederholbar: `-v`, `-vv`, `-vvv`)" -#: src/cli/flags.md:84 +#: src/cli/flags.md:106 msgid "`-q, --quiet`" msgstr "`-q, --quiet`" -#: src/cli/flags.md:84 +#: src/cli/flags.md:106 msgid "Suppress non-error output" msgstr "Nicht-Fehler-Ausgabe unterdrücken" -#: src/cli/flags.md:85 +#: src/cli/flags.md:107 msgid "`--no-color`" msgstr "`--no-color`" -#: src/cli/flags.md:85 +#: src/cli/flags.md:107 msgid "Disable ANSI color codes" msgstr "ANSI-Farbcodes deaktivieren" -#: src/cli/flags.md:87 +#: src/cli/flags.md:109 msgid "" "Successful compilation keeps Cargo, clang, Swift, and linker implementation " "details out of the default output. Pass `--verbose` to stream those " @@ -70150,235 +69978,235 @@ msgstr "" "verbose`, um diese Toolchain-Diagnostik zu streamen; wenn ein internes Tool " "fehlschlägt, zeigt Perry seine erfasste Ausgabe auch ohne `--verbose`." -#: src/cli/flags.md:92 +#: src/cli/flags.md:114 msgid "Compilation Targets" msgstr "Kompilier-Targets" -#: src/cli/flags.md:94 +#: src/cli/flags.md:116 msgid "Use `--target` to cross-compile:" msgstr "Verwenden Sie `--target` für die Cross-Kompilierung:" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "_(none)_" msgstr "_(keine)_" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "Current platform" msgstr "Aktuelle Plattform" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "Default behavior" msgstr "Standardverhalten" -#: src/cli/flags.md:99 +#: src/cli/flags.md:121 msgid "`ios-simulator`" msgstr "`ios-simulator`" -#: src/cli/flags.md:99 +#: src/cli/flags.md:121 msgid "ARM64 simulator binary" msgstr "ARM64-Simulator-Binary" -#: src/cli/flags.md:100 +#: src/cli/flags.md:122 msgid "ARM64 device binary" msgstr "ARM64-Gerät-Binary" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "`visionos-simulator`" msgstr "`visionos-simulator`" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "visionOS Simulator" msgstr "visionOS-Simulator" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "Apple Vision Pro simulator build" msgstr "Apple Vision Pro-Simulator-Build" -#: src/cli/flags.md:102 +#: src/cli/flags.md:124 msgid "visionOS Device" msgstr "visionOS-Gerät" -#: src/cli/flags.md:102 +#: src/cli/flags.md:124 msgid "Apple Vision Pro device build" msgstr "Apple Vision Pro-Gerät-Build" -#: src/cli/flags.md:103 +#: src/cli/flags.md:125 msgid "Native macOS app bundle/build" msgstr "Native macOS App bundle/build" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "`tvos`" msgstr "`tvos`" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "tvOS Device" msgstr "tvOS Gerät" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "ARM64 Apple TV app" msgstr "ARM64 App für Apple TV" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "`tvos-simulator`" msgstr "`tvos-simulator`" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "tvOS Simulator" msgstr "tvOS Simulator" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "Apple TV simulator app" msgstr "Apple TV Simulator App" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "`watchos`" msgstr "`watchos`" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "watchOS Device" msgstr "watchOS Gerät" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "Apple Watch app" msgstr "App für Apple Watch" -#: src/cli/flags.md:107 +#: src/cli/flags.md:129 msgid "`watchos-simulator`" msgstr "`watchos-simulator`" -#: src/cli/flags.md:107 +#: src/cli/flags.md:129 msgid "Apple Watch simulator app" msgstr "Apple Watch Simulator App" -#: src/cli/flags.md:108 +#: src/cli/flags.md:130 msgid "ARM64 device build" msgstr "ARM64-Gerät erstellen" -#: src/cli/flags.md:109 +#: src/cli/flags.md:131 msgid "`android-x86_64`" msgstr "`android-x86_64`" -#: src/cli/flags.md:109 +#: src/cli/flags.md:131 msgid "x86_64 emulator/device build" msgstr "x86_64 emulator/device bauen" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "`ios-widget`" msgstr "`ios-widget`" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "iOS Widget" msgstr "iOS-Widget" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "WidgetKit extension (requires `--app-bundle-id`)" msgstr "WidgetKit-Erweiterung (erfordert `--app-bundle-id`)" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "`ios-widget-simulator`" msgstr "`ios-widget-simulator`" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "iOS Widget (Sim)" msgstr "iOS-Widget (Sim)" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "Widget for simulator" msgstr "Widget für Simulator" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "`watchos-widget`" msgstr "`watchos-widget`" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "watchOS Complication" msgstr "watchOS-Komplikation" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "WidgetKit extension for Apple Watch" msgstr "WidgetKit-Erweiterung für Apple Watch" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "`watchos-widget-simulator`" msgstr "`watchos-widget-simulator`" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "watchOS Widget (Sim)" msgstr "watchOS-Widget (Sim)" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "Widget for watchOS simulator" msgstr "Widget für watchOS-Simulator" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "`android-widget`" msgstr "`android-widget`" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "Android Widget" msgstr "Android-Widget" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "Android App Widget (AppWidgetProvider)" msgstr "Android-App-Widget (AppWidgetProvider)" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "`wearos-tile`" msgstr "`wearos-tile`" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "Wear OS Tile" msgstr "Wear-OS-Tile" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "Wear OS Tile (TileService)" msgstr "Wear-OS-Tile (TileService)" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "`wearos`" msgstr "`wearos`" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "Wear OS App" msgstr "Wear OS App" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "ARM64 Wear OS application shared library" msgstr "ARM64 Wear OS-Anwendung gemeinsam genutzte Bibliothek" -#: src/cli/flags.md:117 +#: src/cli/flags.md:139 msgid "`harmonyos`" msgstr "`harmonyos`" -#: src/cli/flags.md:117 +#: src/cli/flags.md:139 msgid "ARM64 OHOS HAP/native library" msgstr "ARM64 OHOS HAP/native Bibliothek" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "`harmonyos-simulator`" msgstr "`harmonyos-simulator`" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "HarmonyOS Simulator" msgstr "HarmonyOS Simulator" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "x86_64 OHOS simulator output" msgstr "x86_64 OHOS-Simulatorausgabe" -#: src/cli/flags.md:119 +#: src/cli/flags.md:141 msgid "`wasm`" msgstr "`wasm`" -#: src/cli/flags.md:119 +#: src/cli/flags.md:141 msgid "Self-contained HTML with WASM or raw `.wasm` binary" msgstr "Eigenständiges HTML mit WASM oder rohes `.wasm`-Binary" -#: src/cli/flags.md:120 +#: src/cli/flags.md:142 msgid "Outputs HTML file with JS" msgstr "Erzeugt eine HTML-Datei mit JS" -#: src/cli/flags.md:121 +#: src/cli/flags.md:143 msgid "" "Win32/GDI executable for the native Windows host architecture; x64 when " "cross-compiling from another OS" @@ -70386,27 +70214,27 @@ msgstr "" "Win32/GDI ausführbar für die native Windows Host-Architektur; x64 beim Cross-" "Compiling von einem anderen Betriebssystem" -#: src/cli/flags.md:122 +#: src/cli/flags.md:144 msgid "Explicit x64 MSVC target" msgstr "Explizites x64 MSVC-Ziel" -#: src/cli/flags.md:123 +#: src/cli/flags.md:145 msgid "Windows ARM64" msgstr "Windows ARM64" -#: src/cli/flags.md:123 +#: src/cli/flags.md:145 msgid "Explicit ARM64 MSVC target (`windows-arm64` is accepted as an alias)" msgstr "Explizites MSVC-Ziel ARM64 (`windows-arm64` wird als Alias akzeptiert)" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "`windows-winui`" msgstr "`windows-winui`" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "Windows (Fluent)" msgstr "Windows (Fluent)" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "" "Opt-in WinUI 3 / Fluent backend for core Perry controls. Requires the " "Windows App SDK 2.0 runtime; see [Windows](../platforms/windows.md#winui-3-" @@ -70416,102 +70244,102 @@ msgstr "" "Windows App SDK 2.0 Laufzeit; siehe [Windows](../platforms/windows." "md#winui-3-fluent-target)." -#: src/cli/flags.md:125 +#: src/cli/flags.md:147 msgid "GTK4 executable" msgstr "GTK4-Executable" -#: src/cli/flags.md:126 +#: src/cli/flags.md:148 msgid "Explicit x86_64 GNU target" msgstr "Explizites x86_64 GNU-Ziel" -#: src/cli/flags.md:127 +#: src/cli/flags.md:149 msgid "`linux-aarch64` (`linux-arm64`)" msgstr "`linux-aarch64` (`linux-arm64`)" -#: src/cli/flags.md:127 +#: src/cli/flags.md:149 msgid "Explicit aarch64 GNU target" msgstr "Explizites Aarch64 GNU-Ziel" -#: src/cli/flags.md:128 +#: src/cli/flags.md:150 msgid "`linux-musl` (`linux-x86_64-musl`)" msgstr "`linux-musl` (`linux-x86_64-musl`)" -#: src/cli/flags.md:128 +#: src/cli/flags.md:150 msgid "Linux x64 (musl)" msgstr "Linux x64 (musl)" -#: src/cli/flags.md:128 src/cli/flags.md:129 +#: src/cli/flags.md:150 src/cli/flags.md:151 msgid "Fully static headless/server target" msgstr "Vollstatisches headless/server-Ziel" -#: src/cli/flags.md:129 +#: src/cli/flags.md:151 msgid "`linux-aarch64-musl`" msgstr "`linux-aarch64-musl`" -#: src/cli/flags.md:129 +#: src/cli/flags.md:151 msgid "Linux arm64 (musl)" msgstr "Linux Arm64 (Muss)" -#: src/cli/flags.md:131 +#: src/cli/flags.md:153 msgid "Output Types" msgstr "Ausgabetypen" -#: src/cli/flags.md:133 +#: src/cli/flags.md:155 msgid "Use `--output-type` to change what's produced:" msgstr "Verwenden Sie `--output-type`, um zu ändern, was erzeugt wird:" -#: src/cli/flags.md:137 +#: src/cli/flags.md:159 msgid "`executable`" msgstr "`executable`" -#: src/cli/flags.md:137 +#: src/cli/flags.md:159 msgid "Standalone binary (default)" msgstr "Eigenständiges Binary (Standard)" -#: src/cli/flags.md:138 +#: src/cli/flags.md:160 msgid "`dylib`" msgstr "`dylib`" -#: src/cli/flags.md:138 +#: src/cli/flags.md:160 msgid "Shared library (`.dylib`/`.so`) for [plugins](../plugins/overview.md)" msgstr "Shared Library (`.dylib`/`.so`) für [Plugins](../plugins/overview.md)" -#: src/cli/flags.md:139 +#: src/cli/flags.md:161 msgid "`staticlib`" msgstr "`staticlib`" -#: src/cli/flags.md:139 +#: src/cli/flags.md:161 msgid "" "Static archive (`.a`/`.lib`) exposing `perry_module_init` for a native host" msgstr "" "Statisches Archiv (`.a`- Nein, nein`.lib`) `perry_module_init` für einen " "einheimischen Gastgeber" -#: src/cli/flags.md:141 +#: src/cli/flags.md:163 msgid "General Build and Platform Flags" msgstr "Allgemeine Bau- und Plattform-Flaggen" -#: src/cli/flags.md:145 +#: src/cli/flags.md:167 msgid "Output executable, bundle, shared library, archive, or object path." msgstr "" "Ausgabe ausführbar, Bündel, freigegebene Bibliothek, Archiv oder Objektpfad." -#: src/cli/flags.md:146 +#: src/cli/flags.md:168 #, fuzzy msgid "`--platform node\\|bun`" msgstr "`platform-core`" -#: src/cli/flags.md:146 +#: src/cli/flags.md:168 msgid "" "Select the JavaScript host global surface. `node` is the default; `bun` " "installs a real, stable `Bun === globalThis.Bun` namespace." msgstr "" -#: src/cli/flags.md:147 +#: src/cli/flags.md:169 msgid "`--libc glibc\\|musl`" msgstr "`--libc glibc\\|musl`" -#: src/cli/flags.md:147 +#: src/cli/flags.md:169 msgid "" "Select Linux libc/linkage; `musl` upgrades the corresponding Linux target to " "a fully static headless build." @@ -70519,18 +70347,18 @@ msgstr "" "Wählen Sie Linux libc/linkage; `musl` aktualisiert das entsprechende Linux-" "Ziel auf einen vollständig statischen kopflosen Aufbau." -#: src/cli/flags.md:148 +#: src/cli/flags.md:170 msgid "Bundle identifier required by home-screen widget targets." msgstr "Bundle-Kennung durch Home-Screen-Widget-Ziele." -#: src/cli/flags.md:149 +#: src/cli/flags.md:171 msgid "" "Discover and statically bundle native extension packages from a directory." msgstr "" "Entdecke und bündele statisch native Erweiterungspakete aus einem " "Verzeichnis." -#: src/cli/flags.md:150 +#: src/cli/flags.md:172 msgid "" "Run the native TypeScript checker (`@typescript/native-preview`) before " "codegen." @@ -70538,22 +70366,22 @@ msgstr "" "Führen Sie den nativen TypeScript-Checker (`@typescript/native-preview`) vor " "dem Codegen aus." -#: src/cli/flags.md:151 +#: src/cli/flags.md:173 msgid "`--min-windows-version 7\\|8\\|10`" msgstr "`--min-windows-version 7\\|8\\|10`" -#: src/cli/flags.md:151 +#: src/cli/flags.md:173 msgid "" "Set the Windows PE compatibility floor; ignored for non-Windows targets." msgstr "" "Setzen Sie den Windows PE Kompatibilitätsboden; ignoriert für nicht-Windows-" "Ziele." -#: src/cli/flags.md:152 +#: src/cli/flags.md:174 msgid "`--windows-subsystem auto\\|console\\|windows`" msgstr "`--windows-subsystem auto\\|console\\|windows`" -#: src/cli/flags.md:152 +#: src/cli/flags.md:174 msgid "" "Select the Windows PE subsystem instead of relying on import-based auto " "detection." @@ -70561,11 +70389,11 @@ msgstr "" "Wählen Sie das Windows PE-Subsystem, anstatt sich auf die auto-Import-" "Erkennung zu verlassen." -#: src/cli/flags.md:153 +#: src/cli/flags.md:175 msgid "`--skip-swift-build`" msgstr "`--skip-swift-build`" -#: src/cli/flags.md:153 +#: src/cli/flags.md:175 msgid "" "For Apple widget targets, emit SwiftUI source and metadata without invoking " "`swiftc`." @@ -70573,11 +70401,11 @@ msgstr "" "Für Apple-Widget-Ziele senden Sie SwiftUI-Quelle und Metadaten aus, ohne " "`swiftc` aufzurufen." -#: src/cli/flags.md:154 +#: src/cli/flags.md:176 msgid "`--p12-keystore ` / `--p12-password `" msgstr "`--p12-keystore ` / `--p12-password `" -#: src/cli/flags.md:154 +#: src/cli/flags.md:176 msgid "" "Select HarmonyOS HAP signing material. Prefer the corresponding environment/" "config sources for secrets." @@ -70585,36 +70413,36 @@ msgstr "" "Wählen Sie HarmonyOS HAP Signiermaterial. Bevorzugen Sie die entsprechenden " "environment/config-Quellen für Geheimnisse." -#: src/cli/flags.md:155 +#: src/cli/flags.md:177 msgid "`--harmonyos-cert `" msgstr "`--harmonyos-cert `" -#: src/cli/flags.md:155 +#: src/cli/flags.md:177 msgid "HarmonyOS application certificate chain." msgstr "HarmonyOS-Anwendungszertifikatkette." -#: src/cli/flags.md:156 +#: src/cli/flags.md:178 msgid "`--harmonyos-profile `" msgstr "`--harmonyos-profile `" -#: src/cli/flags.md:156 +#: src/cli/flags.md:178 msgid "HarmonyOS signed provisioning profile." msgstr "HarmonyOS unterzeichnet Provisioning-Profil." -#: src/cli/flags.md:157 +#: src/cli/flags.md:179 msgid "`--harmonyos-key-alias `" msgstr "`--harmonyos-key-alias `" -#: src/cli/flags.md:157 +#: src/cli/flags.md:179 msgid "HarmonyOS keystore alias; defaults to `debugKey`." msgstr "HarmonyOS keystore alias; Standardwerte für `debugKey`." -#: src/cli/flags.md:159 +#: src/cli/flags.md:181 #, fuzzy msgid "Bun platform mode" msgstr "`platform` — Modul" -#: src/cli/flags.md:161 +#: src/cli/flags.md:183 msgid "" "`perry compile app.ts --platform bun` exposes Perry's implemented Bun APIs " "through one real global namespace. Direct calls, extracted or destructured " @@ -70624,7 +70452,7 @@ msgid "" "direct Bun shims and imports from `\"bun\"` continue to work." msgstr "" -#: src/cli/flags.md:168 +#: src/cli/flags.md:190 msgid "" "In this mode, `Bun.version` is Perry's runtime crate version, not a claimed " "Bun release number. `Bun.isStandaloneExecutable` is always `true`, because " @@ -70633,7 +70461,7 @@ msgid "" "compatibility error." msgstr "" -#: src/cli/flags.md:173 +#: src/cli/flags.md:195 msgid "" "`Bun.spawn` supports the command-array and `{ cmd, ...options }` forms, " "piped output consumers such as `child.stdout.text()`, lifecycle controls, " @@ -70642,7 +70470,7 @@ msgid "" "are not yet available on Windows." msgstr "" -#: src/cli/flags.md:179 +#: src/cli/flags.md:201 msgid "" "Web APIs otherwise keep Node's behavior; the one runtime difference in this " "mode is that a `Response` may carry a body under a null-body status (204, " @@ -70651,11 +70479,11 @@ msgid "" "Node's `TypeError: Response constructor: Invalid response status code 204`." msgstr "" -#: src/cli/flags.md:186 +#: src/cli/flags.md:208 msgid "Embedding Assets" msgstr "Einbettung von Vermögenswerten" -#: src/cli/flags.md:188 +#: src/cli/flags.md:210 msgid "" "Bake static files (an SPA `dist/`, images, JSON, fonts, …) into the " "standalone executable so it runs with no external files on disk (#5731)." @@ -70664,11 +70492,11 @@ msgstr "" "Schriftarten, ...) in die eigenständige ausführbare Datei, so dass es ohne " "externe Dateien auf der Festplatte läuft (#5731)." -#: src/cli/flags.md:193 +#: src/cli/flags.md:215 msgid "`--embed `" msgstr "`--embed `" -#: src/cli/flags.md:193 +#: src/cli/flags.md:215 msgid "" "Embed a file, directory, or `*`/`**` glob (relative to the project root). " "Repeatable. Merged with `perry.embed` (package.json) and `[compile] embed` " @@ -70678,23 +70506,23 @@ msgstr "" "(relativ zur Projektwurzel). Repeatable. Zusammengelegt mit `perry.embed` " "(package.json) und `[compile] embed` (perry.toml)." -#: src/cli/flags.md:194 +#: src/cli/flags.md:216 #, fuzzy msgid "`--bunfs-root `" msgstr "`unrooted`" -#: src/cli/flags.md:194 +#: src/cli/flags.md:216 msgid "" "Map an extracted Bun standalone root to `/$bunfs/root/`; mapped module paths " "resolve natively and literal file paths remain readable from the relocated " "executable." msgstr "" -#: src/cli/flags.md:195 +#: src/cli/flags.md:217 msgid "`--asset-module `" msgstr "`--asset-module `" -#: src/cli/flags.md:195 +#: src/cli/flags.md:217 msgid "" "Generate a virtual module whose default export maps every file below `dir` " "to a stable `$perryfs` handle. Repeatable; source maps are excluded." @@ -70703,24 +70531,24 @@ msgstr "" "unterhalb von `dir` auf einen stabilen `$perryfs`-Handle abbildet. " "Wiederholbar; Quellkarten sind ausgeschlossen." -#: src/cli/flags.md:199 +#: src/cli/flags.md:221 msgid "\"./dist/**\"" msgstr "\"./dist/**\"" -#: src/cli/flags.md:200 +#: src/cli/flags.md:222 #, fuzzy msgid "# serves dist/ from memory — no dist/ folder needed\n" msgstr "# dient dist/ aus dem Speicher — kein dist/Ordner benötigt" -#: src/cli/flags.md:203 +#: src/cli/flags.md:225 msgid "Embedded files are reachable at runtime three ways:" msgstr "Eingebettete Dateien sind zur Laufzeit auf drei Arten erreichbar:" -#: src/cli/flags.md:205 +#: src/cli/flags.md:227 msgid "" "```typescript,no-test\n" -"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from " -"\"perry\";\n" +"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from \"perry" +"\";\n" "import { readFileSync } from \"fs\";\n" "\n" "for (const f of embeddedFiles()) {\n" @@ -70734,8 +70562,8 @@ msgid "" "```" msgstr "" "```typescript,no-test\n" -"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from " -"\"perry\";\n" +"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from \"perry" +"\";\n" "import { readFileSync } from \"fs\";\n" "\n" "for (const f of embeddedFiles()) {\n" @@ -70748,7 +70576,7 @@ msgstr "" "const html = readFileSync(\"$perryfs/dist/index.html\", \"utf8\");\n" "```" -#: src/cli/flags.md:218 +#: src/cli/flags.md:240 msgid "" "`embeddedFiles()` is a function (not a bare value like Bun's " "`embeddedFiles`) so that array methods dispatch on its result. " @@ -70757,10 +70585,10 @@ msgid "" msgstr "" "`embeddedFiles()` ist eine Funktion (nicht ein blanker Wert wie Bun " "`embeddedFiles`), so dass Array-Methoden auf dem Ergebnis senden. " -"`readEmbedded(path)` und `node:fs` akzeptieren entweder den virtuellen Pfad " -"`$perryfs/` oder den embed-relative Schlüssel." +"`readEmbedded(path)` und `node:fs` akzeptieren entweder den virtuellen Pfad `" +"$perryfs/` oder den embed-relative Schlüssel." -#: src/cli/flags.md:222 +#: src/cli/flags.md:244 msgid "" "Bun-compatible file-loader imports embed the referenced binary automatically " "and bind the default import to its `$perryfs` path:" @@ -70768,7 +70596,7 @@ msgstr "" "Bun-compatible file-loader imports embed the referenced binary automatically " "and bind the default import to its `$perryfs` path:" -#: src/cli/flags.md:225 +#: src/cli/flags.md:247 msgid "" "```typescript,no-test\n" "import sound from \"./sound.mp3\" with { type: \"file\" };\n" @@ -70778,13 +70606,13 @@ msgstr "" "import sound from \"./sound.mp3\" with { type: \"file\" };\n" "```" -#: src/cli/flags.md:229 +#: src/cli/flags.md:251 msgid "" "For source extracted from a Bun standalone executable, mount its extracted " "`root/` directory at Bun's original virtual prefix:" msgstr "" -#: src/cli/flags.md:236 +#: src/cli/flags.md:258 msgid "" "Static imports, re-exports, literal dynamic imports, literal `require()`, " "and `import.meta.require()` calls below `/$bunfs/root/` resolve against that " @@ -70796,7 +70624,7 @@ msgid "" "symlink is needed." msgstr "" -#: src/cli/flags.md:245 +#: src/cli/flags.md:267 msgid "" "Bun bundles can load compiled chunks synchronously with `import.meta." "require(\"./chunk.js\")` or `import.meta[\"require\"](\"./chunk.js\")`. Both " @@ -70806,7 +70634,7 @@ msgid "" "call." msgstr "" -#: src/cli/flags.md:251 +#: src/cli/flags.md:273 msgid "" "Some build pipelines inject a generated module rather than writing it into " "the source checkout. Reproduce that file-map step with `--asset-module`. " @@ -70820,7 +70648,7 @@ msgstr "" "\"file\" }`-Kante auf und behält die generierte Quelle in seinem Cache, " "anstatt die Kasse zu modifizieren." -#: src/cli/flags.md:256 +#: src/cli/flags.md:278 msgid "" "For the pinned OpenCode source graph, build the upstream web UI first, then " "compile from `packages/opencode`'s package root:" @@ -70829,7 +70657,7 @@ msgstr "" "vorgelagerte Web-UI und kompilieren Sie dann aus dem Paketstamm von " "`packages/opencode`:" -#: src/cli/flags.md:266 +#: src/cli/flags.md:288 msgid "" "The first command is the upstream preparation step; Perry reproduces only " "the deterministic `opencode-web-ui.gen.ts` file map. A missing/empty `dist` " @@ -70844,7 +70672,7 @@ msgstr "" "Führen Sie den Vorbereitungsbefehl für jede Quellrevision aus, damit die " "generierten Eingänge nicht veralten können." -#: src/cli/flags.md:272 +#: src/cli/flags.md:294 msgid "" "Every compile also writes `/assets.json`, a deterministic report " "of text, JSON, file-loader, WASM, and explicitly embedded assets. It records " @@ -70857,7 +70685,7 @@ msgstr "" "Ursprung, die Byte-Größe, SHA-256 Digest und gegebenenfalls das generierende " "Asset-Modul." -#: src/cli/flags.md:277 +#: src/cli/flags.md:299 msgid "" "**Note** `node:fs` consults the embedded registry _before_ disk, and a bare " "embed-relative key matches too — so `readFileSync(\"dist/index.html\")` " @@ -70873,7 +70701,7 @@ msgstr "" "explizite Formular `$perryfs/`, wenn Sie die eingebettete Kopie " "speziell meinen." -#: src/cli/flags.md:284 +#: src/cli/flags.md:306 msgid "" "Embedding supports host builds on macOS, Linux, and Windows. Windows uses " "LLVM clang to emit a COFF registration object; set `PERRY_LLVM_CLANG` when " @@ -70885,19 +70713,19 @@ msgstr "" "Sie `PERRY_LLVM_CLANG`, wenn das passende Clang nicht zuerst auf `PATH` ist. " "Cross-Target-Einbettung wird derzeit nicht unterstützt." -#: src/cli/flags.md:289 +#: src/cli/flags.md:311 msgid "Debug Flags" msgstr "Debug-Flags" -#: src/cli/flags.md:293 +#: src/cli/flags.md:315 msgid "Print HIR (intermediate representation) to stdout" msgstr "HIR (Zwischendarstellung) auf stdout ausgeben" -#: src/cli/flags.md:294 +#: src/cli/flags.md:316 msgid "`--trace `" msgstr "`--trace `" -#: src/cli/flags.md:294 +#: src/cli/flags.md:316 msgid "" "Dump IR at one or more pipeline stages. Comma-separated: `hir` (post-" "transform HIR), `llvm` (per-module `.ll` into `.perry-trace/llvm/`), or `all`" @@ -70906,11 +70734,11 @@ msgstr "" "(post-transform HIR), `llvm` (pro Modul `.ll` in `.perry-trace/llvm/`), oder " "`all`" -#: src/cli/flags.md:295 +#: src/cli/flags.md:317 msgid "`--focus `" msgstr "`--focus `" -#: src/cli/flags.md:295 +#: src/cli/flags.md:317 msgid "" "Restrict `--trace hir` to functions/methods/classes whose name contains " "`NAME`, suppressing import/export/init noise. Implies `--trace hir` if no " @@ -70920,7 +70748,7 @@ msgstr "" "enthält, und unterdrückt Import-/Export-/Init-Rauschen. Impliziert `--trace " "hir`, wenn keine Stufe angegeben ist" -#: src/cli/flags.md:296 +#: src/cli/flags.md:318 msgid "" "Produce `.o` object file(s) only, skip linking. The objects are written to `-" "o` — verbatim for a single-module program, otherwise into `-o`'s directory " @@ -70935,11 +70763,11 @@ msgstr "" "aktuellen Verzeichnis. Jeder Pfad wird als `Wrote object file: ` " "gedruckt" -#: src/cli/flags.md:297 +#: src/cli/flags.md:319 msgid "`--no-codegen`" msgstr "`--no-codegen`" -#: src/cli/flags.md:297 +#: src/cli/flags.md:319 msgid "" "Skip the `package.json` `perry.codegen` build-time steps (also " "`PERRY_SKIP_CODEGEN=1`). See [Project Configuration](../getting-started/" @@ -70949,15 +70777,15 @@ msgstr "" "`PERRY_SKIP_CODEGEN=1`). Siehe [Projektkonfiguration](../getting-started/" "project-config.md)" -#: src/cli/flags.md:298 +#: src/cli/flags.md:320 msgid "Keep `.o` and `.asm` intermediate files" msgstr "`.o`- und `.asm`-Zwischendateien behalten" -#: src/cli/flags.md:299 +#: src/cli/flags.md:321 msgid "`--debug-symbols`" msgstr "`--debug-symbols`" -#: src/cli/flags.md:299 +#: src/cli/flags.md:321 msgid "" "Retain symbols/DWARF (and emit a Windows PDB) instead of stripping the " "result, and emit a DWARF **line table** mapping machine code back to your `." @@ -70965,11 +70793,11 @@ msgid "" "(debugging.md)." msgstr "" -#: src/cli/flags.md:300 +#: src/cli/flags.md:322 msgid "`--function-source `" msgstr "" -#: src/cli/flags.md:300 +#: src/cli/flags.md:322 msgid "" "How much `Function.prototype.toString` source to keep. `full` (default) " "stores interned original source (nested functions share one module blob). " @@ -70978,33 +70806,33 @@ msgid "" "`PERRY_FUNCTION_SOURCE`." msgstr "" -#: src/cli/flags.md:301 +#: src/cli/flags.md:323 msgid "`--no-cache`" msgstr "`--no-cache`" -#: src/cli/flags.md:301 +#: src/cli/flags.md:323 msgid "" "Disable the per-module object cache for this build; also `PERRY_NO_CACHE=1`." msgstr "" "Deaktivieren Sie den pro-Module Objekt-Cache für diesen Build; auch " "`PERRY_NO_CACHE=1`." -#: src/cli/flags.md:302 +#: src/cli/flags.md:324 msgid "`--cache-dir `" msgstr "`--cache-dir `" -#: src/cli/flags.md:302 +#: src/cli/flags.md:324 msgid "" "Override the machine-local cache root; see [Cache Directory](cache-dir.md)." msgstr "" "Überschreiben Sie die maschinen-lokale Cache-Root; siehe [Cache-Verzeichnis]" "(cache-dir.md)." -#: src/cli/flags.md:303 +#: src/cli/flags.md:325 msgid "`--verify-native-regions`" msgstr "`--verify-native-regions`" -#: src/cli/flags.md:303 +#: src/cli/flags.md:325 msgid "" "Run native-representation lowering invariants and force codegen instead of " "cache reuse." @@ -71012,31 +70840,31 @@ msgstr "" "Führen Sie native-Representation senken Invarianten und Kraft Codegen statt " "Cache Wiederverwendung." -#: src/cli/flags.md:304 +#: src/cli/flags.md:326 msgid "`--disable-buffer-fast-path`" msgstr "`--disable-buffer-fast-path`" -#: src/cli/flags.md:304 +#: src/cli/flags.md:326 msgid "Disable native Buffer/Uint8Array load/store lowering for A/B diagnosis." msgstr "" "Deaktivieren native Buffer/Uint8Array load/store Senkung für A/B Diagnose." -#: src/cli/flags.md:305 +#: src/cli/flags.md:327 msgid "`--explain-lowering`" msgstr "`--explain-lowering`" -#: src/cli/flags.md:305 +#: src/cli/flags.md:327 msgid "" "Emit a type-lowering evidence report; implies native-region verification." msgstr "" "Geben Sie einen typsenkenden Beweisbericht aus; impliziert eine Überprüfung " "der Heimatregion." -#: src/cli/flags.md:306 +#: src/cli/flags.md:328 msgid "`--opt-report[=json]`" msgstr "`--opt-report[=json]`" -#: src/cli/flags.md:306 +#: src/cli/flags.md:328 msgid "" "Report which values Perry could **not** statically type, why, and whether " "you can fix it. Text by default; `--opt-report=json` emits a stable schema " @@ -71046,11 +70874,11 @@ msgstr "" "beheben können. Text standardmäßig; `--opt-report=json` emittiert ein " "stabiles Schema für das Tooling. Auch über `PERRY_OPT_REPORT=1` einstellbar" -#: src/cli/flags.md:307 +#: src/cli/flags.md:329 msgid "`--statepoint-report[=json]`" msgstr "`--statepoint-report[=json]`" -#: src/cli/flags.md:307 +#: src/cli/flags.md:329 msgid "" "Report native-stack GC root pressure: calls with live roots, audited non-" "collecting calls omitted, relocations, plain-map fallbacks, and live-root " @@ -71058,7 +70886,7 @@ msgid "" "plain stack-map and explicit-bridge modes it also named are gone)" msgstr "" -#: src/cli/flags.md:309 +#: src/cli/flags.md:331 msgid "" "The `--trace`/`--focus` pair localizes \"compiled to the wrong thing\" bugs: " "`perry compile foo.ts --trace hir,llvm --focus parseRow` dumps just the " @@ -71067,20 +70895,20 @@ msgid "" "llvm` forces a full recompile (the object cache otherwise skips codegen for " "unchanged modules, leaving the trace dir empty)." msgstr "" -"Das `--trace`/`--focus`-Paar lokalisiert Fehler des Typs \"falsch " -"compiliert\": `perry compile foo.ts --trace hir,llvm --focus parseRow` gibt " -"nur den abgesenkten HIR der Funktion `parseRow` und das LLVM-IR des Moduls " -"aus, sodass erkennbar ist, in welcher Stufe der Fehler aufgetreten ist, ohne " +"Das `--trace`/`--focus`-Paar lokalisiert Fehler des Typs \"falsch compiliert" +"\": `perry compile foo.ts --trace hir,llvm --focus parseRow` gibt nur den " +"abgesenkten HIR der Funktion `parseRow` und das LLVM-IR des Moduls aus, " +"sodass erkennbar ist, in welcher Stufe der Fehler aufgetreten ist, ohne " "einen vollständigen Modul-Dump durchsuchen zu müssen. `--trace llvm` " "erzwingt eine vollständige Neukompilierung (der Objekt-Cache überspringt " "andernfalls das Codegen für unveränderte Module und lässt das Trace-" "Verzeichnis leer)." -#: src/cli/flags.md:316 +#: src/cli/flags.md:338 msgid "`--opt-report` — why a value stayed boxed" msgstr "`--opt-report` — warum ein Wert verschachtelt blieb" -#: src/cli/flags.md:318 +#: src/cli/flags.md:340 msgid "" "Perry's speed comes from proving static types and selecting unboxed " "representations (see the [representation-selection RFC](https://github.com/" @@ -71098,11 +70926,11 @@ msgstr "" "Analog von LLVM's `-Rpass-missed` Bemerkungen: es druckt, was bewiesen " "wurde, was nicht, und welche Regel machte den Anruf." -#: src/cli/flags.md:345 +#: src/cli/flags.md:367 msgid "Each denied value carries:" msgstr "Jeder verweigerte Wert enthält:" -#: src/cli/flags.md:347 +#: src/cli/flags.md:369 msgid "" "**Position** — `local`, `param`, `return`, `field`, or `alloc-site` (an " "object literal that is never bound to a local, the `.map(x => ({...}))` " @@ -71112,7 +70940,7 @@ msgstr "" "Objekt, das niemals an einen lokalen, das `.map(x => ({...}))`-Idiom " "gebunden ist)." -#: src/cli/flags.md:350 +#: src/cli/flags.md:372 msgid "" "**The rule that denied it**, in the collector's own numbering, so you can " "check it against the named source file." @@ -71120,7 +70948,7 @@ msgstr "" "**Die Regel, die es verweigert**, in der eigenen Nummerierung des " "Kollektors, so dass Sie es gegen die benannte Quelldatei überprüfen können." -#: src/cli/flags.md:352 +#: src/cli/flags.md:374 msgid "" "**An actionability tier**: _Fixable in your source_ (stop reassigning it, " "don't capture it in a closure), _Inherently polymorphic_ (correctly boxed — " @@ -71132,7 +70960,7 @@ msgstr "" "(korrigiert — keine Aktion), oder _Perry limitation_ (nicht Ihr Code; " "benennt das Tracking Problem, wo man existiert)." -#: src/cli/flags.md:356 +#: src/cli/flags.md:378 msgid "" "**A hotness proxy.** Loop-nesting depth **and**, separately, whether the " "enclosing region is an iterating builtin's callback. The two are reported as " @@ -71147,7 +70975,7 @@ msgstr "" "dass die Schleifentiefe alleine die letzte zählt. Auch kein Profil – es sind " "statische Proxies." -#: src/cli/flags.md:362 +#: src/cli/flags.md:384 msgid "" "Wins are reported alongside the misses, so the ratio is visible rather than " "just the complaints." @@ -71155,7 +70983,7 @@ msgstr "" "Gewinne werden neben den Misses gemeldet, so dass das Verhältnis sichtbar " "ist, anstatt nur die Beschwerden." -#: src/cli/flags.md:365 +#: src/cli/flags.md:387 msgid "" "**It is observational only** — emitted code is byte-identical with the flag " "on and off. Like `--trace llvm`, it disables build and object cache reuse " @@ -71170,7 +70998,7 @@ msgstr "" "geht an **Stderr**, also mischt es sich nie in eine `--format json` Nutzlast " "oder die piped Ausgabe Ihres Programms." -#: src/cli/flags.md:371 +#: src/cli/flags.md:393 msgid "" "Values are identified by **function and binding name**: Perry's HIR keeps " "names through lowering but drops source positions, so there is no `file:" @@ -71184,15 +71012,15 @@ msgstr "" "`schema_version: 1`; das Diffing von zwei Builds' JSON ist eine billige CI-" "Prüfung gegen eine Darstellung, die lautlos auf Null zurückgeht." -#: src/cli/flags.md:377 +#: src/cli/flags.md:399 msgid "Output Optimization" msgstr "Ausgabe-Optimierung" -#: src/cli/flags.md:382 +#: src/cli/flags.md:404 msgid "`--march `" msgstr "`--march `" -#: src/cli/flags.md:382 +#: src/cli/flags.md:404 msgid "" "CPU baseline for the generated machine code: an LLVM CPU name (`x86-64-v2`, " "`x86-64-v3`, `znver2`, `apple-m1`, …), `native` (tune to the build machine — " @@ -71214,11 +71042,11 @@ msgstr "" "für `generic`. Gilt für App-Code und den autooptimierten runtime/stdlib-" "Rebuilt." -#: src/cli/flags.md:383 +#: src/cli/flags.md:405 msgid "`--features `" msgstr "`--features `" -#: src/cli/flags.md:383 +#: src/cli/flags.md:405 msgid "" "Define comma-separated compile-time `__feature_NAME__` constants for dead-" "code elimination." @@ -71226,11 +71054,11 @@ msgstr "" "Definieren Sie Komma-getrennte Compil-Time `__feature_NAME__` Konstanten für " "Dead-Code Elimination." -#: src/cli/flags.md:384 +#: src/cli/flags.md:406 msgid "`--no-auto-optimize`" msgstr "`--no-auto-optimize`" -#: src/cli/flags.md:384 +#: src/cli/flags.md:406 msgid "" "Use the prebuilt full runtime/stdlib instead of rebuilding the smallest " "reachable feature set." @@ -71238,50 +71066,50 @@ msgstr "" "Verwenden Sie das vorgefertigte volle runtime/stdlib anstatt das kleinste " "erreichbare Feature-Set neu aufzubauen." -#: src/cli/flags.md:385 src/cli/fast-math.md:13 src/cli/fast-math.md:108 +#: src/cli/flags.md:407 src/cli/fast-math.md:13 src/cli/fast-math.md:108 #: src/cli/fast-math.md:132 msgid "`--fast-math`" msgstr "`--fast-math`" -#: src/cli/flags.md:385 +#: src/cli/flags.md:407 msgid "Permit floating-point reassociation; see [Fast-math](fast-math.md)." msgstr "" "Erlauben Sie die Wiedervereinigung von Gleitpunkten; siehe [Fast-math](fast-" "math.md)." -#: src/cli/flags.md:386 +#: src/cli/flags.md:408 msgid "`--fp-contract off\\|on\\|fast`" msgstr "`--fp-contract off\\|on\\|fast`" -#: src/cli/flags.md:386 +#: src/cli/flags.md:408 msgid "Control fused multiply-add contraction separately from reassociation." msgstr "" "Kontrolle der verschmolzenen Multiplikations-Add-Kontraktion getrennt von " "der Reassoziation." -#: src/cli/flags.md:387 +#: src/cli/flags.md:409 msgid "`--record-function-order`" msgstr "" -#: src/cli/flags.md:387 +#: src/cli/flags.md:409 msgid "" -"Build a binary that appends each compiled function's name to " -"`$PERRY_FUNCTION_ORDER_OUT` the first time it runs; see [Function Order]" +"Build a binary that appends each compiled function's name to `" +"$PERRY_FUNCTION_ORDER_OUT` the first time it runs; see [Function Order]" "(function-order.md)." msgstr "" -#: src/cli/flags.md:388 +#: src/cli/flags.md:410 msgid "`--function-order `" msgstr "" -#: src/cli/flags.md:388 +#: src/cli/flags.md:410 msgid "" "Place the functions a recording listed first and together, in list order, so " "startup code shares pages; unknown names are ignored. See [Function Order]" "(function-order.md)." msgstr "" -#: src/cli/flags.md:390 +#: src/cli/flags.md:412 msgid "" "Minification strips comments, collapses whitespace, and mangles local " "variable/parameter/non-exported function names for smaller output." @@ -71289,11 +71117,11 @@ msgstr "" "Minifizierung entfernt Kommentare, kollabiert Whitespace und mangelt lokale " "Variablen-/Parameter-/nicht-exportierte-Funktions-Namen für kleinere Ausgabe." -#: src/cli/flags.md:392 +#: src/cli/flags.md:414 msgid "Size-optimized builds" msgstr "Größenoptimierte Builds" -#: src/cli/flags.md:394 +#: src/cli/flags.md:416 msgid "" "For the smallest possible native binaries, two opt-in environment variables " "rebuild the auto-optimized runtime/stdlib archives tuned for size instead of " @@ -71305,11 +71133,11 @@ msgstr "" "Geschwindigkeit abgestimmt sind (sie benötigen einen Perry-Workspace " "Checkout, wie der Rest von Auto-optimize):" -#: src/cli/flags.md:401 +#: src/cli/flags.md:423 msgid "`PERRY_SIZE_OPT=z` (or `s`)" msgstr "`PERRY_SIZE_OPT=z` (or `s`)" -#: src/cli/flags.md:401 +#: src/cli/flags.md:423 msgid "" "Rebuild the runtime/stdlib at `-C opt-level=z`/`s` instead of `3`. Roughly " "halves a small program's binary at some runtime-speed cost (compute-heavy " @@ -71322,11 +71150,11 @@ msgstr "" "Größenoptimierte und normale Archive werden unabhängig voneinander " "zwischengespeichert." -#: src/cli/flags.md:402 +#: src/cli/flags.md:424 msgid "`PERRY_SIZE_LTO=fat`" msgstr "`PERRY_SIZE_LTO=fat`" -#: src/cli/flags.md:402 +#: src/cli/flags.md:424 msgid "" "Additionally run whole-archive fat LTO over the rebuilt archives (slower " "rebuild, smaller binary). Only meaningful together with `PERRY_SIZE_OPT`." @@ -71335,11 +71163,11 @@ msgstr "" "Archive (langsamer Wiederaufbau, kleinere Binär). Nur sinnvoll zusammen mit " "`PERRY_SIZE_OPT`." -#: src/cli/flags.md:403 +#: src/cli/flags.md:425 msgid "`PERRY_SIZE_PANIC=abort-immediate`" msgstr "`PERRY_SIZE_PANIC=abort-immediate`" -#: src/cli/flags.md:403 +#: src/cli/flags.md:425 msgid "" "Additionally rebuild the Rust standard library with the `immediate-abort` " "panic strategy when auto-optimize proves that no UI/thread/plugin callback " @@ -71358,7 +71186,7 @@ msgstr "" "ohne ein symbolisiertes Backtrace ab — JS `throw`/`catch`, `finally`, " "Fehlerstacks und `uncaughtException` bleiben unberührt." -#: src/cli/flags.md:405 +#: src/cli/flags.md:427 msgid "" "A `console.log` hello world on macOS arm64: 4.03 MB default → 2.17 MB with " "`PERRY_SIZE_OPT=z PERRY_SIZE_LTO=fat` → 1.87 MB adding " @@ -71370,15 +71198,15 @@ msgstr "" "`PERRY_SIZE_PANIC=abort-immediate`. Programme, die mehr der Laufzeit " "verwenden, schrumpfen proportional weniger." -#: src/cli/flags.md:410 +#: src/cli/flags.md:432 msgid "Testing Flags" msgstr "Test-Flags" -#: src/cli/flags.md:414 +#: src/cli/flags.md:436 msgid "`--enable-geisterhand`" msgstr "`--enable-geisterhand`" -#: src/cli/flags.md:414 +#: src/cli/flags.md:436 msgid "" "Embed the [Geisterhand](../testing/geisterhand.md) HTTP server for " "programmatic UI testing (default port 7676)" @@ -71386,26 +71214,26 @@ msgstr "" "Bettet den [Geisterhand](../testing/geisterhand.md)-HTTP-Server für " "programmatisches UI-Testing ein (Standard-Port 7676)" -#: src/cli/flags.md:415 +#: src/cli/flags.md:437 msgid "`--geisterhand-port `" msgstr "`--geisterhand-port `" -#: src/cli/flags.md:415 +#: src/cli/flags.md:437 msgid "" "Set a custom port for the Geisterhand server (implies `--enable-geisterhand`)" msgstr "" "Benutzerdefinierten Port für den Geisterhand-Server setzen (impliziert `--" "enable-geisterhand`)" -#: src/cli/flags.md:417 +#: src/cli/flags.md:439 msgid "Runtime Flags" msgstr "Runtime-Flags" -#: src/cli/flags.md:421 +#: src/cli/flags.md:443 msgid "Enable V8 JavaScript runtime for unsupported npm packages" msgstr "V8-JavaScript-Runtime für nicht unterstützte npm-Pakete aktivieren" -#: src/cli/flags.md:422 +#: src/cli/flags.md:444 msgid "" "Force-link the wasmi WebAssembly host runtime (auto-detected when " "`WebAssembly.*` is referenced; needed only when loading via dlopen / FFI " @@ -71415,64 +71243,43 @@ msgstr "" "erkannt, wenn `WebAssembly.*` referenziert wird; nur erforderlich beim Laden " "via dlopen / FFI ohne statische Referenz)" -#: src/cli/flags.md:423 +#: src/cli/flags.md:445 msgid "Enable type checking via tsgo IPC" msgstr "Typprüfung über tsgo-IPC aktivieren" -#: src/cli/flags.md:424 +#: src/cli/flags.md:446 msgid "`--strict-eval`" msgstr "`--strict-eval`" -#: src/cli/flags.md:424 +#: src/cli/flags.md:446 msgid "" -"Fail the build if any runtime-unknown `eval(...)` / `new Function()` site is reachable. By default such a site is compiled to a deferred " -"runtime error (throws only if reached) and a compile-time notice is printed. " -"Also settable via `perry.eval = \"error\"` / `perry.strict = true` (package." -"json or perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. See [Limitations]" -"(../language/limitations.md#no-eval-or-dynamic-code)." -msgstr "" -"Fehler beim Bauen, wenn irgendeine Runtime-unbekannte `eval(...)` / `new " -"Function()`-Site erreichbar ist. Standardmäßig wird eine " -"solche Site zu einem latenten Laufzeitfehler kompiliert (wird nur dann " -"angezeigt, wenn sie erreicht wird) und eine Compil-Zeitanzeige wird " -"gedruckt. Auch über `perry.eval = \"error\"` / `perry.strict = true` " -"(package.json oder perry.toml) einstellbar. `PERRY_ALLOW_EVAL=1` zwingt es " -"ab. Siehe [Limitations](../language/limitations.md#no-eval-or-dynamic-code)." +"Legacy compatibility option; current dynamic-code lowering does not enforce " +"a general rejection gate. See [dynamic code " +"limits](../language/limitations.md#dynamic-code-and-module-loading)." +msgstr "" +"Legacy-Kompatibilitätsoption; die aktuelle Absenkung dynamischen Codes " +"erzwingt keine allgemeine Ablehnung. Siehe [Grenzen dynamischen " +"Codes](../language/limitations.md#dynamic-code-and-module-loading)." -#: src/cli/flags.md:425 +#: src/cli/flags.md:447 msgid "`--strict-dynamic-import`" msgstr "`--strict-dynamic-import`" -#: src/cli/flags.md:425 +#: src/cli/flags.md:447 msgid "" -"Fail the build if a dynamic `import(...)` has a runtime-computed (non-" -"resolvable) specifier. By default such a site is compiled to a rejected " -"`Promise` that throws a descriptive `Error` only if reached, and is listed " -"in the same end-of-build notice as deferred eval sites. Also settable via " -"`perry.dynamicImport = \"error\"` / `perry.strict = true` (package.json or " -"perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. Resolvable forms (string " -"literals, ternaries of resolvable arms, template literals over const locals, " -"finite union-typed params, glob) are unaffected. See [Limitations](../" -"language/limitations.md#no-eval-or-dynamic-code)." -msgstr "" -"Fehler beim Bauen, wenn ein dynamischer `import(...)` einen Runtime-Computed " -"(nicht resolvable) Spekifikator hat. Standardmäßig wird eine solche Site zu " -"einer abgelehnten `Promise` kompiliert, die nur dann eine beschreibende " -"`Error` wirft, wenn sie erreicht wird, und wird in der gleichen End-of-Build-" -"Notice als latente Eval-Sites aufgeführt. Auch über `perry.dynamicImport = " -"\"error\"` / `perry.strict = true` (package.json oder perry.toml) " -"einstellbar. `PERRY_ALLOW_EVAL=1` zwingt es ab. Auflösbare Formen " -"(Zeichenketten-Literaturen, Ternaren der auflösbaren Arme, Schablone-" -"Literaturen über Const-Lokale, endliche Unions-Typ-Params, Glob) bleiben " -"unberührt. Siehe [Limitations](../language/limitations.md#no-eval-or-dynamic-" -"code)." - -#: src/cli/flags.md:426 +"Reject unresolved asynchronous import specifiers during module collection. " +"Supported resolvable forms and synchronous `require()` are unaffected. " +"`PERRY_ALLOW_EVAL=1` disables this check." +msgstr "" +"Lehnt unaufgelöste asynchrone Imports bei der Modulerfassung ab. " +"Unterstützte auflösbare Formen und synchrones `require()` bleiben unberührt." +" `PERRY_ALLOW_EVAL=1` deaktiviert diese Prüfung." + +#: src/cli/flags.md:448 msgid "`--strict-unimplemented`" msgstr "`--strict-unimplemented`" -#: src/cli/flags.md:426 +#: src/cli/flags.md:448 msgid "" "Fail at compile time when a recognized but unimplemented Node/stdlib API is " "referenced instead of emitting a deferred runtime error." @@ -71481,22 +71288,22 @@ msgstr "" "Node/stdlib API referenziert wird, anstatt einen latenten Laufzeitfehler " "auszusenden." -#: src/cli/flags.md:427 +#: src/cli/flags.md:449 msgid "`--emit-attest`" msgstr "`--emit-attest`" -#: src/cli/flags.md:427 +#: src/cli/flags.md:449 msgid "Emit `.attest.json`; see [Binary attestation](emit-attest.md)." msgstr "" "Emit `.attest.json`; siehe [Binäre Bescheinigung](emit-attest.md)." -#: src/cli/flags.md:428 +#: src/cli/flags.md:450 msgid "" "Emit the platform sandbox sidecar; see [Sandbox profiles](emit-sandbox.md)." msgstr "" "Die Sandkasten-Sandbox auslassen; siehe [Sandkastenprofile](emit-sandbox.md)." -#: src/cli/flags.md:429 +#: src/cli/flags.md:451 msgid "" "Refuse reachable arbitrary-code-execution surfaces; see [Lockdown](lockdown." "md)." @@ -71504,7 +71311,7 @@ msgstr "" "Nicht erreichbare beliebige Code-Ausführungsflächen; siehe [Lockdown]" "(lockdown.md)." -#: src/cli/flags.md:431 +#: src/cli/flags.md:453 msgid "" "`--type-check` requires a project `tsconfig.json` and the verified preview " "build: install with `npm install --save-dev @typescript/native-preview@7.0.0-" @@ -71514,31 +71321,31 @@ msgid "" "handshake, so other builds are explicitly refused before IPC." msgstr "" -#: src/cli/flags.md:433 src/cli/perry-toml.md:512 +#: src/cli/flags.md:455 src/cli/perry-toml.md:512 msgid "Environment Variables" msgstr "Umgebungsvariablen" -#: src/cli/flags.md:437 src/cli/perry-toml.md:520 +#: src/cli/flags.md:459 src/cli/perry-toml.md:520 msgid "`PERRY_LICENSE_KEY`" msgstr "`PERRY_LICENSE_KEY`" -#: src/cli/flags.md:437 +#: src/cli/flags.md:459 msgid "Perry Hub license key for `perry publish`" msgstr "Perry-Hub-Lizenzschlüssel für `perry publish`" -#: src/cli/flags.md:438 src/cli/perry-toml.md:522 +#: src/cli/flags.md:460 src/cli/perry-toml.md:522 msgid "`PERRY_APPLE_CERTIFICATE_PASSWORD`" msgstr "`PERRY_APPLE_CERTIFICATE_PASSWORD`" -#: src/cli/flags.md:438 +#: src/cli/flags.md:460 msgid "Password for .p12 certificate" msgstr "Passwort für .p12-Zertifikat" -#: src/cli/flags.md:439 +#: src/cli/flags.md:461 msgid "`PERRY_TARGET_CPU`" msgstr "`PERRY_TARGET_CPU`" -#: src/cli/flags.md:439 +#: src/cli/flags.md:461 msgid "" "CPU baseline for generated machine code (same values as `--march`; the flag " "and perry.toml `[build] march` win over the env var)" @@ -71546,62 +71353,62 @@ msgstr "" "CPU-Basiswert für generierten Maschinencode (gleiche Werte wie `--march`; " "die Flagge und perry.toml `[build] march` gewinnen über die Env var)" -#: src/cli/flags.md:440 src/cli/updates.md:146 +#: src/cli/flags.md:462 src/cli/updates.md:146 msgid "`PERRY_NO_UPDATE_CHECK=1`" msgstr "`PERRY_NO_UPDATE_CHECK=1`" -#: src/cli/flags.md:440 +#: src/cli/flags.md:462 msgid "Disable automatic update checks" msgstr "Automatische Update-Prüfungen deaktivieren" -#: src/cli/flags.md:441 src/cli/updates.md:147 +#: src/cli/flags.md:463 src/cli/updates.md:147 msgid "`NO_UPDATE_NOTIFIER`" msgstr "`NO_UPDATE_NOTIFIER`" -#: src/cli/flags.md:441 +#: src/cli/flags.md:463 msgid "The same, using the ecosystem-wide spelling" msgstr "Gleiches gilt für die Verwendung der ökosystemweiten Rechtschreibung" -#: src/cli/flags.md:442 src/cli/updates.md:148 +#: src/cli/flags.md:464 src/cli/updates.md:148 msgid "`PERRY_UPDATE_MODE`" msgstr "`PERRY_UPDATE_MODE`" -#: src/cli/flags.md:442 +#: src/cli/flags.md:464 msgid "`off`/`notify`/`prompt`/`auto` for one run — see [Updates](updates.md)" msgstr "" "`off`/`notify`/`prompt`/`auto` für einen Lauf — siehe [Updates](updates.md)" -#: src/cli/flags.md:443 src/cli/updates.md:149 +#: src/cli/flags.md:465 src/cli/updates.md:149 msgid "`PERRY_UPDATE_SERVER`" msgstr "`PERRY_UPDATE_SERVER`" -#: src/cli/flags.md:443 +#: src/cli/flags.md:465 msgid "Custom update server URL" msgstr "URL für benutzerdefinierten Update-Server" -#: src/cli/flags.md:444 +#: src/cli/flags.md:466 msgid "`CI=true`" msgstr "`CI=true`" -#: src/cli/flags.md:444 +#: src/cli/flags.md:466 msgid "Auto-skip update checks (set by most CI systems)" msgstr "" "Update-Prüfungen automatisch überspringen (von den meisten CI-Systemen " "gesetzt)" -#: src/cli/flags.md:445 +#: src/cli/flags.md:467 msgid "`RUST_LOG`" msgstr "`RUST_LOG`" -#: src/cli/flags.md:445 +#: src/cli/flags.md:467 msgid "Debug logging level (`debug`, `info`, `trace`)" msgstr "Debug-Logging-Level (`debug`, `info`, `trace`)" -#: src/cli/flags.md:446 +#: src/cli/flags.md:468 msgid "`PERRY_OPT_REPORT`" msgstr "`PERRY_OPT_REPORT`" -#: src/cli/flags.md:446 +#: src/cli/flags.md:468 msgid "" "`1`/`text` or `json` — same as `--opt-report[=json]`, for driving the report " "from an environment where adding a flag is awkward" @@ -71610,7 +71417,7 @@ msgstr "" "Berichts aus einer Umgebung, in der das Hinzufügen einer Flagge unangenehm " "ist" -#: src/cli/flags.md:448 +#: src/cli/flags.md:470 msgid "" "The native-stack GC root-pressure report has **no** environment spelling: " "use `--statepoint-report[=json]`. The `PERRY_STATEPOINT_REPORT` variable is " @@ -71625,15 +71432,15 @@ msgstr "" "ein fünfter GC-env-Knopf ohne CI-Arm und wurde unter CLAUDE.mds GC-Knopf-" "Kill-Richtlinie gelöscht." -#: src/cli/flags.md:454 +#: src/cli/flags.md:476 msgid "Configuration Files" msgstr "Konfigurationsdateien" -#: src/cli/flags.md:456 +#: src/cli/flags.md:478 msgid "perry.toml (project)" msgstr "perry.toml (Projekt)" -#: src/cli/flags.md:458 +#: src/cli/flags.md:480 msgid "" "```toml\n" "[project]\n" @@ -71713,11 +71520,11 @@ msgstr "" "category = \"Development\"\n" "```" -#: src/cli/flags.md:496 +#: src/cli/flags.md:518 msgid "~/.perry/config.toml (global)" msgstr "~/.perry/config.toml (global)" -#: src/cli/flags.md:498 +#: src/cli/flags.md:520 msgid "" "```toml\n" "[apple]\n" @@ -71739,74 +71546,74 @@ msgstr "" "key_alias = \"my-key\"\n" "```" -#: src/cli/flags.md:511 +#: src/cli/flags.md:533 #, fuzzy msgid "# Simple CLI program\n" msgstr "# Einfaches CLI-Programm" -#: src/cli/flags.md:513 +#: src/cli/flags.md:535 #, fuzzy msgid "# iOS app for simulator\n" msgstr "# iOS App für Simulator" -#: src/cli/flags.md:516 +#: src/cli/flags.md:538 #, fuzzy msgid "# visionOS app for simulator\n" msgstr "# visionOS App für Simulator" -#: src/cli/flags.md:519 +#: src/cli/flags.md:541 #, fuzzy msgid "# Web app (WASM with DOM bridge — alias: --target wasm)\n" msgstr "# Web-App (WASM mit DOM-Brücke — Alias: --target wasm)" -#: src/cli/flags.md:522 +#: src/cli/flags.md:544 #, fuzzy msgid "# Plugin shared library\n" msgstr "# Freigabebibliothek des Moduls" -#: src/cli/flags.md:525 +#: src/cli/flags.md:547 #, fuzzy msgid "# iOS widget with bundle ID\n" msgstr "# iOS Widget mit Bundle ID" -#: src/cli/flags.md:528 +#: src/cli/flags.md:550 #, fuzzy msgid "# Debug compilation\n" msgstr "# Debug-Compilation" -#: src/cli/flags.md:531 +#: src/cli/flags.md:553 #, fuzzy msgid "# Verbose compilation\n" msgstr "# Verbose Zusammenstellung" -#: src/cli/flags.md:534 +#: src/cli/flags.md:556 #, fuzzy msgid "# Type-checked compilation\n" msgstr "# Typgesteuerte Zusammenstellung" -#: src/cli/flags.md:537 +#: src/cli/flags.md:559 #, fuzzy msgid "# Raw WASM binary (no HTML wrapper)\n" msgstr "# Raw WASM binär (kein HTML-Wrapper)" -#: src/cli/flags.md:540 +#: src/cli/flags.md:562 #, fuzzy msgid "# Minified web output (compresses embedded JS bridge)\n" msgstr "# Minifizierte Webausgabe (komprimiert eingebettete JS-Brücke)" -#: src/cli/flags.md:547 +#: src/cli/flags.md:569 msgid "[Commands](commands.md) — All CLI commands" msgstr "[Befehle](commands.md) — Alle CLI-Befehle" -#: src/cli/flags.md:548 +#: src/cli/flags.md:570 msgid "[Platform Overview](../platforms/overview.md) — Platform targets" msgstr "[Plattform-Überblick](../platforms/overview.md) — Plattform-Targets" -#: src/cli/flags.md:550 +#: src/cli/flags.md:572 msgid "Typed-feedback profile replay" msgstr "" -#: src/cli/flags.md:552 +#: src/cli/flags.md:574 msgid "" "`--typed-feedback-profile ` supplies an **advisory** profile to native " "LLVM lowering. Default builds do not read a profile. The first supported " @@ -71816,13 +71623,13 @@ msgid "" "explained." msgstr "" -#: src/cli/flags.md:558 +#: src/cli/flags.md:580 msgid "" "Capture a workload, then replay it with the **same compiler executable, " "source, target and lowering options**:" msgstr "" -#: src/cli/flags.md:572 +#: src/cli/flags.md:594 msgid "" "The conversion utility is in the Perry source checkout. Pair the catalog " "with the trace from that exact capture build. It retains only observed " @@ -71833,120 +71640,120 @@ msgid "" "normal binary cannot restore omitted instrumentation." msgstr "" -#: src/cli/flags.md:579 +#: src/cli/flags.md:601 msgid "The JSON replay schema is version 1:" msgstr "" -#: src/cli/flags.md:583 +#: src/cli/flags.md:605 #, fuzzy msgid "\"schema_version\"" msgstr "`schemaVersion`" -#: src/cli/flags.md:584 +#: src/cli/flags.md:606 #, fuzzy msgid "\"compiler\"" msgstr "compile" -#: src/cli/flags.md:584 +#: src/cli/flags.md:606 msgid "\"sha256:\"" msgstr "" -#: src/cli/flags.md:585 src/cli/perry-audit-sbom.md:48 +#: src/cli/flags.md:607 src/cli/perry-audit-sbom.md:48 msgid "\"modules\"" msgstr "\"Module\"" -#: src/cli/flags.md:586 +#: src/cli/flags.md:608 #, fuzzy msgid "\"identity\"" msgstr "Identität des Caches" -#: src/cli/flags.md:587 +#: src/cli/flags.md:609 #, fuzzy msgid "\"module\"" msgstr "\"Module\"" -#: src/cli/flags.md:587 +#: src/cli/flags.md:609 msgid "\"app.ts\"" msgstr "" -#: src/cli/flags.md:588 +#: src/cli/flags.md:610 #, fuzzy msgid "\"source_hash\"" msgstr "\"Quelle\"" -#: src/cli/flags.md:588 +#: src/cli/flags.md:610 msgid "\"sha256:\"" msgstr "" -#: src/cli/flags.md:589 +#: src/cli/flags.md:611 #, fuzzy msgid "\"hir_hash\"" msgstr "\"snippet_hash\"" -#: src/cli/flags.md:589 +#: src/cli/flags.md:611 msgid "\"\"" msgstr "" -#: src/cli/flags.md:590 +#: src/cli/flags.md:612 #, fuzzy msgid "\"lowering_hash\"" msgstr "\"Lodash\"" -#: src/cli/flags.md:590 +#: src/cli/flags.md:612 msgid "\"\"" msgstr "" -#: src/cli/flags.md:591 +#: src/cli/flags.md:613 #, fuzzy msgid "\"target\"" msgstr "\"Ziele\"" -#: src/cli/flags.md:591 +#: src/cli/flags.md:613 msgid "\"x86_64-unknown-linux-gnu\"" msgstr "" -#: src/cli/flags.md:593 +#: src/cli/flags.md:615 msgid "\"sites\"" msgstr "" -#: src/cli/flags.md:594 +#: src/cli/flags.md:616 #, fuzzy msgid "\"site_id\"" msgstr "\"client_id\"" -#: src/cli/flags.md:595 +#: src/cli/flags.md:617 #, fuzzy msgid "\"function\"" msgstr "\"Funktionen\"" -#: src/cli/flags.md:595 +#: src/cli/flags.md:617 #, fuzzy msgid "\"perry_fn_app_ts__read\"" msgstr "\"libperry_appreview.a\"" -#: src/cli/flags.md:596 +#: src/cli/flags.md:618 #, fuzzy msgid "\"array_element\"" msgstr "\"Inkremente\"" -#: src/cli/flags.md:597 +#: src/cli/flags.md:619 #, fuzzy msgid "\"operation\"" msgstr "\"caption\"" -#: src/cli/flags.md:597 +#: src/cli/flags.md:619 msgid "\"array[index]\"" msgstr "" -#: src/cli/flags.md:598 +#: src/cli/flags.md:620 msgid "\"observation_kind\"" msgstr "" -#: src/cli/flags.md:598 +#: src/cli/flags.md:620 msgid "\"numeric_array_element\"" msgstr "" -#: src/cli/flags.md:604 +#: src/cli/flags.md:626 msgid "" "Use generated identities, rather than copying this illustrative site ID. " "Site IDs identify deterministic lowering sites within an exact module/" @@ -71960,7 +71767,7 @@ msgid "" "replay is attempted." msgstr "" -#: src/cli/flags.md:614 +#: src/cli/flags.md:636 msgid "" "Malformed or unreadable explicit input is a compilation error naming the " "profile and how to create it. Well-formed schema/compiler/target/source/HIR/" @@ -71974,7 +71781,7 @@ msgid "" "lowering are deterministic for identical inputs." msgstr "" -#: src/cli/flags.md:624 +#: src/cli/flags.md:646 msgid "" "Profiles and TypeScript annotations never authorize an unchecked operation. " "Every replay-selected read rechecks the live receiver, array representation, " @@ -71987,11 +71794,11 @@ msgid "" "materialization record for every claimed profile selection." msgstr "" -#: src/cli/flags.md:634 +#: src/cli/flags.md:656 msgid "Unused re-export collection" msgstr "" -#: src/cli/flags.md:636 +#: src/cli/flags.md:658 msgid "" "Collection prunes unused `export { name } from`, `export * from`, and " "namespace re-export edges by default when the exporting file and every " @@ -72017,14 +71824,14 @@ msgid "" "deferred." msgstr "" -#: src/cli/flags.md:658 +#: src/cli/flags.md:680 msgid "" "Set `PERRY_NO_REEXPORT_PRUNE=1` to disable this collection pass for an A/B " "build. The compile summary reports unique omitted modules in the proven " "static dependency trees, excluding modules retained by another importer." msgstr "" -#: src/cli/flags.md:662 +#: src/cli/flags.md:684 msgid "" "Set `PERRY_COLLECT_ONLY=1` to stop after collection and write `/" "audit.json` and `module-graph.json` (paths, eager/deferred initialization, " @@ -72033,18 +71840,18 @@ msgid "" "checks. For example:" msgstr "" -#: src/cli/flags.md:673 +#: src/cli/flags.md:695 msgid "Linux hot text, including the Rust runtime" msgstr "" -#: src/cli/flags.md:675 +#: src/cli/flags.md:697 msgid "" "Ordering changes layout only: every unlisted function remains available at " "the same optimization level. Normal builds without an order keep the " "original link arguments and startup code." msgstr "" -#: src/cli/flags.md:679 +#: src/cli/flags.md:701 msgid "" "For a hot runtime profile, compile with `PERRY_KEEP_SYMBOLS=1`, then run " "`scripts/record_function_order.py -o app.order -- ./app representative-" @@ -72061,7 +71868,7 @@ msgid "" "avoids instrumentation in the sample run." msgstr "" -#: src/cli/flags.md:692 +#: src/cli/flags.md:714 msgid "" "Apply either list with `--function-order app.order`. On Linux the linker " "places listed generated and Rust function sections together at the beginning " @@ -72505,14 +72312,14 @@ msgstr "" #: src/cli/fast-math.md:55 msgid "" -"The same setting is available through `PERRY_FP_CONTRACT=off|on|fast` or " -"`\"perry\": { \"fpContract\": \"on\" }` in package.json. Explicit package, " +"The same setting is available through `PERRY_FP_CONTRACT=off|on|fast` or `" +"\"perry\": { \"fpContract\": \"on\" }` in package.json. Explicit package, " "env, or CLI `fpContract` values override the `--fast-math` implied default." msgstr "" -"Dieselbe Einstellung ist über `PERRY_FP_CONTRACT=off|on|fast` oder " -"`\"perry\": { \"fpContract\": \"on\" }` in package.json verfügbar. Explizite " -"Paket-, Umgebungsvariablen- oder CLI-`fpContract`-Werte überschreiben den " -"durch `--fast-math` impliziten Standardwert." +"Dieselbe Einstellung ist über `PERRY_FP_CONTRACT=off|on|fast` oder `\"perry" +"\": { \"fpContract\": \"on\" }` in package.json verfügbar. Explizite Paket-, " +"Umgebungsvariablen- oder CLI-`fpContract`-Werte überschreiben den durch `--" +"fast-math` impliziten Standardwert." #: src/cli/fast-math.md:60 msgid "What it actually changes" @@ -72985,9 +72792,8 @@ msgstr "" #: src/cli/dynamic-dispatch.md:11 msgid "" "is the standard string-based obfuscation pattern used by malicious npm " -"packages: `process[\"bind\" + \"ing\"](\"dns\")`, " -"`globalThis[atob(\"ZXZhbA==\")]()`, `fs[methodName]()` where `methodName` is " -"computed at runtime." +"packages: `process[\"bind\" + \"ing\"](\"dns\")`, `globalThis[atob(\"ZXZhbA==" +"\")]()`, `fs[methodName]()` where `methodName` is computed at runtime." msgstr "" "ist das Standard-String-basierte Verschleierungsmuster, das von bösartigen " "npm-Paketen verwendet wird: `process[\"bind\" + \"ing\"](\"dns\")`, " @@ -73283,14 +73089,14 @@ msgstr "" #: src/cli/dynamic-dispatch.md:143 msgid "" "See [`#503`](https://github.com/PerryTS/perry/issues/503) for design " -"discussion and the broader supply-chain hardening series (\\[`#495`–" -"`#506`\\] (https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security))." +"discussion and the broader supply-chain hardening series (\\[`#495`–`#506`" +"\\] (https://github.com/PerryTS/perry/issues?q=is%3Aissue+label%3Aenhancement" +"+security))." msgstr "" "Siehe [`#503`](https://github.com/PerryTS/perry/issues/503) für die Design-" "Diskussion und die umfassendere Supply-Chain-Hardening-Serie (\\[`#495`–" -"`#506`\\] (https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security))." +"`#506`\\] (https://github.com/PerryTS/perry/issues?q=is%3Aissue+label" +"%3Aenhancement+security))." #: src/cli/allow-js-runtime.md:1 msgid "JS Runtime Opt-In (`perry.allowJsRuntime`)" @@ -74224,12 +74030,12 @@ msgstr "Zusammenspiel mit der übrigen Sicherheitsreihe" #: src/cli/lockdown.md:65 msgid "" "Lockdown is the umbrella mode for the wider supply-chain hardening series " -"([`#495`–`#506`](https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security)):" +"([`#495`–`#506`](https://github.com/PerryTS/perry/issues?q=is%3Aissue+label" +"%3Aenhancement+security)):" msgstr "" "Lockdown ist der übergeordnete Modus für die umfassende Supply-Chain-" -"Härtungsreihe ([`#495`–`#506`](https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security)):" +"Härtungsreihe ([`#495`–`#506`](https://github.com/PerryTS/perry/issues?q=is" +"%3Aissue+label%3Aenhancement+security)):" #: src/cli/lockdown.md:68 msgid "" @@ -74649,8 +74455,8 @@ msgstr "\"*\"" #: src/cli/capabilities.md:39 msgid "" -"Keys are exact npm package names (`@scope/pkg` accepted) or the universal " -"`\"*\"` default." +"Keys are exact npm package names (`@scope/pkg` accepted) or the universal `" +"\"*\"` default." msgstr "" "Schlüssel sind exakte npm-Paketnamen (`@scope/pkg` wird akzeptiert) oder der " "universelle `\"*\"`-Standard." @@ -75774,9 +75580,9 @@ msgid "" "machine crashes with SIGILL on older x86-64 CPUs. Equivalent to `--march` / " "`PERRY_TARGET_CPU`." msgstr "" -"CPU-Basiswert für den generierten Maschinencode: ein LLVM-CPU-Name " -"(`\"x86-64-v2\"`, `\"x86-64-v3\"`, `\"znver2\"`, `\"apple-m1\"`, ...), " -"`\"native\"` (Einstimmung auf den kompletten Befehlssatz des Baumaschinens) " +"CPU-Basiswert für den generierten Maschinencode: ein LLVM-CPU-Name (`" +"\"x86-64-v2\"`, `\"x86-64-v3\"`, `\"znver2\"`, `\"apple-m1\"`, ...), `" +"\"native\"` (Einstimmung auf den kompletten Befehlssatz des Baumaschinens) " "oder `\"generic\"` (die portable Basiswertlinie der Zielarchitektur). Pinen " "Sie dies, wenn der Binär auf anderen Rechnern als der Build-Box läuft – ein " "unpinned host-native Build auf einem AVX-512 Rechner stürzt mit SIGILL auf " @@ -75793,11 +75599,11 @@ msgstr "bool" #: src/cli/perry-toml.md:160 msgid "" -"Boolean shorthand for `march`: `true` → `\"native\"`, `false` → " -"`\"generic\"`. Ignored when `march` is set." +"Boolean shorthand for `march`: `true` → `\"native\"`, `false` → `\"generic" +"\"`. Ignored when `march` is set." msgstr "" -"Boolesche Kurzhand für `march`: `true` → `\"native\"`, `false` → " -"`\"generic\"`. Ignoriert, wenn `march` gesetzt ist." +"Boolesche Kurzhand für `march`: `true` → `\"native\"`, `false` → `\"generic" +"\"`. Ignoriert, wenn `march` gesetzt ist." #: src/cli/perry-toml.md:162 msgid "" @@ -75947,8 +75753,8 @@ msgid "" "Separate `.p12` certificate for notarization (only used with `distribute = " "\"both\"`)" msgstr "" -"Separates `.p12`-Zertifikat für Notarisierung (nur mit `distribute = " -"\"both\"` verwendet)" +"Separates `.p12`-Zertifikat für Notarisierung (nur mit `distribute = \"both" +"\"` verwendet)" #: src/cli/perry-toml.md:193 msgid "`notarize_signing_identity`" @@ -76500,8 +76306,8 @@ msgstr "`permissions`" #: src/cli/perry-toml.md:340 msgid "" -"Android permissions (e.g., `[\"INTERNET\", \"CAMERA\", " -"\"ACCESS_FINE_LOCATION\"]`)" +"Android permissions (e.g., `[\"INTERNET\", \"CAMERA\", \"ACCESS_FINE_LOCATION" +"\"]`)" msgstr "" "Android-Berechtigungen (z. B. `[\"INTERNET\", \"CAMERA\", " "\"ACCESS_FINE_LOCATION\"]`)" @@ -76557,11 +76363,11 @@ msgstr "Paketformat: `\"appimage\"`, `\"deb\"` oder `\"rpm\"`" #: src/cli/perry-toml.md:356 msgid "" -"Desktop application category (e.g., `\"Development\"`, `\"Utility\"`, " -"`\"Game\"`)" +"Desktop application category (e.g., `\"Development\"`, `\"Utility\"`, `\"Game" +"\"`)" msgstr "" -"Desktop-Anwendungs-Kategorie (z. B. `\"Development\"`, `\"Utility\"`, " -"`\"Game\"`)" +"Desktop-Anwendungs-Kategorie (z. B. `\"Development\"`, `\"Utility\"`, `\"Game" +"\"`)" #: src/cli/perry-toml.md:357 msgid "Application description for package metadata" @@ -76751,8 +76557,8 @@ msgid "" "this threshold. Values: `\"A\"`, `\"A-\"`, `\"B\"`, `\"C\"`, `\"D\"`, `\"F\"`" msgstr "" "Minimal akzeptable Audit-Note. Der Build schlägt fehl, wenn die tatsächliche " -"Note unter diesem Schwellenwert liegt. Werte: `\"A\"`, `\"A-\"`, `\"B\"`, " -"`\"C\"`, `\"D\"`, `\"F\"`" +"Note unter diesem Schwellenwert liegt. Werte: `\"A\"`, `\"A-\"`, `\"B\"`, `" +"\"C\"`, `\"D\"`, `\"F\"`" #: src/cli/perry-toml.md:417 msgid "`severity`" @@ -76764,11 +76570,11 @@ msgstr "`\"all\"`" #: src/cli/perry-toml.md:417 msgid "" -"Filter findings by severity: `\"all\"`, `\"critical\"`, `\"high\"`, " -"`\"medium\"`, `\"low\"`" +"Filter findings by severity: `\"all\"`, `\"critical\"`, `\"high\"`, `\"medium" +"\"`, `\"low\"`" msgstr "" -"Findings nach Schweregrad filtern: `\"all\"`, `\"critical\"`, `\"high\"`, " -"`\"medium\"`, `\"low\"`" +"Findings nach Schweregrad filtern: `\"all\"`, `\"critical\"`, `\"high\"`, `" +"\"medium\"`, `\"low\"`" #: src/cli/perry-toml.md:418 msgid "`ignore`" @@ -77224,7 +77030,7 @@ msgstr "Perry liest Konfiguration aus beiden Dateien. Was wo hingehört:" msgid "Setting" msgstr "Einstellung" -#: src/cli/perry-toml.md:581 src/internals/memory-model.md:310 +#: src/cli/perry-toml.md:581 src/internals/memory-model.md:294 msgid "File" msgstr "Datei" @@ -78613,8 +78419,8 @@ msgid "" "`recordEmbeddedResponse` reads the body according to your configured " "`source`, so a registry answering a `gh-releases` request is rejected rather " "than read as version `\"\"`. A version that does not parse is also rejected " -"— one malformed answer would otherwise become a permanent \"update " -"available\" your users cannot dismiss." +"— one malformed answer would otherwise become a permanent \"update available" +"\" your users cannot dismiss." msgstr "" "`recordEmbeddedResponse` liest den Körper nach Ihrem konfigurierten " "`source`, so dass eine Registry, die eine `gh-releases`-Anfrage beantwortet, " @@ -78636,9 +78442,9 @@ msgid "" "Perry sends no telemetry unless you accept the first-run prompt (or " "explicitly set `telemetry.enabled = true` in `~/.perry/config.toml`). This " "setting is the master consent gate for every telemetry channel. If it is " -"false or missing, nothing is sent, even when `compatibility_reports = " -"\"on\"`. The environment overrides `PERRY_NO_TELEMETRY=1` and `CI=true` " -"always win as well." +"false or missing, nothing is sent, even when `compatibility_reports = \"on" +"\"`. The environment overrides `PERRY_NO_TELEMETRY=1` and `CI=true` always " +"win as well." msgstr "" #: src/cli/telemetry.md:9 @@ -78875,12 +78681,13 @@ msgstr "" #: src/internals/memory-model.md:5 msgid "" -"If you've ever wondered \"does Perry use reference counting?\" — no. There " -"is no `Rc` at runtime. Perry has a real tracing GC, described below." +"JavaScript heap objects use tracing GC. Rust runtime components may " +"separately use reference-counted ownership; that is not the JavaScript heap " +"collector." msgstr "" -"Falls Sie sich jemals gefragt haben: „Verwendet Perry Reference Counting?“ — " -"nein. Es gibt kein `Rc` zur Laufzeit. Perry besitzt einen echten Tracing-GC, " -"der im Folgenden beschrieben wird." +"JavaScript-Heapobjekte verwenden einen Tracing-GC. Rust-Komponenten können " +"zusätzlich referenzgezählten Besitz verwenden; das ist nicht der JavaScript-" +"Heapkollektor." #: src/internals/memory-model.md:7 msgid "" @@ -78898,119 +78705,141 @@ msgstr "" msgid "Value representation: NaN-boxing" msgstr "Wertrepräsentation: NaN-Boxing" -#: src/internals/memory-model.md:13 +#: src/internals/memory-model.md:13 src/contributing/architecture.md:49 msgid "" -"Every JavaScript value in Perry is a single 64-bit word. The encoding piggy-" -"backs on IEEE 754: any `f64` whose exponent is all-ones and whose mantissa " -"is non-zero is a NaN, and there are ~2⁵² distinct NaN bit patterns. Perry " -"uses the high 16 bits as a type tag and the low 48 (or 32) bits as the " -"payload." +"The generic JavaScript value ABI is a 64-bit NaN-boxed word. Proven native " +"values and specialized storage can use other representations; boxing is " +"required when crossing a generic value boundary." msgstr "" -"Jeder JavaScript-Wert in Perry ist ein einzelnes 64-Bit-Wort. Die Kodierung " -"nutzt IEEE 754: Jedes `f64`, dessen Exponent ausschließlich aus Einsen " -"besteht und dessen Mantisse ungleich null ist, ist ein NaN, und es gibt ~2⁵² " -"verschiedene NaN-Bitmuster. Perry verwendet die oberen 16 Bits als Typ-Tag " -"und die unteren 48 (oder 32) Bits als Nutzlast." +"Die generische JavaScript-Wert-ABI verwendet ein 64-Bit-NaN-Boxing-Wort. " +"Nachgewiesene native Werte und spezialisierte Speicher können andere " +"Darstellungen verwenden; beim Übergang zu einer generischen " +"Wertschnittstelle ist Boxing erforderlich." #: src/internals/memory-model.md:15 +msgid "" +"The encoding piggy-backs on IEEE 754: any `f64` whose exponent is all-ones " +"and whose mantissa is non-zero is a NaN, and there are ~2⁵² distinct NaN bit" +" patterns. Perry uses the high 16 bits as a type tag and the low 48 (or 32) " +"bits as the payload." +msgstr "" +"Die Kodierung nutzt IEEE 754: Ein `f64` mit ausschließlich Einsen im " +"Exponenten und einer Mantisse ungleich null ist NaN; es gibt etwa 2⁵² NaN-" +"Bitmuster. Perry verwendet die oberen 16 Bit als Typtag und die unteren 48 " +"(oder 32) als Nutzdaten." + +#: src/internals/memory-model.md:17 msgid "Tag (high 16 bits)" msgstr "Tag (obere 16 Bits)" -#: src/internals/memory-model.md:15 +#: src/internals/memory-model.md:17 msgid "Payload" msgstr "Nutzlast" -#: src/internals/memory-model.md:17 +#: src/internals/memory-model.md:19 msgid "`0x7FFC…0001`" msgstr "`0x7FFC…0001`" -#: src/internals/memory-model.md:17 src/internals/memory-model.md:18 #: src/internals/memory-model.md:19 src/internals/memory-model.md:20 +#: src/internals/memory-model.md:21 src/internals/memory-model.md:22 msgid "— (singleton)" msgstr "— (Singleton)" -#: src/internals/memory-model.md:18 +#: src/internals/memory-model.md:20 msgid "`0x7FFC…0002`" msgstr "`0x7FFC…0002`" -#: src/internals/memory-model.md:18 +#: src/internals/memory-model.md:20 msgid "`null`" msgstr "`null`" -#: src/internals/memory-model.md:19 +#: src/internals/memory-model.md:21 msgid "`0x7FFC…0003`" msgstr "`0x7FFC…0003`" -#: src/internals/memory-model.md:20 +#: src/internals/memory-model.md:22 msgid "`0x7FFC…0004`" msgstr "`0x7FFC…0004`" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 src/contributing/architecture.md:57 msgid "`0x7FF9`" msgstr "`0x7FF9`" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 msgid "Short string (0–5 bytes)" msgstr "Kurze Zeichenfolge (0–5 Bytes)" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 msgid "length in bits 40–47, the bytes in bits 0–39 — no allocation" msgstr "Länge in Bits 40–47, die Bytes in Bits 0–39 — keine Zuordnung" -#: src/internals/memory-model.md:22 src/contributing/architecture.md:53 +#: src/internals/memory-model.md:24 src/contributing/architecture.md:56 msgid "`0x7FFA`" msgstr "`0x7FFA`" -#: src/internals/memory-model.md:22 src/internals/memory-model.md:24 -#: src/internals/memory-model.md:26 +#: src/internals/memory-model.md:24 src/internals/memory-model.md:27 +#: src/internals/memory-model.md:29 msgid "low 48 bits = heap pointer" msgstr "untere 48 Bits = Heap-Zeiger" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "`0x7FFB`" msgstr "`0x7FFB`" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "JS handle" msgstr "JS-Handgriff" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "low 48 bits = handle id (V8-backed objects)" msgstr "niedrige 48 Bit = Handle id (V8-gestützte Objekte)" -#: src/internals/memory-model.md:24 src/contributing/architecture.md:51 +#: src/internals/memory-model.md:26 src/contributing/architecture.md:58 +#, fuzzy +msgid "`0xFFF9`" +msgstr "`0x7FF9`" + +#: src/internals/memory-model.md:26 src/contributing/architecture.md:58 +msgid "Native handle" +msgstr "Natives Handle" + +#: src/internals/memory-model.md:26 +msgid "low 48 bits = registry id, not a heap pointer" +msgstr "untere 48 Bit = Registrierungs-ID, kein Heapzeiger" + +#: src/internals/memory-model.md:27 src/contributing/architecture.md:54 msgid "`0x7FFD`" msgstr "`0x7FFD`" -#: src/internals/memory-model.md:24 +#: src/internals/memory-model.md:27 msgid "Object / Array / Closure" msgstr "Objekt / Array / Closure" -#: src/internals/memory-model.md:25 src/contributing/architecture.md:52 +#: src/internals/memory-model.md:28 src/contributing/architecture.md:55 msgid "`0x7FFE`" msgstr "`0x7FFE`" -#: src/internals/memory-model.md:25 +#: src/internals/memory-model.md:28 msgid "Int32" msgstr "Int32" -#: src/internals/memory-model.md:25 +#: src/internals/memory-model.md:28 msgid "low 32 bits = signed int" msgstr "untere 32 Bits = vorzeichenbehafteter Integer" -#: src/internals/memory-model.md:26 src/contributing/architecture.md:50 +#: src/internals/memory-model.md:29 src/contributing/architecture.md:53 msgid "`0x7FFF`" msgstr "`0x7FFF`" -#: src/internals/memory-model.md:27 +#: src/internals/memory-model.md:30 msgid "anything else" msgstr "alles andere" -#: src/internals/memory-model.md:27 +#: src/internals/memory-model.md:30 msgid "the full 64 bits are the number" msgstr "alle 64 Bits bilden die Zahl" -#: src/internals/memory-model.md:36 +#: src/internals/memory-model.md:40 msgid "" "Source: `crates/perry-runtime/src/value/tags.rs` (the singleton and pointer " "tags), with the rest of the `value/` module tree for the encode/decode " @@ -79022,11 +78851,11 @@ msgstr "" "`TAG_HOLE` und `TAG_TDZ` sind zwei weitere `0x7FFC` Singletons, die die " "Laufzeit intern verwendet; sie sind keine beobachtbaren Werte." -#: src/internals/memory-model.md:41 +#: src/internals/memory-model.md:45 msgid "Three consequences worth noting:" msgstr "Drei erwähnenswerte Konsequenzen:" -#: src/internals/memory-model.md:43 +#: src/internals/memory-model.md:47 msgid "" "**Numbers are free.** A plain `f64` value is its own representation — no " "boxing, no header, no allocation. Numeric hot loops cost nothing in memory " @@ -79036,7 +78865,7 @@ msgstr "" "Repräsentation — kein Boxing, kein Header, keine Allokation. Numerische Hot-" "Loops verursachen keinerlei Speicher-Traffic." -#: src/internals/memory-model.md:44 +#: src/internals/memory-model.md:48 msgid "" "**The GC can identify pointer values from the tag alone.** When tracing a " "value, the collector masks the high bits, checks for `0x7FFA`/`0x7FFD`/" @@ -79049,7 +78878,7 @@ msgstr "" "niedrigen Bits oder überspringt ihn. Es gibt keine wertbezogene Laufzeit-" "Typabfrage." -#: src/internals/memory-model.md:45 +#: src/internals/memory-model.md:49 msgid "" "**Type checks are bitwise.** `typeof` and many fast paths in the runtime are " "register-level mask-and-compare operations." @@ -79057,42 +78886,36 @@ msgstr "" "**Typprüfungen sind bitweise.** `typeof` und viele Fast-Paths im Runtime " "sind Masken-und-Vergleichs-Operationen auf Registerebene." -#: src/internals/memory-model.md:47 +#: src/internals/memory-model.md:51 msgid "Heap layout: per-thread arena, nursery + old-gen" msgstr "Heap-Layout: Thread-lokale Arena, Nursery + Old-Gen" -#: src/internals/memory-model.md:49 -msgid "" -"Perry is single-threaded by default, and each thread owns its own heap. " -"Sharing across threads happens via deep copy (`SerializedValue`), not shared " -"memory, so the GC never has to synchronize across threads." -msgstr "" -"Perry ist standardmäßig single-threaded, und jeder Thread besitzt seinen " -"eigenen Heap. Die Weitergabe zwischen Threads erfolgt über Deep Copy " -"(`SerializedValue`), nicht über Shared Memory, sodass der GC nie " -"threadübergreifend synchronisieren muss." - -#: src/internals/memory-model.md:51 -msgid "Within a thread, the heap is two arenas:" -msgstr "Innerhalb eines Threads besteht der Heap aus zwei Arenas:" - #: src/internals/memory-model.md:53 msgid "" -"**`ARENA`** — the nursery. New allocations land here. Carved into 1 MB " -"blocks (since v0.5.196)." +"Ordinary values cross agents through `SerializedValue`. SharedArrayBuffer " +"backing is explicitly shared, and supported transfer operations move " +"ownership. Each collector traces its own heap; shared backing and transfer " +"coordination have separate lifetime rules." msgstr "" -"**`ARENA`** — die Nursery. Neue Allokationen landen hier. Aufgeteilt in 1-MB-" -"Blöcke (seit v0.5.196)." +"Gewöhnliche Werte wechseln über `SerializedValue` zwischen Agenten. Der " +"Speicher eines SharedArrayBuffer wird ausdrücklich geteilt; unterstützte " +"Transfers übertragen den Besitz. Jeder Kollektor verfolgt seinen eigenen " +"Heap. Für gemeinsamen Speicher und Transfers gelten eigene " +"Lebensdauerregeln." -#: src/internals/memory-model.md:54 +#: src/internals/memory-model.md:55 msgid "" -"**`OLD_ARENA`** — the old generation. Holds objects that have survived " -"enough minor GCs to be tenured." +"The nursery uses Eden and survivor spaces; `OLD_ARENA` holds old objects. " +"Regular blocks occupy one aligned region (2 MiB on Linux, 1 MiB elsewhere). " +"Dedicated young large-object storage and separately tracked malloc " +"allocations also participate in collection." msgstr "" -"**`OLD_ARENA`** — die Old Generation. Enthält Objekte, die genug Minor-GC-" -"Zyklen überlebt haben, um tenured zu werden." +"Die Nursery verwendet Eden- und Survivor-Bereiche; `OLD_ARENA` enthält alte " +"Objekte. Reguläre Blöcke belegen eine ausgerichtete Region (2 MiB unter " +"Linux, sonst 1 MiB). Auch gesonderter Speicher für junge große Objekte und " +"separat erfasste malloc-Allokationen werden gesammelt." -#: src/internals/memory-model.md:56 +#: src/internals/memory-model.md:57 msgid "" "Every allocation, in either arena, is prefixed by an 8-byte `GcHeader` " "(`crates/perry-runtime/src/gc/types.rs`):" @@ -79100,22 +78923,26 @@ msgstr "" "Jede Zuweisung in beiden Arenen wird durch einen 8-Byte `GcHeader` (`crates/" "perry-runtime/src/gc/types.rs`) voreingestellt:" -#: src/internals/memory-model.md:62 +#: src/internals/memory-model.md:63 #, fuzzy msgid "// GC_TYPE_ARRAY, GC_TYPE_STRING, …\n" msgstr "// GC_TYPE_ARRAY, GC_TYPE_STRING, …" -#: src/internals/memory-model.md:63 +#: src/internals/memory-model.md:64 #, fuzzy msgid "// MARKED | ARENA | PINNED | TENURED | HAS_SURVIVED | …\n" msgstr "WICHTIG | ARENA | PINNED | TENURED | HAS_SURVIVED | …" #: src/internals/memory-model.md:65 +msgid "// kind-specific facts, including object callability\n" +msgstr "" + +#: src/internals/memory-model.md:66 #, fuzzy msgid "// total alloc size, used for arena block walking\n" msgstr "// Gesamtgröße des Allocs, verwendet für Arena Block Walking" -#: src/internals/memory-model.md:69 +#: src/internals/memory-model.md:70 msgid "" "Callers receive a pointer **after** the header (`ptr + 8`), so from " "TypeScript code's perspective the header is invisible. The collector finds " @@ -79125,25 +78952,23 @@ msgstr "" "Header aus Sicht des TypeScript-Codes unsichtbar ist. Der Collector findet " "den Header durch Subtraktion von 8." -#: src/internals/memory-model.md:71 +#: src/internals/memory-model.md:72 msgid "" -"Allocation goes through `gc_malloc(size, obj_type)` in the `gc/` module " -"tree. LLVM-generated code emits calls to this for every object literal, " -"array literal, closure capture, string concat, BigInt operation, etc. Going " -"through the GC allocation funnel is how the collector accounts for memory " -"and decides when to collect." +"Allocation uses runtime helpers and compiler-emitted inline bump allocation." +" Inline births initialize a complete layout and obey the same accounting and" +" GC birth-color contract. `gc_malloc` is one allocation entry point, not a " +"call emitted for every object." msgstr "" -"Die Zuordnung erfolgt durch `gc_malloc(size, obj_type)` im `gc/` Modulbaum. " -"LLVM-generierter Code sendet Anrufe zu diesem für jedes Objekt wörtlich, " -"Array wörtlich, Schließung Capture, String Concat, BigInt Betrieb, etc. " -"Durch den GC-Zuweisungs-Trichter zu gehen ist, wie der Kollektor für " -"Speicher Konten und entscheidet, wann zu sammeln." +"Allokationen verwenden Laufzeithelfer und vom Compiler erzeugte Inline-Bump-" +"Allokation. Inline-Allokationen initialisieren ein vollständiges Layout und " +"erfüllen dieselben Regeln für Speicherbilanz und GC-Erzeugungsfarbe. " +"`gc_malloc` ist ein Allokationseinstieg, kein Aufruf für jedes Objekt." -#: src/internals/memory-model.md:77 +#: src/internals/memory-model.md:74 msgid "How the GC finds roots" msgstr "Wie der GC Roots findet" -#: src/internals/memory-model.md:79 +#: src/internals/memory-model.md:76 msgid "" "This is the part most people are surprised by: if Perry compiles through " "LLVM, the optimizer is free to keep values in registers, spill them to stack " @@ -79155,32 +78980,30 @@ msgstr "" "auslagern oder rematerialisieren — nichts davon kann der Collector " "introspektieren. Wie weiß der Collector also, welche JS-Werte lebendig sind?" -#: src/internals/memory-model.md:81 +#: src/internals/memory-model.md:78 msgid "Three mechanisms cover different storage locations:" msgstr "Drei Mechanismen decken verschiedene Speicherorte ab:" -#: src/internals/memory-model.md:83 +#: src/internals/memory-model.md:80 msgid "1. Target-aware precise roots (codegen-emitted)" msgstr "1. Zielgenaue Wurzeln (Codegen-emittent)" -#: src/internals/memory-model.md:85 +#: src/internals/memory-model.md:82 msgid "" -"One pointer-local analysis feeds two correct lowerings. On supported 64-bit " -"AArch64/arm64 and x86-64 targets, native RS4GC statepoints plus Perry's " -"compact stack map are the default. `arm64_32` watchOS, ARM64 Windows, and " -"unsupported architectures use Perry's heap-backed shadow frames. The " -"fallback is a root lowering, not an unrooted mode; see [the current GC page]" -"(garbage-collector.md#roots-by-target)." +"Native AArch64 and x86-64 use LLVM RS4GC statepoints and compact stack maps." +" WASI and ARM64 Windows use shadow frames. watchOS ILP32 currently rejects " +"compiler emission. Managed values must remain in writable roots across " +"collection; generated native homes store boxed values and are reloaded after" +" relocation. See [roots by target](garbage-collector.md#roots-by-target)." msgstr "" -"Eine Pointer-lokale Analyse liefert zwei korrekte Absenkungen. Auf " -"unterstützten 64-Bit AArch64/arm64 und x86-64 Targets sind native RS4GC " -"Statepoints sowie Perry's kompakte Stack-Map die Standardeinstellung. " -"`arm64_32` watchOS, ARM64 Windows und nicht unterstützte Architekturen " -"verwenden Perrys hap-backed-Schattenrahmen. Der Fallback ist ein Root-" -"Downing, kein unrooted Modus; siehe [die aktuelle GC-Seite](garbage-" -"collector.md#roots-by-target)." +"Native AArch64- und x86-64-Ziele verwenden LLVM-RS4GC-Statepoints und " +"kompakte Stackmaps. WASI und ARM64 Windows verwenden Shadow Frames. watchOS " +"ILP32 lehnt die Codeausgabe derzeit ab. Verwaltete Werte müssen während " +"einer Sammlung in beschreibbaren Roots bleiben; native Speicherplätze " +"enthalten geboxtе Werte und werden nach Verschiebungen neu geladen. Siehe " +"[Roots nach Zielplattform](garbage-collector.md#roots-by-target)." -#: src/internals/memory-model.md:91 +#: src/internals/memory-model.md:84 msgid "" "At a safepoint the selected map describes each live managed local regardless " "of whether LLVM kept it in a register, spilled it, or relocated it." @@ -79189,11 +79012,11 @@ msgstr "" "verwaltete lokale unabhängig davon, ob LLVM sie in einem Register " "aufbewahrt, verschüttet oder verschoben hat." -#: src/internals/memory-model.md:94 +#: src/internals/memory-model.md:87 msgid "2. Conservative native-stack diagnostic" msgstr "2. Konservative Native-Stack-Diagnose" -#: src/internals/memory-model.md:96 +#: src/internals/memory-model.md:89 msgid "" "The production default does not scan the native stack conservatively: `Auto` " "resolves to `SkipDisabled`. `PERRY_CONSERVATIVE_STACK_SCAN=full` is a " @@ -79208,11 +79031,11 @@ msgstr "" "doppeldeutige Wurzel nicht sicher umgeschrieben werden kann, macht dieser " "Arm das Kopieren minderwertig." -#: src/internals/memory-model.md:102 +#: src/internals/memory-model.md:95 msgid "3. Registered runtime root scanners" msgstr "3. Registrierte Runtime-Root-Scanner" -#: src/internals/memory-model.md:104 +#: src/internals/memory-model.md:97 msgid "" "Some roots live in the runtime itself, not in user code: pending Promises, " "timer callbacks, exception state, async-context stacks, shape caches, " @@ -79228,11 +79051,11 @@ msgstr "" "gc_runtime_root_holders.py` zählt die Halter auf und verweigert nicht " "klassifizierte oder veraltete Inventareinträge." -#: src/internals/memory-model.md:110 +#: src/internals/memory-model.md:103 msgid "Generational behaviour" msgstr "Generationales Verhalten" -#: src/internals/memory-model.md:112 +#: src/internals/memory-model.md:105 msgid "" "Most JS allocations die young — object literals in a loop body, short-lived " "closures, intermediate strings. A generational collector exploits this by " @@ -79243,7 +79066,7 @@ msgstr "" "Collector nutzt dies aus, indem er die Nursery häufig und die Old Gen selten " "sammelt." -#: src/internals/memory-model.md:114 +#: src/internals/memory-model.md:107 msgid "" "Aging is recorded two different ways, because the two minor paths need " "different things from it." @@ -79251,30 +79074,20 @@ msgstr "" "Altern wird zwei verschiedene Wege aufgezeichnet, weil die beiden kleinen " "Pfade verschiedene Dinge davon benötigen." -#: src/internals/memory-model.md:117 -msgid "" -"**The non-copying minor** uses two flag bits in `gc_flags` (`crates/perry-" -"runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` on the first minor an " -"object survives, `GC_FLAG_TENURED` on the second, at which point the object " -"is logically promoted. The two-bit scheme avoids a counter field in the " -"header, and a tenured object initially stays physically where it is — that " -"promotion is a flag flip, not a copy. Whether such objects are then " -"evacuated into `OLD_ARENA` (with every reference rewritten) is a policy " -"decision taken per cycle from nursery/RSS pressure and measured movable " -"candidates, and it requires generated write barriers to be active." -msgstr "" -"**Der nicht-kopierende Minderjährige** verwendet zwei Flag-Bits in " -"`gc_flags` (`crates/perry-runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` " -"bei der ersten Moll überlebt ein Objekt, `GC_FLAG_TENURED` bei der zweiten, " -"an dem Punkt, an dem das Objekt logisch gefördert wird. Das Zwei-Bit-Schema " -"vermeidet ein Gegenfeld im Header, und ein Tenured-Objekt bleibt zunächst " -"physisch dort, wo es ist – dass Promotion ein Flag Flip ist, nicht eine " -"Kopie. Ob solche Objekte dann in `OLD_ARENA` evakuiert werden (mit jeder " -"Referenz neu geschrieben) ist eine politische Entscheidung, die pro Zyklus " -"von nursery/RSS Druck und gemessen beweglichen Kandidaten getroffen wird, " -"und es erfordert generierte Schreibbarrieren aktiv zu sein." +#: src/internals/memory-model.md:110 +msgid "" +"The non-copying minor records survival in `gc_flags`: " +"`GC_FLAG_HAS_SURVIVED`, then `GC_FLAG_TENURED`. Logical promotion does not " +"itself move the object. Evacuation requires active barriers and rewritable " +"roots; budgeted low-pause cycles remain non-moving." +msgstr "" +"Die nicht kopierende Minor-Sammlung erfasst Überleben in `gc_flags`: " +"zunächst `GC_FLAG_HAS_SURVIVED`, dann `GC_FLAG_TENURED`. Logische Promotion " +"verschiebt das Objekt nicht. Evakuierung benötigt aktive Barrieren und " +"umschreibbare Roots; budgetierte Zyklen mit kurzen Pausen bleiben " +"unbeweglich." -#: src/internals/memory-model.md:127 +#: src/internals/memory-model.md:112 msgid "" "**The copying minor** — the default nursery path — stores an exact short age " "in the header instead, and its promotion threshold is _adaptive_: a survivor-" @@ -79293,7 +79106,7 @@ msgstr "" "festen `PROMOTION_AGE` und keinen Knopf; `crates/perry-runtime/src/gc/" "tenuring.rs` ist die Definition." -#: src/internals/memory-model.md:135 +#: src/internals/memory-model.md:120 msgid "" "When a copying minor's whole young generation measures (near-)entirely live " "it does not copy at all: the blocks change generation in place. The current " @@ -79307,11 +79120,11 @@ msgstr "" "Footprint-Budgets ab — [Altern, Beförderung und wo ein Objekt geboren wird]" "(garbage-collector.md#aging-promotion-and-where-an-object-is-born)." -#: src/internals/memory-model.md:140 +#: src/internals/memory-model.md:125 msgid "Write barriers and the remembered set" msgstr "Write Barriers und das Remembered Set" -#: src/internals/memory-model.md:142 +#: src/internals/memory-model.md:127 msgid "" "Generational collectors have one fundamental problem: if an old-gen object " "points to a young-gen object, a minor GC (which only traces the nursery) " @@ -79322,7 +79135,7 @@ msgstr "" "Nursery verfolgt) von diesem Zeiger wissen, sonst gibt er ein lebendiges " "Objekt frei." -#: src/internals/memory-model.md:144 +#: src/internals/memory-model.md:129 msgid "" "The fix is a **write barrier**: every time a pointer field is written, the " "runtime checks \"is this old → young?\" and, if so, records the parent in a " @@ -79334,7 +79147,7 @@ msgstr "" "ja, trägt er den Elternteil in einem **Remembered Set** ein. Minor-GC-Läufe " "behandeln Remembered-Set-Einträge als zusätzliche Roots." -#: src/internals/memory-model.md:146 +#: src/internals/memory-model.md:131 msgid "" "In Perry, codegen emits write-barrier calls by default so copied minor GC " "and evacuation can rely on exact remembered-set data. Set " @@ -79350,25 +79163,25 @@ msgstr "" "exakte Helferbarrieren. Generationale Minderjährige fallen dann auf volle " "Mark-Sweep zurück, anstatt einem leeren, erinnerten Satz zu vertrauen." -#: src/internals/memory-model.md:152 +#: src/internals/memory-model.md:137 msgid "Triggers and tuning" msgstr "Trigger und Tuning" -#: src/internals/memory-model.md:154 +#: src/internals/memory-model.md:139 msgid "`gc_check_trigger` in `gc/policy.rs` responds to four signal families:" msgstr "`gc_check_trigger` in `gc/policy.rs` reagiert auf vier Signalfamilien:" -#: src/internals/memory-model.md:156 +#: src/internals/memory-model.md:141 msgid "**Nursery pressure** — allocation growth and the adaptive nursery cap." msgstr "" "**Kinderhochdruck** — Verteilung des Wachstums und der adaptiven " "Kinderkrippe." -#: src/internals/memory-model.md:157 +#: src/internals/memory-model.md:142 msgid "**Malloc count pressure** — too many separately tracked allocations." msgstr "**Malloc-Druckzahl** — zu viele getrennt verfolgte Zuweisungen." -#: src/internals/memory-model.md:158 +#: src/internals/memory-model.md:143 msgid "" "**Major pacing** — unreclaimed post-collection bytes outgrow the live " "baseline." @@ -79376,7 +79189,7 @@ msgstr "" "**Großes Tempo** — die nicht zurückgeforderten Nacherhebungsbytes über dem " "Lebend-Basiswert liegen." -#: src/internals/memory-model.md:159 +#: src/internals/memory-model.md:144 msgid "" "**Explicit/host requests** — user `gc()` and warning/critical OS memory " "pressure." @@ -79384,7 +79197,7 @@ msgstr "" "**Explicit/host Anfragen** — Benutzer `gc()` und warning/critical OS " "Speicherdruck." -#: src/internals/memory-model.md:161 +#: src/internals/memory-model.md:146 msgid "" "Device/container budgets scale trigger and reclaim ceilings down. Released " "blocks are reused thread-locally under a process-wide byte cap before " @@ -79396,15 +79209,15 @@ msgstr "" "Kappe wiederverwendet, bevor der Allokator zurückkehrt; siehe die aktuelle " "GC-Seite für die Druckstufen und das Cap- und Drain-Verhalten des Pools." -#: src/internals/memory-model.md:166 +#: src/internals/memory-model.md:151 msgid "Escape hatches and diagnostics" msgstr "Escape Hatches und Diagnose" -#: src/internals/memory-model.md:170 +#: src/internals/memory-model.md:155 msgid "`PERRY_GEN_GC=0` / `off` / `false`" msgstr "`PERRY_GEN_GC=0` / `off` / `false`" -#: src/internals/memory-model.md:170 +#: src/internals/memory-model.md:155 msgid "" "Disable generational mode; fall back to full mark-sweep (intended for " "bisection only)." @@ -79412,25 +79225,26 @@ msgstr "" "Generationalen Modus deaktivieren; auf vollständiges Mark-Sweep zurückfallen " "(nur für Bisection vorgesehen)." -#: src/internals/memory-model.md:171 +#: src/internals/memory-model.md:156 msgid "`PERRY_GC_FORCE_EVACUATE=1`" msgstr "`PERRY_GC_FORCE_EVACUATE=1`" -#: src/internals/memory-model.md:171 +#: src/internals/memory-model.md:156 msgid "" -"With generated write barriers active and policy evacuation allowed, stress-" -"copy every marked non-pinned nursery object instead of only tenured " -"survivors." +"Stress evacuation of eligible live nursery objects; requires active barriers" +" and movable roots. Explicit `gc()` runs an evacuating minor before its full" +" sweep under this control." msgstr "" -"Wenn generierte Write Barriers aktiv und Policy-Evakuierung erlaubt sind, " -"wird jedes markierte, nicht angepinnte Nursery-Objekt per Stress-Copy " -"kopiert, anstatt nur tenurierte Überlebende." +"Erzwingt zur Belastungsprüfung die Evakuierung geeigneter lebender Nursery-" +"Objekte; erfordert aktive Barrieren und verschiebbare Roots. Explizites " +"`gc()` führt dabei vor dem vollständigen Sweep eine evakuierende Minor-" +"Sammlung aus." -#: src/internals/memory-model.md:172 +#: src/internals/memory-model.md:157 msgid "`PERRY_GC_VERIFY_EVACUATION=1`" msgstr "`PERRY_GC_VERIFY_EVACUATION=1`" -#: src/internals/memory-model.md:172 +#: src/internals/memory-model.md:157 msgid "" "After an evacuation that actually forwards objects, panic if any mutable " "live slot still points at a forwarded nursery object after rewrite." @@ -79439,11 +79253,11 @@ msgstr "" "Panic ausgelöst, wenn nach dem Umschreiben ein veränderbarer Live-Slot noch " "auf ein weitergeleitetes Nursery-Objekt zeigt." -#: src/internals/memory-model.md:173 +#: src/internals/memory-model.md:158 msgid "`PERRY_WRITE_BARRIERS=0` / `off` / `false`" msgstr "`PERRY_WRITE_BARRIERS=0` / `off` / `false`" -#: src/internals/memory-model.md:173 +#: src/internals/memory-model.md:158 msgid "" "Disable codegen-emitted write barriers at compile time and runtime exact " "helper barriers at runtime for benchmark/debug bisection. Unset, `=1`, " @@ -79453,11 +79267,11 @@ msgstr "" "runtime-genaue Helper-Barriers zur Laufzeit für Benchmark-/Debug-Bisection. " "Nicht gesetzt, `=1`, `=on` und `=true` halten Barriers aktiviert." -#: src/internals/memory-model.md:174 src/internals/garbage-collector.md:413 +#: src/internals/memory-model.md:159 src/internals/garbage-collector.md:409 msgid "`PERRY_GC_DIAG=1`" msgstr "`PERRY_GC_DIAG=1`" -#: src/internals/memory-model.md:174 +#: src/internals/memory-model.md:159 msgid "" "Print per-cycle diagnostics, including one evacuation-policy line for cycles " "where evacuation was considered and for `barriers_inactive` skips." @@ -79466,11 +79280,11 @@ msgstr "" "Evakuierungsrichtlinie für Zyklen, bei denen Evakuierung erwogen wurde, " "sowie für `barriers_inactive`-Auslassungen." -#: src/internals/memory-model.md:176 +#: src/internals/memory-model.md:161 msgid "Rooting-bug instruments" msgstr "Rooting-Bug Instrumente" -#: src/internals/memory-model.md:178 +#: src/internals/memory-model.md:163 msgid "" "A value that is live but not rooted across a collection point leaves nothing " "behind at collection time — there is literally nothing for the collector to " @@ -79489,11 +79303,11 @@ msgstr "" "um diese Detektionslatenz zu zerbrechen. **Alle sind Standard-off und inert, " "wenn aus.**" -#: src/internals/memory-model.md:187 +#: src/internals/memory-model.md:172 msgid "`PERRY_GC_PROTECT_FROMSPACE=1`" msgstr "`PERRY_GC_PROTECT_FROMSPACE=1`" -#: src/internals/memory-model.md:187 +#: src/internals/memory-model.md:172 msgid "" "After an **evacuating (copying) minor**, do not recycle from-space. Retired " "Eden and active-survivor blocks are detached into a bounded quarantine, " @@ -79518,11 +79332,11 @@ msgstr "" "wieder zurück – eine Kerndatei oder Debugger sieht noch die eigentliche " "Absturzstelle." -#: src/internals/memory-model.md:188 +#: src/internals/memory-model.md:173 msgid "`PERRY_GC_PROTECT_FROMSPACE=poison`" msgstr "`PERRY_GC_PROTECT_FROMSPACE=poison`" -#: src/internals/memory-model.md:188 +#: src/internals/memory-model.md:173 msgid "" "As above without `mprotect`: poison only. Use where a fault is unwanted, or " "for the sub-page block edges `mprotect` cannot cover (those are always " @@ -79532,11 +79346,11 @@ msgstr "" "unerwünschte ist, oder für die Unterseite Blockkanten `mprotect` nicht " "decken (die sind immer Gift gefüllt und separat gezählt)." -#: src/internals/memory-model.md:189 +#: src/internals/memory-model.md:174 msgid "`PERRY_GC_PROTECT_FROMSPACE_DEPTH=N`" msgstr "`PERRY_GC_PROTECT_FROMSPACE_DEPTH=N`" -#: src/internals/memory-model.md:189 +#: src/internals/memory-model.md:174 msgid "" "How many retired page-sets stay quarantined (default `4`, minimum `1`). " "Expired sets are restored to read/write and **recycled back into Eden**, " @@ -79550,11 +79364,11 @@ msgstr "" "Standfußabdruck wird durch `N × from-space bytes` begrenzt und keine " "`mprotect`'d-Seite wird dem Systemallokator jemals übergeben." -#: src/internals/memory-model.md:190 +#: src/internals/memory-model.md:175 msgid "`PERRY_GC_FROMSPACE_SCAN_ABORT=1`" msgstr "`PERRY_GC_FROMSPACE_SCAN_ABORT=1`" -#: src/internals/memory-model.md:190 +#: src/internals/memory-model.md:175 msgid "" "Abort on the **first** offending slot the whole-heap from-space scan finds, " "printing slot, holder, target (including the target's `obj_type`) and a " @@ -79566,11 +79380,11 @@ msgstr "" "und ein Sammler-Backtrace. Jetzt impliziert `PERRY_GC_FROMSPACE_SCAN=1`; " "zuvor war es still inert auf seine eigene." -#: src/internals/memory-model.md:191 +#: src/internals/memory-model.md:176 msgid "`PERRY_GC_SCHEDULE_SEED=`" msgstr "`PERRY_GC_SCHEDULE_SEED=`" -#: src/internals/memory-model.md:191 +#: src/internals/memory-model.md:176 msgid "" "**Seeded GC-schedule fuzzing** — when nursery pressure is not due, add a " "minor collection at a handled safepoint when a deterministic pseudo-random " @@ -79602,11 +79416,11 @@ msgstr "" "Samen 0. Komposiert mit den beiden oben; diese Paarung ist, was einen " "Wurzelfehler in einen sofortigen präzisen Fehler verwandelt." -#: src/internals/memory-model.md:192 +#: src/internals/memory-model.md:177 msgid "`PERRY_GC_SCHEDULE_RATE=<0..1>`" msgstr "`PERRY_GC_SCHEDULE_RATE=<0..1>`" -#: src/internals/memory-model.md:192 +#: src/internals/memory-model.md:177 msgid "" "Expected fraction of eligible handled safepoints that receive an " "_additional_ schedule-triggered collection (default `0.05`). Inert without a " @@ -79624,7 +79438,7 @@ msgstr "" "Punkt, an dem der Samen aufhört, wichtig zu sein, weil jedes Ordinal " "ausgewählt ist. Außer-Bereich Werte Klemme." -#: src/internals/memory-model.md:194 +#: src/internals/memory-model.md:179 msgid "" "These instruments have explicit caveats, because each has burned a prior " "investigation:" @@ -79632,7 +79446,7 @@ msgstr "" "Diese Instrumente haben explizite Vorbehalte, weil jedes eine vorherige " "Untersuchung verbrannt hat:" -#: src/internals/memory-model.md:197 +#: src/internals/memory-model.md:182 msgid "" "`PERRY_GC_PROTECT_FROMSPACE` gates **only** the copying minor's from-space " "reset. A run with the knob on and zero copying minors protects nothing. " @@ -79644,7 +79458,7 @@ msgstr "" "Kopieren von Minderjährigen schützt nichts. Überprüfen Sie eine `[gc-" "fromspace-protect] retired_set=#N`-Linie unter `PERRY_GC_DIAG=1`." -#: src/internals/memory-model.md:200 +#: src/internals/memory-model.md:185 #, fuzzy msgid "" "**Depth is the knob to raise when a suspected bug does not fault.** A stale " @@ -79678,7 +79492,7 @@ msgstr "" "Faustregel: Tiefe ≥ die Anzahl der sicheren Punkte, die der Verdächtige " "überlebt." -#: src/internals/memory-model.md:213 +#: src/internals/memory-model.md:198 msgid "" "`PERRY_GC_SCHEDULE_SEED` cannot select loop back-edge polls that codegen " "never produced. Those are a **compile-time** property " @@ -79705,7 +79519,7 @@ msgstr "" "Zusammenfassung Zeile, aber kein hartes Urteil (ein spärliches Saatgut " "legitim zwingt nichts ist kein gebrochenes Instrument)." -#: src/internals/memory-model.md:223 +#: src/internals/memory-model.md:208 msgid "" "**`PERRY_GC_SCHEDULE_SEED`'s determinism is per-thread, and that is the " "honest scope.** The safepoint counter is thread-local: no wall clock, no " @@ -79730,7 +79544,7 @@ msgstr "" "abhängig von Interleaving machen. Melden Sie den Fall, den Sie gemessen " "haben." -#: src/internals/memory-model.md:232 +#: src/internals/memory-model.md:217 msgid "" "**A clean sweep means nothing without a safepoint count.** The mode prints " "`[gc-schedule] done: seed=… safepoints=… scheduled_collections=…` at exit, " @@ -79746,7 +79560,7 @@ msgstr "" "Ursache ist ein Binär ohne `PERRY_GC_MOVING_LOOP_POLLS=1` kompiliert, die " "keine In-Loop-Safepoints für einen Zeitplan zu wählen hat." -#: src/internals/memory-model.md:238 +#: src/internals/memory-model.md:223 msgid "" "**Page protection is Unix-only.** `mprotect` / `sigaction` / `sysconf` are " "not exposed by the `libc` crate on `x86_64-pc-windows-msvc`, a target `perry-" @@ -79761,11 +79575,11 @@ msgstr "" "`bytes_protected` bleibt `0`, während `bytes_poisoned` den gesamten " "pensionierten Bereich zählt." -#: src/internals/memory-model.md:244 +#: src/internals/memory-model.md:229 msgid "Why this design" msgstr "Warum dieses Design" -#: src/internals/memory-model.md:246 +#: src/internals/memory-model.md:231 msgid "" "The combination—NaN-boxing, per-thread arenas, target-aware precise roots, " "registered runtime scanners, barriers, and generational aging—is what lets " @@ -79776,23 +79590,21 @@ msgstr "" "was Perry durch LLVM gehen lässt und trotzdem einen bewegten verwalteten " "Haufen laufen lässt." -#: src/internals/memory-model.md:250 +#: src/internals/memory-model.md:235 msgid "" -"Going to native code does not preclude having a GC. It means the collector's " -"relationship with compiled code is mediated by an ABI and the selected root " -"map; the linked runtime remains a real tracing collector. There is nothing " -"reference-counted at runtime." +"Going to native code does not preclude having a GC. It means the collector's" +" relationship with compiled code is mediated by an ABI and the selected root" +" map; the linked runtime remains a real tracing collector." msgstr "" -"Zum nativen Code zu gehen, schließt nicht aus, dass ein GC. Das bedeutet, " -"dass die Beziehung des Kollektors zu kompiliertem Code durch eine ABI und " -"die ausgewählte Root Map vermittelt wird; die verknüpfte Laufzeit bleibt ein " -"echter Tracing Kollektor. Zur Laufzeit gibt es keine Referenzzählung." +"Nativer Code schließt einen GC nicht aus. Die Zusammenarbeit zwischen " +"Kollektor und kompiliertem Code erfolgt über eine ABI und die gewählte " +"Rootmap; die eingebundene Laufzeit bleibt ein echter Tracing-Kollektor." -#: src/internals/memory-model.md:255 +#: src/internals/memory-model.md:239 msgid "Profiling Perry's memory on macOS" msgstr "Profilierung des Speichers von Perry auf macOS" -#: src/internals/memory-model.md:257 +#: src/internals/memory-model.md:241 msgid "" "Two things to know before reading `vmmap`, Instruments' VM Tracker, or " "`footprint` output for a Perry binary:" @@ -79800,7 +79612,7 @@ msgstr "" "Zwei Dinge zu wissen, bevor Sie `vmmap`, Instruments' VM Tracker oder " "`footprint` Ausgang für eine Perry Binär lesen:" -#: src/internals/memory-model.md:260 +#: src/internals/memory-model.md:244 msgid "" "**The heap does not show up under `MALLOC_*`.** Perry routes all runtime " "allocation through mimalloc (`#[global_allocator]`, issue #62), whose " @@ -79812,7 +79624,7 @@ msgstr "" "deren Mappings als ihre eigenen anonymen Regionen erscheinen, nicht in den " "Systemmalloc-Zonen." -#: src/internals/memory-model.md:264 +#: src/internals/memory-model.md:248 msgid "" "**Those regions used to render as `IOAccelerator`** — i.e. GPU driver memory " "— because mimalloc tags its mappings with VM tag 100, which macOS tooling " @@ -79830,7 +79642,7 @@ msgstr "" "`MIMALLOC_OS_TAG=`, um das Tag selbst zu steuern — die Laufzeit verzögert " "sich auf eine explizite Env-Einstellung." -#: src/internals/memory-model.md:272 +#: src/internals/memory-model.md:256 msgid "" "The retag runs from a `__DATA,__mod_init_func` constructor (`perry-runtime/" "src/mimalloc_os_tag.rs`), _not_ from `js_gc_init`, and that placement is " @@ -79852,7 +79664,7 @@ msgstr "" "`main`. . Auf einem Bau zwischen diesen beiden, erwarten ~all des Haufens " "als `IOAccelerator` und ein Token `Memory Tag 240` Region." -#: src/internals/memory-model.md:282 +#: src/internals/memory-model.md:266 msgid "" "So: large `IOAccelerator` regions on a current build are a _bug_, not a " "documentation caveat — most likely the module initializer was dropped from " @@ -79869,7 +79681,7 @@ msgstr "" "`mi_option_get(mi_option_os_tag)` auch im defekten Fall 240 meldet, so dass " "es keine Diagnose ist." -#: src/internals/memory-model.md:289 +#: src/internals/memory-model.md:273 msgid "" "Also note that mimalloc purges freed memory with `MADV_FREE`\\-style advice: " "macOS keeps such pages counted in RSS and `phys_footprint` until memory " @@ -79881,7 +79693,7 @@ msgstr "" "Speicherdruck gezählt, so Überschrift RSS-Zahlen überschreiben, was der " "Prozess tatsächlich unter Druck halten würde." -#: src/internals/memory-model.md:294 +#: src/internals/memory-model.md:278 msgid "" "**`__DATA` dirty is not the inline caches any more.** Every property-access " "site used to own a 96-byte `[12 x i64]` cache global — 25 MB of `__bss` on a " @@ -79895,11 +79707,11 @@ msgid "" "that reports `0` there executed no property site that could prime." msgstr "" -#: src/internals/memory-model.md:305 +#: src/internals/memory-model.md:289 msgid "Source map" msgstr "Quellzuordnung" -#: src/internals/memory-model.md:307 +#: src/internals/memory-model.md:291 msgid "" "Paths, not line numbers: a line number is a claim nothing re-derives, and " "every row of this table once pointed into a `gc.rs` that no longer exists." @@ -79908,183 +79720,186 @@ msgstr "" "wiedergibt, und jede Zeile dieser Tabelle hat einmal in eine `gc.rs` " "gezeigt, die nicht mehr existiert." -#: src/internals/memory-model.md:310 +#: src/internals/memory-model.md:294 msgid "Topic" msgstr "Thema" -#: src/internals/memory-model.md:310 +#: src/internals/memory-model.md:294 msgid "Symbol to look for" msgstr "Symbol für die Suche nach" -#: src/internals/memory-model.md:312 +#: src/internals/memory-model.md:296 msgid "NaN-boxing tags" msgstr "NaN-Box-Tags" -#: src/internals/memory-model.md:312 -msgid "`crates/perry-runtime/src/value/tags.rs`" +#: src/internals/memory-model.md:296 +#, fuzzy +msgid "`crates/perry-runtime/src/value/tags.rs`, `crates/perry-abi/src/lib.rs`" msgstr "`crates/perry-runtime/src/value/tags.rs`" -#: src/internals/memory-model.md:312 -msgid "`POINTER_TAG`, `STRING_TAG`" +#: src/internals/memory-model.md:296 +#, fuzzy +msgid "`POINTER_TAG`, `STRING_TAG`, `HANDLE_TAG`" msgstr "`POINTER_TAG`, `STRING_TAG`" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`GcHeader`, type/flag constants" msgstr "`GcHeader`, Typ-/Flag-Konstanten" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`crates/perry-runtime/src/gc/types.rs`" msgstr "`crates/perry-runtime/src/gc/types.rs`" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`GcHeader`" msgstr "`GcHeader`" -#: src/internals/memory-model.md:314 +#: src/internals/memory-model.md:298 msgid "`gc_malloc`" msgstr "`gc_malloc`" -#: src/internals/memory-model.md:314 +#: src/internals/memory-model.md:298 msgid "`crates/perry-runtime/src/gc/malloc.rs`" msgstr "`crates/perry-runtime/src/gc/malloc.rs`" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "Shadow frames (fallback root lowering)" msgstr "Schatten-Rahmen (Fallback-Root-Absenkung)" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "`crates/perry-runtime/src/gc/roots/shadow_stack.rs`" msgstr "`crates/perry-runtime/src/gc/roots/shadow_stack.rs`" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "`js_shadow_frame_push`" msgstr "`js_shadow_frame_push`" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "Registered root scanners" msgstr "Registrierte Root-Scanner" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "`crates/perry-runtime/src/gc/roots.rs`" msgstr "`crates/perry-runtime/src/gc/roots.rs`" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "`gc_register_mutable_root_scanner`" msgstr "`gc_register_mutable_root_scanner`" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "Copying minor" msgstr "Minderjährige kopieren" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "`crates/perry-runtime/src/gc/copying.rs`" msgstr "`crates/perry-runtime/src/gc/copying.rs`" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "`move_young`" msgstr "`move_young`" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "Whole-block in-place promotion" msgstr "Ganzer Block an Ort und Stelle Beförderung" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "`crates/perry-runtime/src/arena/promote.rs`" msgstr "`crates/perry-runtime/src/arena/promote.rs`" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "`finish_in_place_promotion`" msgstr "`finish_in_place_promotion`" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "Promotion policy" msgstr "Absatzförderungspolitik" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "`crates/perry-runtime/src/gc/promote_in_place.rs`" msgstr "`crates/perry-runtime/src/gc/promote_in_place.rs`" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "`PROMOTE_SURVIVAL_THRESHOLD_PERMILLE`" msgstr "`PROMOTE_SURVIVAL_THRESHOLD_PERMILLE`" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "Adaptive tenuring threshold" msgstr "Anpassungsschwelle" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "`crates/perry-runtime/src/gc/tenuring.rs`" msgstr "`crates/perry-runtime/src/gc/tenuring.rs`" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "`tenuring_survivals`" msgstr "`tenuring_survivals`" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "Write barriers" msgstr "Schreibbarrieren" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "`crates/perry-runtime/src/gc/barrier_store.rs`" msgstr "`crates/perry-runtime/src/gc/barrier_store.rs`" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "`runtime_write_barrier_slot`" msgstr "`runtime_write_barrier_slot`" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "Explicit pinning" msgstr "Explizites Anstecken" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "`crates/perry-runtime/src/gc/pin.rs`" msgstr "`crates/perry-runtime/src/gc/pin.rs`" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "`pin_object`" msgstr "`pin_object`" -#: src/internals/memory-model.md:323 +#: src/internals/memory-model.md:307 msgid "Conservative-pin predicate" msgstr "Prädikat der Konservativen" -#: src/internals/memory-model.md:323 -msgid "`crates/perry-runtime/src/gc/verify.rs`" +#: src/internals/memory-model.md:307 +#, fuzzy +msgid "`crates/perry-runtime/src/gc/rewrite.rs`" msgstr "`crates/perry-runtime/src/gc/verify.rs`" -#: src/internals/memory-model.md:323 +#: src/internals/memory-model.md:307 msgid "`is_conservatively_pinned`" msgstr "`is_conservatively_pinned`" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "Collection triggers and pacing" msgstr "Sammelauslöser und Tempos" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "`crates/perry-runtime/src/gc/policy.rs`" msgstr "`crates/perry-runtime/src/gc/policy.rs`" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "`gc_check_trigger`" msgstr "`gc_check_trigger`" -#: src/internals/memory-model.md:325 +#: src/internals/memory-model.md:309 msgid "Current operations page" msgstr "Seite der laufenden Operationen" -#: src/internals/memory-model.md:325 +#: src/internals/memory-model.md:309 msgid "`docs/src/internals/garbage-collector.md`" msgstr "`docs/src/internals/garbage-collector.md`" -#: src/internals/memory-model.md:326 +#: src/internals/memory-model.md:310 msgid "Design plan (historical)" msgstr "Entwurfsplan (historisch)" -#: src/internals/memory-model.md:326 +#: src/internals/memory-model.md:310 msgid "`docs/generational-gc-plan.md`" msgstr "`docs/generational-gc-plan.md`" -#: src/internals/memory-model.md:340 +#: src/internals/memory-model.md:324 msgid "" "Those rows are not decorative: each is bound to a `gc-symbol` marker above " "and re-derived by `scripts/check_gc_doc_claims.py`, so a rename that leaves " @@ -82070,40 +81885,15 @@ msgstr "Müllsammler: aktuelle Architektur und Betrieb" #: src/internals/garbage-collector.md:3 msgid "" -"**Current as of 2026-08-12.** This is the source of truth for the collector " -"that ships. The [generational plan](https://github.com/PerryTS/perry/blob/" -"main/docs/generational-gc-plan.md) and [statepoint experiment](https://" -"github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment.md) are " -"chronological evidence; their opening decisions describe the date they were " -"written, not today's defaults." -msgstr "" -"**Aktuell ab 2026-08-12.** Dies ist die Quelle der Wahrheit für den Sammler, " -"der Schiffe. Die [Erzeugungsplan](https://github.com/PerryTS/perry/blob/main/" -"docs/generational-gc-plan.md) und [Statepoint-Experiment](https://github.com/" -"PerryTS/perry/blob/main/docs/statepoint-gc-experiment.md) sind " -"chronologische Belege; ihre Eröffnungsentscheidungen beschreiben das Datum, " -"an dem sie geschrieben wurden, nicht die heutigen Voreinstellungen." - -#: src/internals/garbage-collector.md:9 -msgid "" -"This page deliberately names **no issue numbers**. A sentence of the form " -"\"that phase is still unsliced (see the tracker)\" becomes false the moment " -"the tracker closes, and nothing in the tree notices — which is exactly how " -"three paragraphs here went stale inside one landing train. State what the " -"code does; the tracker's state is not in this repository. Every number below " -"is bound to the constant it came from by a `gc-fact` marker and re-derived " -"by `scripts/check_gc_doc_claims.py`, which `lint` runs." -msgstr "" -"Diese Seite nennt absichtlich **keine Ausgabenummern**. Ein Satz der Form " -"\"die Phase ist noch ungeschnitten (siehe den Tracker)\" wird falsch, sobald " -"der Tracker schließt, und nichts im Baum bemerkt – genau so sind hier drei " -"Absätze in einem Landezug verharren. Geben Sie an, was der Code tut; der " -"Status des Trackers ist nicht in diesem Projektarchiv. Jede Zahl unten ist " -"an die Konstante gebunden, die sie von einem `gc-fact`-Marker stammte und " -"von `scripts/check_gc_doc_claims.py` wieder abgeleitet wurde, welches `lint` " -"läuft." +"**Reviewed against the code on 2026-11-01.** Historical plans record earlier" +" designs. Source paths, `gc-fact` constants and `gc-symbol` checks below " +"bind the operational description to the implementation." +msgstr "" +"**Am 2026-11-01 mit dem Code abgeglichen.** Historische Pläne beschreiben " +"frühere Entwürfe. Quellpfade, `gc-fact`-Konstanten und `gc-symbol`-Prüfungen" +" verknüpfen die folgende Betriebsbeschreibung mit der Implementierung." -#: src/internals/garbage-collector.md:18 +#: src/internals/garbage-collector.md:5 msgid "" "Perry uses a per-thread, tracing generational collector. JavaScript values " "are NaN-boxed, allocations carry an 8-byte `GcHeader`, and each runtime " @@ -82119,22 +81909,22 @@ msgstr "" "perry-runtime/src/arena/`-Modulbäume; die Root-Reduktion der Code-Generation " "erfolgt in `crates/perry-codegen/src/codegen/` und `native_root_coverage/`." -#: src/internals/garbage-collector.md:25 +#: src/internals/garbage-collector.md:12 msgid "Collection paths" msgstr "Sammelpfade" -#: src/internals/garbage-collector.md:27 +#: src/internals/garbage-collector.md:14 #, fuzzy msgid "" "New GC-managed allocations normally enter nursery blocks of one aligned " "region (2 MiB on Linux, 1 MiB elsewhere)" msgstr "Neue GC-managed-Zuweisungen geben normalerweise 1 MiB-Kindergärten ein" -#: src/internals/garbage-collector.md:30 +#: src/internals/garbage-collector.md:17 msgid "A collection can take one of three paths:" msgstr "Eine Sammlung kann einen von drei Wegen nehmen:" -#: src/internals/garbage-collector.md:32 +#: src/internals/garbage-collector.md:19 msgid "" "**Copying minor.** At a precise safepoint, live young objects are copied, " "roots and heap slots are rewritten, and whole from-space blocks are reset. " @@ -82147,7 +81937,7 @@ msgstr "" "sind, werden in `OLD_ARENA` befördert. Dies ist der schnelle " "Kindergartenpfad, und es hat eine ganze Block-Variante unten beschrieben." -#: src/internals/garbage-collector.md:36 +#: src/internals/garbage-collector.md:23 msgid "" "**Non-moving minor/fallback.** When collection begins somewhere that cannot " "safely relocate every live reference, the nursery is marked and swept in " @@ -82160,7 +81950,7 @@ msgstr "" "einen nicht-bewegten Pfad. Dies ist der Pfad der flachen `HAS_SURVIVED` → " "`TENURED` Flaggenpaar altert Objekte auf." -#: src/internals/garbage-collector.md:40 +#: src/internals/garbage-collector.md:27 msgid "" "**Full mark-sweep.** Major pacing, critical host pressure, explicit full " "work, or `PERRY_GEN_GC=0` trace both generations and reclaim dead old " @@ -82170,7 +81960,7 @@ msgstr "" "Vollarbeit, oder `PERRY_GEN_GC=0` verfolgen beide Generationen und fordern " "tote alte Objekte sowie Kindergartenmüll zurück." -#: src/internals/garbage-collector.md:44 +#: src/internals/garbage-collector.md:31 msgid "" "**Block-granular reclamation in the full sweep.** A synchronous full sweep " "reclaims an arena block without entering it when the cycle's exact pointer " @@ -82184,7 +81974,7 @@ msgid "" "and bytes on each sweep's `[gc] blocks:` line." msgstr "" -#: src/internals/garbage-collector.md:57 +#: src/internals/garbage-collector.md:44 msgid "" "**The allocation-point trigger check.** `gc_check_trigger` runs on every " "`gc_malloc` and every arena block fill, and nearly always finds nothing due. " @@ -82201,13 +81991,13 @@ msgid "" "runtime/src/gc/trigger_watermark.rs`)." msgstr "" -#: src/internals/garbage-collector.md:72 +#: src/internals/garbage-collector.md:59 msgid "" "**Per-live-object cost of a synchronous full.** Three parts of a full scale " "with the live set, and each has a cheaper exact form:" msgstr "" -#: src/internals/garbage-collector.md:75 +#: src/internals/garbage-collector.md:62 msgid "" "_Membership._ The census answers \"is this address an arena object start?\" " "from one object-start bitmap per censused block (one bit per 8-byte " @@ -82216,7 +82006,7 @@ msgid "" "one bit test." msgstr "" -#: src/internals/garbage-collector.md:80 +#: src/internals/garbage-collector.md:67 msgid "" "_Remembered-set rebuild._ Full mark drains record live old parents' strong " "old→young and old→malloc slots through the same descriptor visit that marks " @@ -82226,14 +82016,14 @@ msgid "" "already traced. No second old-generation walk is needed." msgstr "" -#: src/internals/garbage-collector.md:89 +#: src/internals/garbage-collector.md:76 msgid "" "_Sweep page accounting._ Consecutive single-page old objects are summed and " "applied to their page's metadata once, before any page-index flush that " "could zero it." msgstr "" -#: src/internals/garbage-collector.md:95 +#: src/internals/garbage-collector.md:82 msgid "" "**Promoted-cohort fulls.** Old-reclaim pacing credits every promotion to its " "growth baseline, so old-gen garbage that a minor promoted and that died " @@ -82256,7 +82046,7 @@ msgid "" "`[gc-trigger]` lines and a `[gc-promoted-cohort]` line per cohort full." msgstr "" -#: src/internals/garbage-collector.md:118 +#: src/internals/garbage-collector.md:105 msgid "" "**One pass per block in a synchronous full.** An unbudgeted census and an " "unbudgeted sweep parse each arena block themselves instead of calling the " @@ -82270,7 +82060,7 @@ msgid "" "that is not a forwarding stub without queueing it." msgstr "" -#: src/internals/garbage-collector.md:136 +#: src/internals/garbage-collector.md:123 msgid "" "`PERRY_GC_SCAVENGE` is on by default and lets nursery pressure route to the " "direct minor. `PERRY_GC_SCAVENGE_NURSERY_MB` tunes its base high-water cap, " @@ -82280,24 +82070,24 @@ msgstr "" "Kinderzimmers zum direkten Moll. `PERRY_GC_SCAVENGE_NURSERY_MB` stimmt seine " "Basis High-Wasser-Kappe, 16 MiB standardmäßig" -#: src/internals/garbage-collector.md:140 +#: src/internals/garbage-collector.md:127 msgid "" "The effective cap is a survival-driven ladder over that base, counted in " "quarters of it" msgstr "" -#: src/internals/garbage-collector.md:143 +#: src/internals/garbage-collector.md:130 msgid "each thread starts at a quarter of the base" msgstr "" -#: src/internals/garbage-collector.md:145 +#: src/internals/garbage-collector.md:132 msgid "" "(4 MiB) and stays there while its minors find little alive, which keeps the " "young generation's footprint small. While survivor influx stays above 4% of " "the cap the ladder grows in debounced ×2 steps, up to 4× the base" msgstr "" -#: src/internals/garbage-collector.md:149 +#: src/internals/garbage-collector.md:136 msgid "" "(64 MiB), and it shrinks back while influx stays below 1%. On survivor-heavy " "and live-set-bound workloads copying survivors, not a minor's fixed cost, " @@ -82309,7 +82099,7 @@ msgid "" "falls back to full mark-sweep." msgstr "" -#: src/internals/garbage-collector.md:158 +#: src/internals/garbage-collector.md:145 msgid "" "Old-generation page defragmentation is implemented, including the mutable-" "root rewrite contract that relocating an old page requires, but it is **opt-" @@ -82329,11 +82119,11 @@ msgstr "" "beschränkt. Die Evakuierung der Kindergärten und die normale alte Generation " "sind in jeder Hinsicht unbeeindruckt." -#: src/internals/garbage-collector.md:166 +#: src/internals/garbage-collector.md:153 msgid "Aging, promotion, and where an object is born" msgstr "Altern, Beförderung und wo ein Objekt geboren wird" -#: src/internals/garbage-collector.md:168 +#: src/internals/garbage-collector.md:155 msgid "" "The copying minor's tenuring threshold is **adaptive, not fixed**: it is the " "largest number of survivals `S` whose projected survivor occupancy still " @@ -82344,7 +82134,7 @@ msgstr "" "immer noch der gewünschten Überlebensgröße entspricht, an eine Decke von 4 " "geklemmt" -#: src/internals/garbage-collector.md:172 +#: src/internals/garbage-collector.md:159 msgid "" "and it drops to 1 (promote on first copy) when a measured survival-rate lock " "shows the aging round is filtering nothing. The loop is always on and has no " @@ -82356,7 +82146,7 @@ msgstr "" "immer an und hat keinen Env-Knopf; `crates/perry-runtime/src/gc/tenuring.rs` " "ist seine Definition und gibt das feedback-Signal an, das es verwendet." -#: src/internals/garbage-collector.md:177 +#: src/internals/garbage-collector.md:164 msgid "" "While the first nursery-growth sample is awaiting its first survivor round, " "copying minors keep pointer-bearing parents young for one additional round. " @@ -82374,7 +82164,7 @@ msgid "" "early." msgstr "" -#: src/internals/garbage-collector.md:191 +#: src/internals/garbage-collector.md:178 msgid "" "**Whole-block in-place promotion.** When the previous cycle measured a young-" "survival ratio at or above 95%" @@ -82382,7 +82172,7 @@ msgstr "" "**Ganzer Block an Ort und Stelle Beförderung.** Wenn der vorherige Zyklus " "ein Jugend-Überlebens-Verhältnis von oder über 95% gemessen hat" -#: src/internals/garbage-collector.md:194 +#: src/internals/garbage-collector.md:181 msgid "" "the next copying minor relabels the young blocks as old-gen instead of " "evacuating them object by object: nothing moves, so nothing in the heap or " @@ -82397,7 +82187,7 @@ msgstr "" "die Entscheidung des nächsten Zyklus misst und nicht angenommen wird —, mit " "Ausnahme der Volllebigkeit bei oder über 98 %" -#: src/internals/garbage-collector.md:200 +#: src/internals/garbage-collector.md:187 msgid "" "where the trace itself is skipped and every object on the block is " "registered as live. Two budgets bound the retained garbage that costs: a " @@ -82407,11 +82197,11 @@ msgstr "" "registriert ist. Zwei Budgets banden den Restmüll, der kostet: eine laufende " "Obergrenze auf beförderten toten Bytes" -#: src/internals/garbage-collector.md:204 +#: src/internals/garbage-collector.md:191 msgid "and an untraced-promotion floor" msgstr "und ein nicht verkehrender Boden" -#: src/internals/garbage-collector.md:206 +#: src/internals/garbage-collector.md:193 msgid "" "that forces a measuring cycle before the assumption can run away. A full " "collection resets both. `PERRY_GC_PROMOTE_IN_PLACE=0` reverts to object-by-" @@ -82426,66 +82216,103 @@ msgstr "" "`PERRY_GC_SCHEDULE_SEED`) zu machen, schalten den Pfad direkt ab, anstatt " "still zu stoppen, ihr eigenes Thema auszuüben." -#: src/internals/garbage-collector.md:214 +#: src/internals/garbage-collector.md:201 msgid "" -"**Born-old thresholds are type-dependent.** A pointer-free allocation above " -"16 KiB" +"**Size selects storage, not necessarily age.** The large-object cutoffs are " +"16 KiB for pointer-free cells and 128 KiB for pointer-bearing cells. " +"Eligible leaves and containers enter dedicated non-moving young storage: a " +"minor traces their children, reclaims dead cells, or promotes survivors at " +"the same address. Types with external backing, move hooks, or other address-" +"stability contracts retain their specialized allocation policy." msgstr "" -"**Born-old Schwellen sind typabhängig.** Eine pointer-freie Zuweisung über " -"16 KiB" +"**Die Größe bestimmt den Speicherbereich, nicht unbedingt das Alter.** Die " +"Grenzen für große Objekte betragen 16 KiB für zeigerfreie und 128 KiB für " +"zeigerhaltige Zellen. Geeignete Blätter und Container erhalten gesonderten, " +"unbeweglichen jungen Speicher: Eine Minor-Sammlung verfolgt ihre Kinder, " +"gibt tote Zellen frei oder stuft Überlebende an derselben Adresse hoch. " +"Typen mit externem Speicher, Verschiebungs-Hooks oder anderen " +"Adressstabilitätsverträgen behalten ihre spezielle Allokationsstrategie." -#: src/internals/garbage-collector.md:217 -msgid "" -"is born in old-gen; a pointer-bearing one stays nursery-resident until 128 " -"KiB" -msgstr "" -"wird im alten Gen geboren; ein Zeiger tragender bleibt kinderheimisch bis " -"128 KiB" - -#: src/internals/garbage-collector.md:219 +#: src/internals/garbage-collector.md:206 msgid "" -"The split matters when reading a survival ratio: a flat threshold sends the " -"intermediate backing stores of a growing array straight to old-gen, so the " -"garbage they abandon never appears in the young generation at all." -msgstr "" -"Die Spaltung spielt beim Lesen eines Überlebensverhältnisses eine Rolle: " -"Eine flache Schwelle sendet die Zwischenspeicher einer wachsenden Array " -"direkt zu alten Genen, so dass der Müll, den sie aufgeben, in der jungen " -"Generation überhaupt nicht erscheint." - -#: src/internals/garbage-collector.md:223 +"Admission is derived from GC type descriptors in `crates/perry-" +"runtime/src/gc/types.rs`; allocation and collection live in `crates/perry-" +"runtime/src/arena/allocators.rs` and `crates/perry-" +"runtime/src/arena/young_large.rs`. Scoped JSON birth rules can keep eligible" +" storage in the ordinary nursery beyond the normal cutoff." +msgstr "" +"Die Zulassung folgt den GC-Typdeskriptoren in `crates/perry-" +"runtime/src/gc/types.rs`; Allokation und Sammlung liegen in `crates/perry-" +"runtime/src/arena/allocators.rs` und `crates/perry-" +"runtime/src/arena/young_large.rs`. Begrenzte JSON-Erzeugungsregeln können " +"geeigneten Speicher über die normale Grenze hinaus in der gewöhnlichen " +"Nursery halten." + +#: src/internals/garbage-collector.md:208 +msgid "" +"The inline nursery cursor carries a permission bound at " +"`InlineArenaState.size`. It is zero while allocate-black is active; the " +"physical block extent remains in `ArenaBlock.size`. Every birth-color " +"transition and cursor refill updates this same bound. A successful positive-" +"size bump therefore proves idle birth color for a complete unpublished " +"layout, while the refused path retains ordinary coloring and mark seeding. " +"Native roots still use LLVM statepoints." +msgstr "" +"Der Inline-Nursery-Cursor trägt in `InlineArenaState.size` eine " +"Berechtigungsgrenze. Während Allocate-Black ist sie null; die physische " +"Blockgröße bleibt in `ArenaBlock.size`. Jeder Wechsel der Erzeugungsfarbe " +"und jedes Nachfüllen aktualisiert dieselbe Grenze. Ein erfolgreicher " +"positiver Bump beweist daher die ruhende Erzeugungsfarbe für ein " +"vollständiges, unveröffentlichtes Layout; bei Ablehnung bleiben normale " +"Färbung und Markierungsinitialisierung erhalten. Native Roots verwenden " +"weiterhin LLVM-Statepoints." + +#: src/internals/garbage-collector.md:215 +msgid "" +"For compiler-owned descriptors uniquely named in the whole-image layout " +"plan, fixed births initialize the header with the immutable template word " +"from that same plan. Opaque or ambiguous descriptors keep their checked " +"runtime load; no runtime shape lookup or validity structure is introduced." +msgstr "" +"Für compilereigene Deskriptoren mit eindeutigem Namen im bildweiten " +"Layoutplan initialisieren feste Allokationen den Header mit dem " +"unveränderlichen Vorlagenwort dieses Plans. Undurchsichtige oder mehrdeutige" +" Deskriptoren behalten ihren geprüften Laufzeitladezugriff; es entsteht " +"weder eine Laufzeit-Shape-Suche noch eine Gültigkeitsstruktur." + +#: src/internals/garbage-collector.md:220 msgid "Roots, by target" msgstr "Wurzeln, nach Ziel" -#: src/internals/garbage-collector.md:225 +#: src/internals/garbage-collector.md:222 msgid "One root-set analysis feeds two lowerings:" msgstr "Eine Wurzel-Set-Analyse füttert zwei Absenkungen:" -#: src/internals/garbage-collector.md:227 +#: src/internals/garbage-collector.md:224 msgid "target" msgstr "target" -#: src/internals/garbage-collector.md:227 +#: src/internals/garbage-collector.md:224 msgid "shipped precise-root lowering" msgstr "präzise-Root-Absenkung ausgeliefert" -#: src/internals/garbage-collector.md:229 +#: src/internals/garbage-collector.md:226 msgid "64-bit AArch64/arm64 and x86-64, including x86-64 Windows" msgstr "64-Bit AArch64/arm64 und x86-64, einschließlich x86-64 Windows" -#: src/internals/garbage-collector.md:229 +#: src/internals/garbage-collector.md:226 msgid "LLVM RS4GC statepoints plus Perry's compact native stack map" msgstr "LLVM RS4GC-Statuspunkte plus Perrys kompakte native Stapelkarte" -#: src/internals/garbage-collector.md:230 +#: src/internals/garbage-collector.md:227 msgid "wasm32 WASI and ARM64 Windows" msgstr "" -#: src/internals/garbage-collector.md:230 +#: src/internals/garbage-collector.md:227 msgid "Perry shadow frames" msgstr "Perry Schattenrahmen" -#: src/internals/garbage-collector.md:232 +#: src/internals/garbage-collector.md:229 msgid "" "Rooting is selected by the target. Native platforms always use LLVM " "statepoints; environment controls and function-size estimates cannot select " @@ -82496,7 +82323,7 @@ msgid "" "rewrites those same locations before generated code reloads them." msgstr "" -#: src/internals/garbage-collector.md:240 +#: src/internals/garbage-collector.md:237 msgid "" "WASI uses platform shadow frames because it has no native frame walker or " "statepoint stack-map backend. ARM64 Windows lacks the required native " @@ -82504,7 +82331,7 @@ msgid "" "compiler emission rather than pretending its LP64 offsets work." msgstr "" -#: src/internals/garbage-collector.md:245 +#: src/internals/garbage-collector.md:242 msgid "" "Runtime-owned roots do not live in generated frames. Registered scanners " "visit module globals, pending async work, caches, registries, and other side " @@ -82519,7 +82346,7 @@ msgstr "" "`RuntimeHandleScope`/`RuntimeHandle` und müssen nach einem Aufruf, der " "sammeln kann, einen Handle erneut lesen." -#: src/internals/garbage-collector.md:251 +#: src/internals/garbage-collector.md:248 msgid "" "Root owners supply the representation. Generated globals and native " "statepoint homes keep JSValue words at rest; raw managed locals are boxed " @@ -82547,7 +82374,7 @@ msgid "" "the same producer contract." msgstr "" -#: src/internals/garbage-collector.md:274 +#: src/internals/garbage-collector.md:271 msgid "" "Legacy shadow-frame homes on WASI and ARM64 Windows can still mix raw " "pointers and JSValues. Their source kind retains the mixed decoder and " @@ -82558,7 +82385,7 @@ msgid "" "rather than validating every root that aliases it." msgstr "" -#: src/internals/garbage-collector.md:282 +#: src/internals/garbage-collector.md:279 msgid "" "The conservative native-stack scan is not part of the production default: " "`Auto` resolves to `SkipDisabled`. `PERRY_CONSERVATIVE_STACK_SCAN=full` is " @@ -82573,11 +82400,11 @@ msgstr "" "daher das Kopieren geringfügig nicht förderfähig; es ist nützlich Beweise, " "nicht ein zweites normales Rooting Backend." -#: src/internals/garbage-collector.md:288 +#: src/internals/garbage-collector.md:285 msgid "Barriers and weak references" msgstr "Barrieren und schwache Referenzen" -#: src/internals/garbage-collector.md:290 +#: src/internals/garbage-collector.md:287 msgid "" "Every old-to-young pointer publication must hit the remembered-set barrier. " "Codegen emits barriers for generated heap stores and runtime helpers perform " @@ -82590,7 +82417,7 @@ msgstr "" "Speicher. Eine kleine Spur erinnerte sich an alte Eltern, anstatt alle alten " "Gens zurückzuverfolgen." -#: src/internals/garbage-collector.md:295 +#: src/internals/garbage-collector.md:292 msgid "" "WeakRef, WeakMap, WeakSet, and FinalizationRegistry targets are excluded " "from the strong trace. Weak processing is **registry-scoped on every path**: " @@ -82611,11 +82438,11 @@ msgstr "" "anstatt O (arena) und ein budgetierter Zyklus kann zum Mutator zwischen " "Inhabern zurückkehren." -#: src/internals/garbage-collector.md:305 +#: src/internals/garbage-collector.md:302 msgid "Budgets, memory pressure, and released blocks" msgstr "Haushaltspläne, Gedächtnisdruck und freigegebene Blöcke" -#: src/internals/garbage-collector.md:307 +#: src/internals/garbage-collector.md:304 msgid "" "The collector derives a heap budget, in priority order, from " "`PERRY_GC_HEAP_LIMIT` (MiB), Apple embedded available-memory APIs, container " @@ -82629,15 +82456,15 @@ msgstr "" "Skala Trigger-Obergrenzen, Rückforderungsschwellen, Kinderruhe und RSS-" "Druckschwellen gesenkt; desktop/server-Defaults bleiben unverändert." -#: src/internals/garbage-collector.md:313 +#: src/internals/garbage-collector.md:310 msgid "Platform hosts call `js_gc_memory_pressure(level)`:" msgstr "Plattform-Hosts rufen `js_gc_memory_pressure(level)`:" -#: src/internals/garbage-collector.md:315 +#: src/internals/garbage-collector.md:312 msgid "warning (`1`) requests a prompt minor;" msgstr "warning (`1`) requests a prompt minor;" -#: src/internals/garbage-collector.md:316 +#: src/internals/garbage-collector.md:313 msgid "" "critical (`2+`) requests a full collection so old-gen garbage and idle arena " "blocks can be reclaimed;" @@ -82645,7 +82472,7 @@ msgstr "" "kritische (`2+`) verlangt eine vollständige Sammlung, so dass alte Müll-und " "Leerlauf-Arenenblöcke zurückgewonnen werden können;" -#: src/internals/garbage-collector.md:318 +#: src/internals/garbage-collector.md:315 msgid "" "if collection is unsafe, the request is made sticky and drains at the next " "precise safepoint/allocation check." @@ -82653,23 +82480,23 @@ msgstr "" "if collection is unsafe, the request is made sticky and drains at the next " "precise safepoint/allocation check." -#: src/internals/garbage-collector.md:321 +#: src/internals/garbage-collector.md:318 msgid "" -"Released 1 MiB blocks first enter a **per-thread LIFO reuse pool under a " -"process-wide byte cap**. The reuse order is thread-local; the budget is not " -"— a single global reservation is what bounds N live agents to one allowance " -"instead of N of them. The cap is derived by `gc_block_pool_cap_with_budget` " -"in `crates/perry-runtime/src/gc/heap_budget.rs`: 64 MiB" +"Released regular arena blocks first enter a **per-thread LIFO reuse pool " +"under a process-wide byte cap**. The reuse order is thread-local; the budget" +" is not — a single global reservation is what bounds N live agents to one " +"allowance instead of N of them. The cap is derived by " +"`gc_block_pool_cap_with_budget` in `crates/perry-" +"runtime/src/gc/heap_budget.rs`: 64 MiB" msgstr "" -"Veröffentlicht 1 MiB Blöcke zuerst geben Sie eine **LIFO-" -"Wiederverwendungspool pro Thread unter einer prozessweiten Byte-Cap**. Die " -"Reihenfolge der Wiederverwendung ist thread-local; das Budget ist nicht - " -"ein einziger globaler Vorbehalt ist, was N Live Agents an eine Beihilfe " -"anstelle von N von ihnen gebunden. Die Kappe wird von " -"`gc_block_pool_cap_with_budget` in `crates/perry-runtime/src/gc/heap_budget." -"rs` abgeleitet: 64 MiB" +"Freigegebene reguläre Arenablöcke gelangen zuerst in einen **threadlokalen " +"LIFO-Wiederverwendungspool mit prozessweitem Bytelimit**. Die Reihenfolge " +"ist threadlokal, das Budget nicht: Eine globale Reservierung begrenzt N " +"Agenten auf ein gemeinsames Kontingent. Die Grenze bestimmt " +"`gc_block_pool_cap_with_budget` in `crates/perry-" +"runtime/src/gc/heap_budget.rs`: 64 MiB" -#: src/internals/garbage-collector.md:327 +#: src/internals/garbage-collector.md:323 #, fuzzy msgid "" "on an unconstrained desktop/server process, scaled to one eighth of a device/" @@ -82690,7 +82517,7 @@ msgstr "" "unsafe/deferred Perioden und entleert den Pool, wenn die geschuldete " "Vollsammlung seine Arena-Reklamation beendet." -#: src/internals/garbage-collector.md:339 +#: src/internals/garbage-collector.md:335 msgid "" "Reported heap usage (`js_arena_stats`, `process.memoryUsage().heapUsed`) is " "an exact post-collection **live census** plus incremental deltas, not a sum " @@ -82700,11 +82527,11 @@ msgstr "" "heapUsed`) ist eine exakte Post-Sammlung **Lebendzählung** plus " "inkrementelle Deltas, keine Summe von Blockhochwasser-Offsets." -#: src/internals/garbage-collector.md:343 +#: src/internals/garbage-collector.md:339 msgid "Idle-time reclaim" msgstr "" -#: src/internals/garbage-collector.md:345 +#: src/internals/garbage-collector.md:341 msgid "" "Every automatic collection above is scheduled by allocation, so a program " "that bursts and then goes quiet keeps whatever the last mid-burst cycle left " @@ -82715,40 +82542,40 @@ msgid "" "completed since its last full, and 3000 ms" msgstr "" -#: src/internals/garbage-collector.md:353 +#: src/internals/garbage-collector.md:349 msgid "" "have passed since that collection was observed, it opens a full budgeted " "cycle with the block-pool drain armed and steps it in 4000 µs" msgstr "" -#: src/internals/garbage-collector.md:356 +#: src/internals/garbage-collector.md:352 msgid "" "slices, returning to the loop the moment a wake is pending. Two reducer " "fulls are never closer than 10000 ms" msgstr "" -#: src/internals/garbage-collector.md:359 +#: src/internals/garbage-collector.md:355 msgid "apart. A full that lowers old-gen occupancy by less than 5 percent" msgstr "" -#: src/internals/garbage-collector.md:361 +#: src/internals/garbage-collector.md:357 msgid "of what it started with (or by less than 4 MiB" msgstr "" -#: src/internals/garbage-collector.md:363 +#: src/internals/garbage-collector.md:359 msgid "" ") doubles the number of collections required before the next attempt, up to " "32" msgstr "" -#: src/internals/garbage-collector.md:365 +#: src/internals/garbage-collector.md:361 msgid "" "(two to that power); a productive full resets the requirement to one. The " "same slicing finishes a budgeted cycle the pacer left open when the mutator " "went quiet." msgstr "" -#: src/internals/garbage-collector.md:369 +#: src/internals/garbage-collector.md:365 msgid "" "Arena capacity has a second re-arm signal because returning a proven-empty " "block deliberately takes two full collection observations, while the " @@ -82756,150 +82583,150 @@ msgid "" "capacity floor," msgstr "" -#: src/internals/garbage-collector.md:373 +#: src/internals/garbage-collector.md:369 msgid "two consecutive post-collection live/capacity readings" msgstr "" -#: src/internals/garbage-collector.md:375 +#: src/internals/garbage-collector.md:371 msgid "at or below 50 percent" msgstr "" -#: src/internals/garbage-collector.md:377 +#: src/internals/garbage-collector.md:373 msgid "" "open a bounded right-size episode. It grants only enough idle fulls to reach " "two full observations" msgstr "" -#: src/internals/garbage-collector.md:380 +#: src/internals/garbage-collector.md:376 msgid "" "(counting any full already in the low-utilization streak), and stops early " "once live data reaches 60 percent of capacity." msgstr "" -#: src/internals/garbage-collector.md:383 +#: src/internals/garbage-collector.md:379 msgid "" "After a bounded episode, stable low occupancy cannot immediately repeat it: " "a new episode re-arms only after utilization reaches 70 percent" msgstr "" -#: src/internals/garbage-collector.md:386 +#: src/internals/garbage-collector.md:382 msgid "or capacity grows by at least both 25 percent and 8 MiB." msgstr "" -#: src/internals/garbage-collector.md:389 +#: src/internals/garbage-collector.md:385 msgid "" "That hysteresis preserves burst headroom and prevents an unreleasably " "fragmented heap from buying a full every ten seconds. The right-size debt " "still passes through the reducer's quiet, rate, wake, and work-budget gates." msgstr "" -#: src/internals/garbage-collector.md:394 +#: src/internals/garbage-collector.md:390 msgid "" "`PERRY_GC_DIAG=1` reports the reducer's counters on the `[gc-idle-reclaim]` " "exit line, and `PERRY_GC_IDLE_RECLAIM=0` disables it." msgstr "" -#: src/internals/garbage-collector.md:399 +#: src/internals/garbage-collector.md:395 msgid "Supported controls" msgstr "Unterstützte Steuerungen" -#: src/internals/garbage-collector.md:401 +#: src/internals/garbage-collector.md:397 msgid "" "These are the operational controls most useful outside collector development:" msgstr "Dies sind die am nützlichsten außerhalb der Kollektor-Entwicklung:" -#: src/internals/garbage-collector.md:403 +#: src/internals/garbage-collector.md:399 msgid "knob" msgstr "knob" -#: src/internals/garbage-collector.md:403 +#: src/internals/garbage-collector.md:399 msgid "purpose" msgstr "purpose" -#: src/internals/garbage-collector.md:405 +#: src/internals/garbage-collector.md:401 msgid "`PERRY_GEN_GC=0`" msgstr "`PERRY_GEN_GC=0`" -#: src/internals/garbage-collector.md:405 +#: src/internals/garbage-collector.md:401 msgid "bisection fallback to full mark-sweep" msgstr "bis zum Ende des Mark-Sweep" -#: src/internals/garbage-collector.md:406 +#: src/internals/garbage-collector.md:402 msgid "`PERRY_WRITE_BARRIERS=0`" msgstr "`PERRY_WRITE_BARRIERS=0`" -#: src/internals/garbage-collector.md:406 +#: src/internals/garbage-collector.md:402 msgid "compile/runtime barrier bisection; also forces full mark-sweep" msgstr "compile/runtime Barriere Bisektion; auch Kräfte volle Markierung-Sweep" -#: src/internals/garbage-collector.md:407 +#: src/internals/garbage-collector.md:403 msgid "`PERRY_GC_SCAVENGE=0`" msgstr "`PERRY_GC_SCAVENGE=0`" -#: src/internals/garbage-collector.md:407 +#: src/internals/garbage-collector.md:403 msgid "disable direct nursery scavenging for pacing comparison" msgstr "" "Deaktivieren Sie das direkte Kinderzimmer-Scavenging für den Schrittvergleich" -#: src/internals/garbage-collector.md:408 +#: src/internals/garbage-collector.md:404 msgid "`PERRY_GC_PROMOTE_IN_PLACE=0`" msgstr "`PERRY_GC_PROMOTE_IN_PLACE=0`" -#: src/internals/garbage-collector.md:408 +#: src/internals/garbage-collector.md:404 msgid "revert whole-block promotion to object-by-object evacuation" msgstr "Ganzblockförderung auf Objekt-für-Objekt-Evakuierung umkehren" -#: src/internals/garbage-collector.md:409 +#: src/internals/garbage-collector.md:405 msgid "`PERRY_GC_SCAVENGE_NURSERY_MB=N`" msgstr "`PERRY_GC_SCAVENGE_NURSERY_MB=N`" -#: src/internals/garbage-collector.md:409 +#: src/internals/garbage-collector.md:405 msgid "set the base nursery cap" msgstr "Einstellung der Basis Kindermütze" -#: src/internals/garbage-collector.md:410 +#: src/internals/garbage-collector.md:406 msgid "`PERRY_GC_HEAP_LIMIT=N`" msgstr "`PERRY_GC_HEAP_LIMIT=N`" -#: src/internals/garbage-collector.md:410 +#: src/internals/garbage-collector.md:406 msgid "override the process heap budget in MiB" msgstr "überschreiben Sie das Prozesshaufenbudget in MiB" -#: src/internals/garbage-collector.md:411 +#: src/internals/garbage-collector.md:407 msgid "`PERRY_CONSERVATIVE_STACK_SCAN=full`" msgstr "`PERRY_CONSERVATIVE_STACK_SCAN=full`" -#: src/internals/garbage-collector.md:411 +#: src/internals/garbage-collector.md:407 msgid "diagnostic full native-stack scan; disables copying" msgstr "Diagnose-Voll-Native-Stacks-Scan; deaktiviert Kopieren" -#: src/internals/garbage-collector.md:412 +#: src/internals/garbage-collector.md:408 msgid "`PERRY_GC_TRACE=1`" msgstr "`PERRY_GC_TRACE=1`" -#: src/internals/garbage-collector.md:412 +#: src/internals/garbage-collector.md:408 msgid "emit structured per-cycle trace records" msgstr "emittieren strukturierte pro-Zyklus-Trace-Datensätze" -#: src/internals/garbage-collector.md:413 +#: src/internals/garbage-collector.md:409 msgid "" "emit human-readable collector diagnostics (per cycle, plus `[gc-trigger]`/" "`[gc-full]`/`[gc-budgeted]`/`[gc-charge]` decision and charge attribution " "and the per-minor `[gc-survival]` root attribution)" msgstr "" -#: src/internals/garbage-collector.md:414 +#: src/internals/garbage-collector.md:410 #, fuzzy msgid "`PERRY_ALLOC_SITE_SAMPLE=N`" msgstr "`PERRY_GC_HEAP_LIMIT=N`" -#: src/internals/garbage-collector.md:414 +#: src/internals/garbage-collector.md:410 msgid "" "sample the arena allocation-site histogram every N bytes (`[alloc-site]`); " "`1`/`on` selects the default interval" msgstr "" -#: src/internals/garbage-collector.md:416 +#: src/internals/garbage-collector.md:412 #, fuzzy msgid "" "Rooting stress uses `PERRY_GC_SCHEDULE_SEED`, `PERRY_GC_SCHEDULE_RATE`, " @@ -82929,7 +82756,7 @@ msgstr "" "`PERRY_STACKMAP_WALKER` werden akzeptiert, sind aber keine zusätzlichen " "unterstützten Kollektormodi." -#: src/internals/garbage-collector.md:430 +#: src/internals/garbage-collector.md:426 msgid "" "`scripts/check_gc_env_knobs.py` derives the accepted names from live runtime/" "codegen/compiler parsers and rejects a current document, executable script, " @@ -82959,11 +82786,11 @@ msgstr "" "fehlschlägt `cargo-test`; auch kann nicht ruhig gehen, während diese Seite " "hält Anspruch auf die alte Sache." -#: src/internals/garbage-collector.md:442 +#: src/internals/garbage-collector.md:438 msgid "Validation and CI authority" msgstr "Validierung und CI-Behörde" -#: src/internals/garbage-collector.md:444 +#: src/internals/garbage-collector.md:440 msgid "" "As of 2026-08-16, branch protection requires exactly one status, `pr-gate` — " "the fan-in of `test.yml`'s PR tier (see `docs/src/testing/ci-tiers.md`; the " @@ -82975,37 +82802,37 @@ msgstr "" "md`; die Ebenenrichtlinie ist `scripts/ci_plan.py`). Die GC-spezifische " "Abdeckung wird bewusst aufgeteilt:" -#: src/internals/garbage-collector.md:448 +#: src/internals/garbage-collector.md:444 msgid "where it runs" msgstr "wo es läuft" -#: src/internals/garbage-collector.md:448 +#: src/internals/garbage-collector.md:444 msgid "required status" msgstr "erforderlicher Status" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "root-holder custody, GC-knob drift, and this page's path/number claims" msgstr "" "root-holder-Gewahrsam, GC-Knob Drift, und diese Seite path/number Ansprüche" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "`test.yml` → `lint`" msgstr "`test.yml` → `lint`" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "yes (via `pr-gate`)" msgstr "Ja (über `pr-gate`)" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "runtime unit suite and `run_memory_stability_tests.sh` four-mode matrix" msgstr "" "Laufzeiteinheit Suite und `run_memory_stability_tests.sh` Vier-Modus-Matrix" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "`test.yml` → `cargo-test`" msgstr "`test.yml` → `cargo-test`" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "" "yes (via `pr-gate`; the PR tier is diff-scoped, the sweep/full tiers run the " "workspace)" @@ -83013,7 +82840,7 @@ msgstr "" "ja (über `pr-gate`; die PR-Ebene ist diff-scoped, die sweep/full-Ebenen " "laufen den Arbeitsbereich)" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "" "GC × representation-selection matrix, rooting-bug instruments, write-barrier " "stress" @@ -83021,25 +82848,25 @@ msgstr "" "GC × Darstellungs-Auswahl-Matrix, Rooting-Bug-Instrumente, Schreib-Barriere-" "Stress" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "`test.yml` → `gc-stress`" msgstr "`test.yml` → `gc-stress`" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "yes (via `pr-gate`; PR subset on PRs, full matrix in the sweep)" msgstr "ja (über `pr-gate`; PR-Teilmenge auf PRs, volle Matrix im Sweep)" -#: src/internals/garbage-collector.md:453 +#: src/internals/garbage-collector.md:449 msgid "emitted root dominance, including native statepoint IR" msgstr "emittierte Root-Dominanz, einschließlich nativer Statepoint IR" -#: src/internals/garbage-collector.md:453 +#: src/internals/garbage-collector.md:449 msgid "`gc-root-dominance.yml`" msgstr "`gc-root-dominance.yml`" -#: src/internals/garbage-collector.md:453 -#: src/internals/garbage-collector.md:454 -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:449 +#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:451 msgid "" "not branch-required; PR arm opt-in via `run-extended-tests`, six-hourly on " "`main`" @@ -83047,27 +82874,27 @@ msgstr "" "nicht verzweigt erforderlich; PR-Arm Opt-in über `run-extended-tests`, sechs " "Stunden lang auf `main`" -#: src/internals/garbage-collector.md:454 +#: src/internals/garbage-collector.md:450 msgid "pinned collector counters/RSS/wall matrix" msgstr "gepinnter Kollektor counters/RSS/wall Matrix" -#: src/internals/garbage-collector.md:454 +#: src/internals/garbage-collector.md:450 msgid "`gc-ratchet.yml`" msgstr "`gc-ratchet.yml`" -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:451 msgid "thread-local mechanism/policy budget" msgstr "thread-local mechanism/policy Budget" -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:451 msgid "`tls-budget.yml`" msgstr "`tls-budget.yml`" -#: src/internals/garbage-collector.md:457 +#: src/internals/garbage-collector.md:453 msgid "Useful local preflight commands:" msgstr "Nützliche lokale Vorflugbefehle:" -#: src/internals/garbage-collector.md:470 +#: src/internals/garbage-collector.md:466 msgid "" "The dedicated performance/rooting workflows have broader compiler and host " "requirements; their workflow files are the authority for exact commands and " @@ -83077,11 +82904,11 @@ msgstr "" "Compiler- und Host-Anforderungen; ihre Workflow-Dateien sind die Autorität " "für exakte Befehle und Relevanzfilter." -#: src/internals/garbage-collector.md:474 +#: src/internals/garbage-collector.md:470 msgid "Historical evidence" msgstr "Historische Beweise" -#: src/internals/garbage-collector.md:476 +#: src/internals/garbage-collector.md:472 msgid "" "[Generational GC plan](https://github.com/PerryTS/perry/blob/main/docs/" "generational-gc-plan.md): original design and phase-by-phase landing log." @@ -83089,7 +82916,7 @@ msgstr "" "[Generationenplan für GC](https://github.com/PerryTS/perry/blob/main/docs/" "generational-gc-plan.md): Originalentwurf und Phasen-für-Phasen-Landebuch." -#: src/internals/garbage-collector.md:478 +#: src/internals/garbage-collector.md:474 msgid "" "[Statepoint GC experiment](https://github.com/PerryTS/perry/blob/main/docs/" "statepoint-gc-experiment.md): chronological prototype measurements and " @@ -83099,7 +82926,7 @@ msgstr "" "statepoint-gc-experiment.md): chronologische Prototypenmessungen und " "Korrekturen, die zum nativen Standard führen." -#: src/internals/garbage-collector.md:480 +#: src/internals/garbage-collector.md:476 msgid "" "[GC rooting invariant](gc-rooting-invariant.md): current codegen soundness " "rule, checker modes, known blind spots, and debugging instruments." @@ -83108,7 +82935,7 @@ msgstr "" "Soundness-Regel, Checker-Modi, bekannte Blind-Spots und Debugging-" "Instrumente." -#: src/internals/garbage-collector.md:482 +#: src/internals/garbage-collector.md:478 msgid "" "[Memory Model](memory-model.md): NaN-boxing, allocation representation, and " "platform memory-tooling notes." @@ -84042,9 +83869,9 @@ msgstr "" "`js_function_call`, `js_invoke_closure`, `js_object_get_property`, " "`js_object_set_property`, `js_string_replace` — extrapolierte Schreibweisen, " "keiner von ihnen ein `extern \"C\" fn` irgendwo in der Laufzeit. Vier der " -"zehn waren vier verschiedene Arten zu sagen, \"rufen Sie einen JS-" -"Abschluss\", so dass die einzige offensichtlich polle-fähig Betrieb in der " -"Sprache wurde Null mal abgedeckt; die realen Einstiegspunkte sind " +"zehn waren vier verschiedene Arten zu sagen, \"rufen Sie einen JS-Abschluss" +"\", so dass die einzige offensichtlich polle-fähig Betrieb in der Sprache " +"wurde Null mal abgedeckt; die realen Einstiegspunkte sind " "`js_closure_callN`, die `RECEIVER_SINKS` in der gleichen Datei bereits " "richtig geschrieben." @@ -84669,10 +84496,10 @@ msgid "" "running the program.** There is no execution in which \"the barrier did not " "run\" is a distinguishable event." msgstr "" -"Dies ist CLAUDE.md's Hazard 4 (\"das Gate läuft, aber sein Subjekt hat " -"nie\") mit dem Subjekt invertiert: **Das Fehlen einer Barriere kann nicht " -"durch das Programm beobachtet werden.** Es gibt keine Ausführung, in der " -"\"die Barriere nicht lief\" ein unterscheidendes Ereignis ist." +"Dies ist CLAUDE.md's Hazard 4 (\"das Gate läuft, aber sein Subjekt hat nie" +"\") mit dem Subjekt invertiert: **Das Fehlen einer Barriere kann nicht durch " +"das Programm beobachtet werden.** Es gibt keine Ausführung, in der \"die " +"Barriere nicht lief\" ein unterscheidendes Ereignis ist." #: src/internals/gc-rooting-invariant.md:574 msgid "" @@ -85347,6 +85174,100 @@ msgstr "" "Laden. Ein Vermisstes kostet einen Tag, und das kostet es für jeden, der den " "Absturz trifft, nicht für dich." +#: src/internals/program-facts.md:3 +msgid "" +"`perry-wpfacts` analyzes lowered HIR value flow, not declared TypeScript " +"types. The default driver solves per-module units and exchanges contracts to" +" a fixed point. `PERRY_FACTS_UNITS=0` selects the whole-program reference " +"solve; `PERRY_PROGRAM_FACTS=0` disables the pass. Work or memory limits " +"discard unproved results and preserve generic behavior." +msgstr "" +"`perry-wpfacts` analysiert den Wertfluss im abgesenkten HIR, nicht " +"deklarierte TypeScript-Typen. Standardmäßig löst der Treiber Einheiten pro " +"Modul und tauscht Verträge bis zu einem Fixpunkt aus. `PERRY_FACTS_UNITS=0` " +"wählt die programmweite Referenzanalyse; `PERRY_PROGRAM_FACTS=0` deaktiviert" +" den Pass. Arbeits- oder Speichergrenzen verwerfen unbewiesene Ergebnisse " +"und erhalten generisches Verhalten." + +#: src/internals/program-facts.md:5 +msgid "" +"Facts describe field writers, definite birth keys, value kinds, escaping " +"objects, stable field prefixes, fixed layouts, receiver sets and function " +"targets. A known field offset does not alone prove property presence: " +"declared or reserved fields may still contain a hole. Typed-array facts also" +" require evidence about exposure, detachment and resizing." +msgstr "" +"Fakten beschreiben Feldschreiber, sicher vorhandene Erzeugungsschlüssel, " +"Wertarten, entweichende Objekte, stabile Feldpräfixe, feste Layouts, " +"Empfängermengen und Funktionsziele. Ein bekannter Feldoffset allein beweist " +"nicht das Vorhandensein einer Eigenschaft: Deklarierte oder reservierte " +"Felder können noch ein Loch enthalten. Typed-Array-Fakten erfordern außerdem" +" Nachweise über externe Zugänglichkeit, Ablösung und Größenänderung." + +#: src/internals/program-facts.md:7 +msgid "" +"Codegen consumes these facts today. A proven receiver and field can use a " +"fixed-offset load or store; multiple layouts may require class selection. " +"Uncertain receivers retain guards and a complete generic fallback. " +"Interpreted code is modeled as unknown behavior, with prototype-write guards" +" where required. Pointer stores still need GC barriers, and managed values " +"still need roots." +msgstr "" +"Codegen nutzt diese Fakten bereits. Nachgewiesene Empfänger und Felder " +"erlauben Zugriffe mit festem Offset; mehrere Layouts können eine " +"Klassenauswahl erfordern. Unsichere Empfänger behalten Prüfungen und einen " +"vollständigen generischen Rückfallpfad. Interpretierter Code gilt als " +"unbekanntes Verhalten, mit Prüfungen auf Prototypschreibzugriffe, wo nötig. " +"Zeigerspeicherungen benötigen weiterhin GC-Barrieren, verwaltete Werte " +"weiterhin Roots." + +#: src/internals/program-facts.md:9 +msgid "" +"Local initializer evidence and whole-program facts are distinct analyses. An" +" annotation alone cannot justify unboxed arithmetic, a field load or removal" +" of a GC root. Native scalar and packed-array paths need their own " +"representation, mutation and escape proofs; there is no blanket promise that" +" all TypeScript values remain boxed or become unboxed." +msgstr "" +"Lokale Initialisierungsnachweise und programmweite Fakten sind getrennte " +"Analysen. Eine Annotation allein rechtfertigt weder ungeboxte Arithmetik " +"noch einen Feldzugriff oder das Entfernen eines GC-Roots. Native Skalar- und" +" gepackte Array-Pfade benötigen eigene Darstellungs-, Mutations- und Escape-" +"Nachweise; TypeScript-Werte sind nicht pauschal immer geboxt oder ungeboxt." + +#: src/internals/program-facts.md:11 +msgid "" +"Use `PERRY_FACTS_DUMP=` for analysis and elision records, " +"`PERRY_FACTS_CONSUMED=` for source sites consuming facts, and " +"`PERRY_FACTS_REPORT=1` for solver statistics. `PERRY_FACTS_ELIDE=0` keeps " +"analysis but disables its check elision. Contract builds " +"(`PERRY_CHECK_CONTRACTS=1`, runtime `contract-checks`) validate claims " +"against actual objects. [Fallback diagnostics](fallback-diagnostics.md) " +"explain retained generic operations." +msgstr "" +"`PERRY_FACTS_DUMP=` liefert Analyse- und Elisionsprotokolle, " +"`PERRY_FACTS_CONSUMED=` die Fakten nutzenden Quellstellen und " +"`PERRY_FACTS_REPORT=1` Solver-Statistiken. `PERRY_FACTS_ELIDE=0` erhält die " +"Analyse, deaktiviert aber das Entfernen von Prüfungen. Vertrags-Builds " +"(`PERRY_CHECK_CONTRACTS=1`, Laufzeitfeature `contract-checks`) prüfen " +"Aussagen an tatsächlichen Objekten. [Fallback-Diagnosen](fallback-" +"diagnostics.md) erklären verbleibende generische Operationen." + +#: src/internals/program-facts.md:13 +msgid "" +"The implementation starts in " +"`crates/perry/src/commands/compile/program_facts.rs` and `crates/perry-" +"wpfacts/src/`. Codegen receives `ProgramFactsView` in `crates/perry-" +"codegen/src/codegen/program_facts_view.rs`; field consumers live in " +"`crates/perry-codegen/src/expr/wpfacts_access.rs`. Fact digests participate " +"in object-cache identity." +msgstr "" +"Der Einstieg liegt in `crates/perry/src/commands/compile/program_facts.rs` " +"und `crates/perry-wpfacts/src/`. Codegen erhält `ProgramFactsView` in " +"`crates/perry-codegen/src/codegen/program_facts_view.rs`; Feldverbraucher " +"liegen in `crates/perry-codegen/src/expr/wpfacts_access.rs`. Fakten-Digests " +"fließen in die Identität des Objektcaches ein." + #: src/internals/local-binding-type-evidence.md:3 msgid "" "Perry compiles TypeScript, but TypeScript annotations do not exist at " @@ -85547,10 +85468,19 @@ msgstr "" "weil numerische und boolesche Anmerkungen jeden NaN-Boxwert halten können." #: src/internals/local-binding-type-evidence.md:79 +msgid "" +"[Program facts](program-facts.md) provide a separate cross-module analysis. " +"Local initializer evidence is not the sole source of specialization proofs." +msgstr "" +"[Programmfakten](program-facts.md) liefern eine separate modulübergreifende " +"Analyse. Lokale Initialisierungsnachweise sind nicht die einzige Quelle für " +"Spezialisierungsbeweise." + +#: src/internals/local-binding-type-evidence.md:81 msgid "Static inventory and CI gate" msgstr "Statisches Inventar und CI-Gate" -#: src/internals/local-binding-type-evidence.md:81 +#: src/internals/local-binding-type-evidence.md:83 msgid "" "`scripts/local_binding_type_audit.py` scans production HIR/codegen sources " "for both accessors and for remaining lower-level type-map reads. Its count-" @@ -85560,16 +85490,16 @@ msgstr "" "für beide Accessors und für die verbleibenden unteren Ebenen Typ-Map liest. " "Sein Count-exact Inventar ist `scripts/local_binding_type_allowlist.json`." -#: src/internals/local-binding-type-evidence.md:85 +#: src/internals/local-binding-type-evidence.md:87 msgid "Each inventory group states one of three verdicts:" msgstr "Jede Inventargruppe gibt eines von drei Urteilen an:" -#: src/internals/local-binding-type-evidence.md:87 +#: src/internals/local-binding-type-evidence.md:89 msgid "`runtime-validated`: emitted code checks the current runtime value;" msgstr "" "`runtime-validated`: emittierter Code überprüft den aktuellen Laufzeitwert;" -#: src/internals/local-binding-type-evidence.md:88 +#: src/internals/local-binding-type-evidence.md:90 msgid "" "`representation-proven`: another analysis establishes the storage fact and " "invalidates it on writes;" @@ -85577,14 +85507,14 @@ msgstr "" "`representation-proven`: Eine andere Analyse stellt die Speicher-Fakten fest " "und entwertet sie auf schreibt;" -#: src/internals/local-binding-type-evidence.md:90 +#: src/internals/local-binding-type-evidence.md:92 msgid "" "`metadata-only`: the type read cannot select a runtime answer or layout." msgstr "" "`metadata-only`: Der Typ read kann keine Laufzeitantwort oder kein Layout " "auswählen." -#: src/internals/local-binding-type-evidence.md:92 +#: src/internals/local-binding-type-evidence.md:94 msgid "" "The gate fails when a consumer is added without a verdict, when a count " "changes, when an entry matches nothing, or when code bypasses the accessors " @@ -85599,7 +85529,7 @@ msgstr "" "Modi, und ein leerer Scan, so dass ein veralteter Scanner kann nicht " "berichten, einen lebhaften Erfolg." -#: src/internals/local-binding-type-evidence.md:98 +#: src/internals/local-binding-type-evidence.md:100 msgid "Run it locally with:" msgstr "Führen Sie es lokal mit:" @@ -85870,8 +85800,8 @@ msgstr "" "schwache Verarbeitung, die verwendet wird, um eine Arbeitseinheit pro Halter " "aufzuladen, während `process_finreg_after_mark` das gesamte Datensatzfeld " "des Halters innerhalb der Einheit betrat. Eine Registrierung mit einer " -"Million Registrierungen war daher eine atomare, haufengroße " -"\"Arbeitseinheit\", die hinter einer zeitgeprüften API saß." +"Million Registrierungen war daher eine atomare, haufengroße \"Arbeitseinheit" +"\", die hinter einer zeitgeprüften API saß." #: src/internals/gc-step-bounds.md:35 msgid "" @@ -86773,9 +86703,9 @@ msgstr "" msgid "" "`#[deny]` the escape hatch per-module as each module finishes, so migrated " "code cannot regress. **Done, as a test rather than an attribute**: Rust has " -"no `#[deny]` for \"do not call this `pub(crate)` function from this " -"module\", so `rooting::migration_ledger` `include_str!`s each finished " -"module and fails the build if it names `expr::temp_root`." +"no `#[deny]` for \"do not call this `pub(crate)` function from this module" +"\", so `rooting::migration_ledger` `include_str!`s each finished module and " +"fails the build if it names `expr::temp_root`." msgstr "" "`#[deny]` die Fluchtluke pro Modul, wie jedes Modul beendet, so migrierte " "Code kann nicht zurücktreten. **Fertig, als Test und nicht als Attribut**: " @@ -87225,10 +87155,10 @@ msgstr "" "textuelle `.ll` und Shells heraus zu einem Benutzer-gelieferte `clang`, so " "dass es kontrolliert weder die Pass-Pipeline noch die Stackmap-Emission. " "\\#7241 Phase 0 entfernt genau diese Einschränkung — es baut die Pipeline " -"über die LLVM C API und unabhängig überprüft, dass **`gc \"statepoint-" -"example\"` konstruiert, überprüft und emittiert**. Es pinnt auch LLVM 22 " -"(abtötet den Apple-clang-21-vs-22 parse skew) und ist Opt-in hinter einer " -"Fracht-Funktion, so dass die Standard-Build ist Byte-for-Byte unverändert." +"über die LLVM C API und unabhängig überprüft, dass **`gc \"statepoint-example" +"\"` konstruiert, überprüft und emittiert**. Es pinnt auch LLVM 22 (abtötet " +"den Apple-clang-21-vs-22 parse skew) und ist Opt-in hinter einer Fracht-" +"Funktion, so dass die Standard-Build ist Byte-for-Byte unverändert." #: src/internals/rfc-rooting-by-construction.md:368 msgid "**⇒ The dependency order is 0 → 2, with 1 and 3 independent of both.**" @@ -87242,9 +87172,9 @@ msgstr "Was Statepoints mit diesem RFC machen" #: src/internals/rfc-rooting-by-construction.md:372 msgid "" -"Adopting layer 2 **deletes several of this document's \"what it cannot " -"catch\" entries rather than mitigating them**, because the shadow frame " -"stops existing:" +"Adopting layer 2 **deletes several of this document's \"what it cannot catch" +"\" entries rather than mitigating them**, because the shadow frame stops " +"existing:" msgstr "" "Annahme von Schicht 2 **löscht mehrere Einträge dieses Dokuments \"was es " "nicht fangen kann\" anstatt sie zu mildern**, weil der Schattenrahmen nicht " @@ -87425,8 +87355,8 @@ msgstr "" "Schritt 1 ist _not_ getan und sollte nicht angenommen werden: Die gruseligen " "Flucht-Hatch-Typen wurden nie benötigt, weil die Kombinator-Form eine " "Anrufstelle zu einer Zeit ohne sie migriert. Der `RefCell`'d Emitter, der " -"den Bestellfehler machen würde, bleibt ein Compilerfehler unerbaut. " -"**\\#7615 ist die Kampagnenkarte** — 88 Module, 694 Rohpointer, 262 " +"den Bestellfehler machen würde, bleibt ein Compilerfehler unerbaut. **" +"\\#7615 ist die Kampagnenkarte** — 88 Module, 694 Rohpointer, 262 " "Gefahrenstellen, zehn Scheiben." #: src/internals/node-api-host.md:3 @@ -89833,51 +89763,69 @@ msgstr "" msgid "Compilation Pipeline" msgstr "Kompilierungs-Pipeline" -#: src/contributing/architecture.md:22 +#: src/contributing/architecture.md:23 msgid "Crate Map" msgstr "Crate-Karte" -#: src/contributing/architecture.md:26 +#: src/contributing/architecture.md:27 msgid "CLI driver, command parsing, compilation orchestration" msgstr "CLI-Treiber, Befehls-Parsing, Kompilierungs-Orchestrierung" -#: src/contributing/architecture.md:27 +#: src/contributing/architecture.md:28 msgid "`perry-parser`" msgstr "`perry-parser`" -#: src/contributing/architecture.md:27 +#: src/contributing/architecture.md:28 msgid "SWC wrapper for TypeScript parsing" msgstr "SWC-Wrapper für TypeScript-Parsing" -#: src/contributing/architecture.md:28 +#: src/contributing/architecture.md:29 msgid "`perry-hir`" msgstr "`perry-hir`" -#: src/contributing/architecture.md:28 +#: src/contributing/architecture.md:29 msgid "HIR types and data structures, plus AST→HIR lowering" msgstr "HIR-Typen und Datenstrukturen sowie AST→HIR-Absenkung" -#: src/contributing/architecture.md:29 +#: src/contributing/architecture.md:30 msgid "`perry-transform`" msgstr "`perry-transform`" -#: src/contributing/architecture.md:29 +#: src/contributing/architecture.md:30 msgid "IR passes: function inlining, closure conversion, async lowering" msgstr "IR-Passes: Funktions-Inlining, Closure-Konvertierung, Async-Lowering" -#: src/contributing/architecture.md:30 +#: src/contributing/architecture.md:31 +#, fuzzy +msgid "`perry-wpfacts`" +msgstr "`perry-parser`" + +#: src/contributing/architecture.md:31 +msgid "Value-flow facts and cross-module contracts" +msgstr "Wertflussfakten und modulübergreifende Verträge" + +#: src/contributing/architecture.md:32 +#, fuzzy +msgid "`perry-abi`" +msgstr "`perry-hir`" + +#: src/contributing/architecture.md:32 +msgid "Shared compiler/runtime value and layout contract" +msgstr "Gemeinsamer Wert- und Layoutvertrag von Compiler und Laufzeit" + +#: src/contributing/architecture.md:33 msgid "`perry-codegen`" msgstr "`perry-codegen`" -#: src/contributing/architecture.md:30 +#: src/contributing/architecture.md:33 msgid "LLVM-based native code generation" msgstr "LLVM-basierte native Code-Generierung" -#: src/contributing/architecture.md:31 +#: src/contributing/architecture.md:34 msgid "`perry-codegen-wasm`" msgstr "`perry-codegen-wasm`" -#: src/contributing/architecture.md:31 +#: src/contributing/architecture.md:34 msgid "" "WebAssembly code generation for `--target web` / `--target wasm` (HIR → WASM " "bytecode + JS bridge)" @@ -89885,11 +89833,11 @@ msgstr "" "WebAssembly-Code-Generierung für `--target web` / `--target wasm` (HIR → " "WASM-Bytecode + JS-Brücke)" -#: src/contributing/architecture.md:32 +#: src/contributing/architecture.md:35 msgid "`perry-codegen-js`" msgstr "`perry-codegen-js`" -#: src/contributing/architecture.md:32 +#: src/contributing/architecture.md:35 msgid "" "Legacy JavaScript code generator (still present for the JS minifier; the JS-" "emit `--target web` path was consolidated into `perry-codegen-wasm`)" @@ -89897,19 +89845,19 @@ msgstr "" "Legacy-JavaScript-Codegenerator (noch vorhanden für den JS-Minifier; der JS-" "emit `--target web`-Pfad wurde in `perry-codegen-wasm` konsolidiert)" -#: src/contributing/architecture.md:33 +#: src/contributing/architecture.md:36 msgid "`perry-codegen-swiftui`" msgstr "`perry-codegen-swiftui`" -#: src/contributing/architecture.md:33 +#: src/contributing/architecture.md:36 msgid "SwiftUI code generation for WidgetKit extensions" msgstr "SwiftUI-Code-Generierung für WidgetKit-Erweiterungen" -#: src/contributing/architecture.md:34 +#: src/contributing/architecture.md:37 msgid "`perry-runtime`" msgstr "`perry-runtime`" -#: src/contributing/architecture.md:34 +#: src/contributing/architecture.md:37 msgid "" "Runtime library: NaN-boxed values, GC, arena allocator, objects, arrays, " "strings" @@ -89917,116 +89865,113 @@ msgstr "" "Runtime-Bibliothek: NaN-geboxte Werte, GC, Arena-Allokator, Objekte, Arrays, " "Strings" -#: src/contributing/architecture.md:35 +#: src/contributing/architecture.md:38 msgid "`perry-ffi`" msgstr "`perry-ffi`" -#: src/contributing/architecture.md:35 +#: src/contributing/architecture.md:38 msgid "Stable interface used by native binding crates" msgstr "Stabile Schnittstelle, die von nativen Bindungskisten verwendet wird" -#: src/contributing/architecture.md:36 +#: src/contributing/architecture.md:39 msgid "`perry-stdlib`" msgstr "`perry-stdlib`" -#: src/contributing/architecture.md:36 +#: src/contributing/architecture.md:39 msgid "Runtime-coupled Node.js and Perry standard-library implementations" msgstr "" "Laufzeitgekoppelte Node.js- und Perry-Standard-Bibliotheksimplementierungen" -#: src/contributing/architecture.md:37 +#: src/contributing/architecture.md:40 msgid "`perry-ext-*`" msgstr "`perry-ext-*`" -#: src/contributing/architecture.md:37 +#: src/contributing/architecture.md:40 msgid "Independently linked native bindings selected per program" msgstr "" "Unabhängig verknüpfte native Bindungen, die pro Programm ausgewählt werden" -#: src/contributing/architecture.md:38 +#: src/contributing/architecture.md:41 msgid "`perry-ui` / `perry-ui-model`" msgstr "`perry-ui` / `perry-ui-model`" -#: src/contributing/architecture.md:38 +#: src/contributing/architecture.md:41 msgid "Shared UI interface and public model metadata" msgstr "Gemeinsame Benutzeroberfläche und öffentliche Modell Metadaten" -#: src/contributing/architecture.md:39 +#: src/contributing/architecture.md:42 msgid "macOS UI (AppKit)" msgstr "macOS-UI (AppKit)" -#: src/contributing/architecture.md:40 +#: src/contributing/architecture.md:43 msgid "iOS UI (UIKit)" msgstr "iOS-UI (UIKit)" -#: src/contributing/architecture.md:42 +#: src/contributing/architecture.md:45 msgid "Key Concepts" msgstr "Schlüsselkonzepte" -#: src/contributing/architecture.md:44 +#: src/contributing/architecture.md:47 msgid "NaN-Boxing" msgstr "NaN-Boxing" -#: src/contributing/architecture.md:46 -msgid "" -"All JavaScript values are represented as 64-bit NaN-boxed values. The upper " -"16 bits encode the type tag:" -msgstr "" -"Alle JavaScript-Werte werden als 64-Bit-NaN-geboxte Werte dargestellt. Die " -"oberen 16 Bits kodieren das Typ-Tag:" - -#: src/contributing/architecture.md:48 +#: src/contributing/architecture.md:51 msgid "Tag" msgstr "Tag" -#: src/contributing/architecture.md:50 +#: src/contributing/architecture.md:53 msgid "String (lower 48 bits = pointer)" msgstr "String (untere 48 Bits = Zeiger)" -#: src/contributing/architecture.md:51 +#: src/contributing/architecture.md:54 msgid "Pointer/Object (lower 48 bits = pointer)" msgstr "Pointer/Objekt (untere 48 Bits = Zeiger)" -#: src/contributing/architecture.md:52 +#: src/contributing/architecture.md:55 msgid "Int32 (lower 32 bits = integer)" msgstr "Int32 (untere 32 Bits = Integer)" -#: src/contributing/architecture.md:53 +#: src/contributing/architecture.md:56 msgid "BigInt (lower 48 bits = pointer)" msgstr "BigInt (untere 48 Bits = Zeiger)" -#: src/contributing/architecture.md:54 +#: src/contributing/architecture.md:57 +msgid "Inline short string" +msgstr "Inline-Kurzstring" + +#: src/contributing/architecture.md:59 msgid "Special constants" msgstr "Spezielle Konstanten" -#: src/contributing/architecture.md:54 +#: src/contributing/architecture.md:59 msgid "undefined, null, true, false" msgstr "undefined, null, true, false" -#: src/contributing/architecture.md:55 +#: src/contributing/architecture.md:60 msgid "Any other" msgstr "Alles andere" -#: src/contributing/architecture.md:55 +#: src/contributing/architecture.md:60 msgid "Float64 (the full 64 bits)" msgstr "Float64 (die vollen 64 Bits)" -#: src/contributing/architecture.md:59 +#: src/contributing/architecture.md:64 msgid "" -"Generational mark-sweep GC, per-thread arena split into nursery + old-gen. " -"Roots come from a precise shadow stack (emitted by codegen), a conservative " -"native-stack scan, and 9 registered runtime scanners. Two-bit aging tenures " -"objects after surviving 2 minor cycles; a write barrier maintains a " -"remembered set for old → young pointers." +"The per-thread tracing collector combines copying nursery collection, non-" +"moving fallbacks and full mark-sweep. Native roots use LLVM statepoints; " +"supported fallback targets use shadow frames. Runtime root scanners cover " +"globals and side tables. Conservative stack scanning is diagnostic-only. " +"Adaptive aging, large young storage and whole-block promotion have separate " +"policies." msgstr "" -"Generationaler Mark-Sweep-GC, Thread-lokale Arena aufgeteilt in Nursery + " -"Old-Gen. Roots stammen von einem präzisen Shadow Stack (vom Codegen " -"emittiert), einem konservativen nativen Stack-Scan und 9 registrierten " -"Runtime-Scannern. Zwei-Bit-Aging tenuriert Objekte nach dem Überleben von 2 " -"Minor-Zyklen; eine Write Barrier pflegt einen Remembered Set für Old-→-Young-" -"Zeiger." +"Der threadlokale Tracing-Kollektor kombiniert kopierende Nursery-Sammlung, " +"unbewegliche Rückfallpfade und vollständiges Mark-Sweep. Native Roots " +"verwenden LLVM-Statepoints; unterstützte Ersatzplattformen verwenden Shadow " +"Frames. Laufzeitscanner erfassen globale Werte und Seitentabellen. " +"Konservatives Stackscanning dient nur der Diagnose. Adaptive Alterung, " +"großer junger Speicher und Ganzblock-Promotion haben eigene Strategien." -#: src/contributing/architecture.md:61 +#: src/contributing/architecture.md:66 msgid "" "See [Internals → Memory Model](../internals/memory-model.md) for the full " "picture (NaN-boxing, heap layout, root discovery, generational behaviour, " @@ -90036,71 +89981,80 @@ msgstr "" "vollständige Bild (NaN-Boxing, Heap-Layout, Root-Erkennung, generationales " "Verhalten, Umgebungsvariablen-Escape-Hatches)." -#: src/contributing/architecture.md:63 +#: src/contributing/architecture.md:68 msgid "Handle-Based UI" msgstr "Handle-basierte UI" -#: src/contributing/architecture.md:65 +#: src/contributing/architecture.md:70 msgid "" -"UI widgets are represented as small integer handles NaN-boxed with " -"`POINTER_TAG`. Each handle maps to a native platform widget (NSButton, " +"Native UI registry IDs use `HANDLE_TAG` (`0xFFF9`), distinct from managed " +"heap pointers. Each handle maps to a native platform widget (NSButton, " "UILabel, GtkButton, etc.). Two dispatch tables route method calls and " "property accesses to the correct FFI function." msgstr "" -"UI-Widgets werden als kleine Integer-Handles dargestellt, die mit " -"`POINTER_TAG` NaN-geboxt sind. Jedes Handle bildet auf ein natives Plattform-" -"Widget ab (NSButton, UILabel, GtkButton usw.). Zwei Dispatch-Tabellen routen " -"Methodenaufrufe und Eigenschaftszugriffe zur korrekten FFI-Funktion." +"Native UI-Registrierungs-IDs verwenden `HANDLE_TAG` (`0xFFF9`), getrennt von" +" verwalteten Heapzeigern. Jedes Handle bezeichnet ein natives Plattform-" +"Widget (NSButton, UILabel, GtkButton usw.). Zwei Dispatch-Tabellen leiten " +"Methodenaufrufe und Eigenschaftszugriffe an die richtige FFI-Funktion " +"weiter." -#: src/contributing/architecture.md:67 +#: src/contributing/architecture.md:72 msgid "Source Code Organization" msgstr "Quellcode-Organisation" -#: src/contributing/architecture.md:69 -msgid "The codegen crate is organized into focused modules:" -msgstr "Die Codegen-Crate ist in fokussierte Module organisiert:" - -#: src/contributing/architecture.md:86 -msgid "The HIR lowering was split into 8 modules:" -msgstr "Das HIR-Lowering wurde in 8 Module aufgeteilt:" +#: src/contributing/architecture.md:74 +msgid "" +"Start with `crates/perry-hir/src/lower/` for AST lowering, `crates/perry-" +"codegen/src/codegen/` for module orchestration, and its sibling `expr/`, " +"`stmt/`, `lower_call/` and `native_root_coverage/` trees for emission. " +"[Program facts](../internals/program-facts.md) connect analysis to layout " +"and specialization. Turnloop supplies the event loop; Tokio is not part of " +"the runtime." +msgstr "" +"Beginnen Sie mit `crates/perry-hir/src/lower/` für AST-Absenkung, " +"`crates/perry-codegen/src/codegen/` für Modulsteuerung und den benachbarten " +"Verzeichnissen `expr/`, `stmt/`, `lower_call/` und `native_root_coverage/` " +"für Codeausgabe. [Programmfakten](../internals/program-facts.md) verbinden " +"Analyse, Layout und Spezialisierung. Turnloop stellt die Ereignisschleife " +"bereit; Tokio gehört nicht zur Laufzeit." -#: src/contributing/architecture.md:100 +#: src/contributing/architecture.md:76 #, fuzzy msgid "Adding a native UI widget" msgstr "Eine native Bindung erstellen" -#: src/contributing/architecture.md:102 +#: src/contributing/architecture.md:78 msgid "A new widget touches four places:" msgstr "" -#: src/contributing/architecture.md:104 +#: src/contributing/architecture.md:80 msgid "" "Runtime: create the widget in `crates/perry-ui-macos/src/widgets/` and call " "`register_widget(view)`." msgstr "" -#: src/contributing/architecture.md:105 +#: src/contributing/architecture.md:81 msgid "" "FFI: export `perry_ui__create` from `crates/perry-ui-macos/src/lib." "rs`." msgstr "" -#: src/contributing/architecture.md:106 +#: src/contributing/architecture.md:82 msgid "" "Codegen: declare the extern and add the `NativeMethodCall` dispatch in " "`crates/perry-codegen/src/lower_call/native/native_ui_widgets_branch.rs`." msgstr "" -#: src/contributing/architecture.md:107 +#: src/contributing/architecture.md:83 msgid "" "HIR: `crates/perry-hir/src/lower/`, only if the widget has instance methods." msgstr "" -#: src/contributing/architecture.md:111 +#: src/contributing/architecture.md:87 msgid "[Building from Source](building.md)" msgstr "[Aus dem Quellcode bauen](building.md)" -#: src/contributing/architecture.md:112 +#: src/contributing/architecture.md:88 #, fuzzy msgid "See `AGENTS.md` in the repository root for implementation traps" msgstr "" @@ -90556,8 +90510,8 @@ msgstr "" #: src/contributing/building.md:21 msgid "" -"For the cached Make targets, install the pinned Mr Boxington once with `make " -"mbx-deps`." +"`make build-dev` provisions its pinned build tools automatically. For `make " +"build-prod`, install the pinned Mr Boxington once with `make mbx-deps`." msgstr "" #: src/contributing/building.md:24 @@ -90723,10 +90677,60 @@ msgstr "" "langsamsten Einheiten, damit die Regressionen der Bauzeit sichtbar sind." #: src/contributing/building.md:83 +#, fuzzy +msgid "Faster development rebuilds with Blaze" +msgstr "Entwicklungs-Builds" + +#: src/contributing/building.md:85 +msgid "" +"`make build-dev` installs verified [Blaze](https://github.com/indexable-inc/" +"blaze) and mbx 1.28.0 binaries, selects their matching `nightly-2026-10-25` " +"compiler, and activates the Cargo shim automatically. Linux GNU x64/ARM64 " +"and Apple Silicon macOS use the prebuilt toolchain pinned in `scripts/build-" +"tools.ref`. Setup fails if the download or compiler identity cannot be " +"verified." +msgstr "" + +#: src/contributing/building.md:91 +msgid "" +"The default upstream archive currently targets an older compiler. These " +"prebuilts come from the pinned upstream source, built once in the shared " +"[jdalton/blaze release workflow](https://github.com/jdalton/blaze/actions/" +"workflows/release.yaml). Project builds download them without compiling " +"Blaze. The development Docker image installs them under `/opt/mbx-blaze`, " +"outside the checkout mount." +msgstr "" + +#: src/contributing/building.md:97 +msgid "" +"Development builds retain `force-unwind-tables=yes` for exception transport. " +"They use full unwind tables because Blaze refuses `use-sync-unwind=yes`. " +"Host production commands retain the repository's compiler and compact-table " +"configuration unless custom Rust flags override it. The development image " +"uses full tables by default; use the production Dockerfile for distribution " +"builds. `PERRY_DEV_RUSTFLAGS` customizes development flags and must preserve " +"`-C force-unwind-tables=yes`." +msgstr "" + +#: src/contributing/building.md:104 +msgid "" +"The first build loads the workspace. Later supported edits use Blaze while " +"mbx caches dependencies and manages worktree targets. Explicit `--release` " +"and `--profile` builds stay on Cargo and mbx. The Rust unit-test CI job uses " +"the same prebuilts and mbx object cache. Other production checks retain the " +"normal compiler path. The tools remain experimental. In a Linux ARM64 " +"development image, `perry-native-registration` passed 27 tests through " +"Blaze. The `no_std` `perry-abi` crate passed four tests only after Blaze " +"reported missing `lang_start` and fell back to Cargo. Full workspace " +"compatibility is not established. Linux ARM64 and Apple Silicon passed a " +"separate edited-program probe that also verifies an mbx dependency cache hit." +msgstr "" + +#: src/contributing/building.md:115 msgid "Compiler cache for ephemeral worktrees" msgstr "Compiler-Cache für ephemere Worktrees" -#: src/contributing/building.md:85 +#: src/contributing/building.md:117 msgid "" "Short-lived worktrees and coding agents can opt into a compiler cache shared " "outside the repository. Install `sccache` in your user environment, then run " @@ -90736,16 +90740,16 @@ msgstr "" "Cache außerhalb des Repositorys freigeben. Installieren Sie `sccache` in " "Ihrer Benutzerumgebung, dann führen Sie Cargo durch den Wrapper:" -#: src/contributing/building.md:91 +#: src/contributing/building.md:123 #, fuzzy msgid "# Slim, optimized developer CLI\n" msgstr "# Schlanker, optimierter Entwickler CLI" -#: src/contributing/building.md:97 +#: src/contributing/building.md:129 msgid "" "The wrapper disables Cargo incremental compilation because `sccache` cannot " -"cache incremental artifacts. It stores compiler objects under " -"`${XDG_CACHE_HOME:-$HOME/.cache}/perry/sccache` by default, not in the " +"cache incremental artifacts. It stores compiler objects under `" +"${XDG_CACHE_HOME:-$HOME/.cache}/perry/sccache` by default, not in the " "worktree; set `SCCACHE_DIR` or `SCCACHE_CACHE_SIZE` to override its `12G` " "cache policy. It does not install or configure `sccache` globally." msgstr "" @@ -90756,7 +90760,7 @@ msgstr "" "um seine `12G`-Cache-Richtlinie zu überschreiben. Es installiert oder " "konfiguriert `sccache` nicht global." -#: src/contributing/building.md:103 +#: src/contributing/building.md:135 msgid "" "In a long-lived worktree, use ordinary `cargo` commands instead so the local " "incremental cache remains available. Cache benefit depends on the compiler, " @@ -90767,16 +90771,16 @@ msgstr "" "Benefit hängt vom Compiler, Flags, Abhängigkeiten und davon ab, wie viel " "Vorarbeiten der Cache wiederverwenden kann." -#: src/contributing/building.md:107 +#: src/contributing/building.md:139 msgid "Build Specific Crates" msgstr "Spezifische Crates bauen" -#: src/contributing/building.md:110 +#: src/contributing/building.md:142 #, fuzzy msgid "# Runtime only (must rebuild stdlib too!)\n" msgstr "# Nur Laufzeit (muss auch stdlib neu aufbauen!)" -#: src/contributing/building.md:112 +#: src/contributing/building.md:144 #, fuzzy msgid "" "# The .a static archives are emitted by separate wrapper crates (#5422), so " @@ -90785,7 +90789,7 @@ msgstr "" "# Die .a statischen Archive werden durch separate Wrapperkisten (#5422) " "emittiert, so dass ein" -#: src/contributing/building.md:114 +#: src/contributing/building.md:146 #, fuzzy msgid "" "# explicitly when you need libperry_runtime.a / libperry_stdlib.a (e.g. to " @@ -90794,23 +90798,23 @@ msgstr "" "# explizit, wenn Sie libperry_runtime.a / libperry_stdlib.a benötigen (e.g. " "zum Verlinken" -#: src/contributing/building.md:118 +#: src/contributing/building.md:150 msgid "" "# `perry` refuses to link workspace archives that lack a GC-effects sidecar\n" msgstr "" -#: src/contributing/building.md:120 +#: src/contributing/building.md:152 msgid "" "\"the runtime GC effects sidecar cannot speak for the archives this program\n" "# links\"" msgstr "" -#: src/contributing/building.md:123 +#: src/contributing/building.md:155 #, fuzzy msgid "# Codegen only\n" msgstr "# Nur Codegen" -#: src/contributing/building.md:128 +#: src/contributing/building.md:160 msgid "" "**Important**: When rebuilding `perry-runtime`, you must also rebuild `perry-" "stdlib` because `libperry_stdlib.a` embeds perry-runtime as a static " @@ -90820,11 +90824,11 @@ msgstr "" "stdlib` neu bauen, weil `libperry_stdlib.a` perry-runtime als statische " "Abhängigkeit einbettet." -#: src/contributing/building.md:130 +#: src/contributing/building.md:162 msgid "Slim developer CLI" msgstr "Schlanker Entwickler CLI" -#: src/contributing/building.md:132 +#: src/contributing/building.md:164 msgid "" "The default build is the full official CLI. For compiler work you can build " "a slimmer CLI that omits the publish / mobile / updater / native / audit " @@ -90835,7 +90839,7 @@ msgstr "" "native / audit Befehle und die nicht-native Codegen Backends (#5422) " "auslässt:" -#: src/contributing/building.md:140 +#: src/contributing/building.md:172 msgid "" "`dev-cli` keeps `compile` / `run` / `check` / `types` / `cache` / `dev`. " "Disabled commands drop out of `--help`, and disabled `--target` backends " @@ -90849,16 +90853,16 @@ msgstr "" "Siehe `crates/perry/Cargo.toml` für die vollständige Funktionsliste (`full-" "cli`, `publish-cli`, `backend-wasm`, ...)." -#: src/contributing/building.md:145 +#: src/contributing/building.md:177 msgid "Run Tests" msgstr "Tests ausführen" -#: src/contributing/building.md:148 +#: src/contributing/building.md:180 #, fuzzy msgid "# Product unit targets\n" msgstr "# Produkt Einzelziele" -#: src/contributing/building.md:150 +#: src/contributing/building.md:182 #, fuzzy msgid "" "# Inspect the nightly CI test scope (all Linux-compatible test crates)\n" @@ -90866,43 +90870,43 @@ msgstr "" "# Prüfen Sie den nächtlichen CI-Testbereich (alle Linux-kompatiblen " "Testkisten)" -#: src/contributing/building.md:153 +#: src/contributing/building.md:185 #, fuzzy msgid "# Specific crate\n" msgstr "# Spezifische Kiste" -#: src/contributing/building.md:159 +#: src/contributing/building.md:191 msgid "Compile and Run TypeScript" msgstr "TypeScript kompilieren und ausführen" -#: src/contributing/building.md:162 +#: src/contributing/building.md:194 #, fuzzy msgid "# Compile a TypeScript file\n" msgstr "# Eine TypeScript-Datei kompilieren" -#: src/contributing/building.md:165 +#: src/contributing/building.md:197 #, fuzzy msgid "# Debug: print HIR\n" msgstr "# Debug: HIR drucken" -#: src/contributing/building.md:170 +#: src/contributing/building.md:202 msgid "Development Workflow" msgstr "Entwicklungs-Workflow" -#: src/contributing/building.md:172 +#: src/contributing/building.md:204 msgid "Make changes to the relevant crate" msgstr "Änderungen an der relevanten Crate vornehmen" -#: src/contributing/building.md:173 +#: src/contributing/building.md:205 msgid "`cargo check -p perry` for fast product feedback" msgstr "`cargo check -p perry` für schnelles Produkt feedback" -#: src/contributing/building.md:174 +#: src/contributing/building.md:206 msgid "Run tests for the crates affected by the change" msgstr "" "Durchführen von Tests für die Kisten, die von der Änderung betroffen sind" -#: src/contributing/building.md:175 +#: src/contributing/building.md:207 msgid "" "Test with a real TypeScript file: `cargo run --release -- test.ts -o test " "&& ./test`" @@ -90910,22 +90914,22 @@ msgstr "" "Mit einer echten TypeScript-Datei testen: `cargo run --release -- test.ts -o " "test && ./test`" -#: src/contributing/building.md:203 +#: src/contributing/building.md:235 msgid "[Architecture](architecture.md) — Crate map and pipeline overview" msgstr "[Architektur](architecture.md) — Crate-Karte und Pipeline-Überblick" -#: src/contributing/building.md:204 +#: src/contributing/building.md:236 #, fuzzy msgid "See `AGENTS.md` for implementation pitfalls" msgstr "" "Siehe `CLAUDE.md` für detaillierte Implementierungs-Hinweise und Fallstricke" -#: src/contributing/building.md:206 +#: src/contributing/building.md:238 #, fuzzy msgid "Development debug information" msgstr "Entwicklungs-Builds" -#: src/contributing/building.md:208 +#: src/contributing/building.md:240 msgid "" "`dev` (including tests) and `perry-dev` use `line-tables-only` debug " "information to retain file/line backtraces while reducing build storage and " @@ -90934,26 +90938,27 @@ msgid "" "stripping is unchanged." msgstr "" -#: src/contributing/building.md:213 +#: src/contributing/building.md:245 msgid "" "The policy is checked by `python3 scripts/check_dev_debug.py` in PR lint." msgstr "" -#: src/contributing/building.md:215 +#: src/contributing/building.md:247 msgid "Cached Linux linking" msgstr "" -#: src/contributing/building.md:217 +#: src/contributing/building.md:249 msgid "" -"Use `make mbx-deps` and the cached Make targets for local builds. Linux GNU " -"x86_64 and AArch64 targets use mold 2.42.0 via `.mbx.toml`; install `clang` " -"on the build host. MBX downloads the pinned mold release and verifies its " -"GitHub SHA-256 digest. macOS, Windows, Android and custom musl linker paths " -"keep the system linker. The core-runtime and Linux release scripts require " -"MBX instead of silently bypassing the cache when it is missing." +"The development Make target provisions its cache automatically. Production " +"builds use `make mbx-deps` first. Linux GNU x86_64 and AArch64 targets use " +"mold 2.42.0 via `.mbx.toml`; install `clang` on the build host. MBX " +"downloads the pinned mold release and verifies its GitHub SHA-256 digest. " +"macOS, Windows, Android and custom musl linker paths keep the system linker. " +"The core-runtime and Linux release scripts require MBX instead of silently " +"bypassing the cache when it is missing." msgstr "" -#: src/contributing/building.md:224 +#: src/contributing/building.md:257 msgid "" "Use `MBX_LINKER=system` explicitly for linker bisection. Do not replace " "`RUSTFLAGS`: Perry requires the unwind-table and x86_64 frame-pointer flags " @@ -90963,13 +90968,13 @@ msgid "" "comparisons." msgstr "" -#: src/contributing/building.md:230 +#: src/contributing/building.md:263 msgid "" "See [MBX managed linkers](https://mr-boxington.jdx.dev/linkers) and [mold]" "(https://github.com/rui314/mold)." msgstr "" -#: src/contributing/building.md:233 +#: src/contributing/building.md:266 msgid "" "For dedicated Linux agent hosts, use [compressed ZFS build storage](build-" "storage.md) and `make build-agent` to check storage before building." @@ -91514,8 +91519,8 @@ msgstr "2a. Rezept für Simulatoren (iOS / tvOS)" #: src/contributing/releasing.md:187 msgid "" "`perry-ui-ios` and `perry-ui-tvos` honor `PERRY_UI_TEST_MODE=1` — when set, " -"the app renders one frame, optionally writes a screenshot to " -"`$PERRY_UI_SCREENSHOT_PATH`, and exits cleanly. Combine with `xcrun simctl` " +"the app renders one frame, optionally writes a screenshot to `" +"$PERRY_UI_SCREENSHOT_PATH`, and exits cleanly. Combine with `xcrun simctl` " "to verify a doc-example runs without a human:" msgstr "" "`perry-ui-ios` und `perry-ui-tvos` ehren `PERRY_UI_TEST_MODE=1` — wenn " @@ -91926,6 +91931,764 @@ msgstr "" "Z`; fügen Sie `publish_npm=true` nur dann hinzu, wenn das idempotent npm " "Bein selbst ebenfalls erneut getestet werden muss." +#~ msgid "" +#~ "Perry compiles TypeScript to native machine code via LLVM. There's no " +#~ "interpreter, no VM, no isolate. A function pointer is just a function " +#~ "pointer — it's valid on any thread." +#~ msgstr "" +#~ "Perry kompiliert TypeScript via LLVM zu nativem Maschinencode. Es gibt " +#~ "keinen Interpreter, keine VM, kein Isolate. Ein Funktionszeiger ist " +#~ "einfach ein Funktionszeiger — er ist auf jedem Thread gültig." + +#~ msgid "" +#~ "Each thread gets its own memory arena (bump allocator) and garbage " +#~ "collector. No synchronization overhead during computation. When a thread " +#~ "finishes, its arena is freed automatically." +#~ msgstr "" +#~ "Jeder Thread erhält seine eigene Speicher-Arena (Bump-Allokator) und " +#~ "Garbage Collector. Kein Synchronisations-Overhead während der Berechnung. " +#~ "Wenn ein Thread endet, wird seine Arena automatisch freigegeben." + +#~ msgid "" +#~ "Perry includes a mark-sweep garbage collector. It runs automatically when " +#~ "memory pressure is detected (~8MB arena blocks), but you can also trigger " +#~ "it manually:" +#~ msgstr "" +#~ "Perry enthält einen Mark-Sweep-Garbage-Collector. Er läuft automatisch, " +#~ "wenn Speicherdruck erkannt wird (~8 MB Arena-Blöcke), aber Sie können ihn " +#~ "auch manuell auslösen:" + +#~ msgid "" +#~ "The GC uses conservative stack scanning to find roots and supports arena-" +#~ "allocated objects (arrays, objects) and malloc-allocated objects " +#~ "(strings, closures, promises, BigInts, errors)." +#~ msgstr "" +#~ "Der GC verwendet konservatives Stack-Scanning, um Roots zu finden, und " +#~ "unterstützt arena-allokierte Objekte (Arrays, Objekte) und malloc-" +#~ "allokierte Objekte (Strings, Closures, Promises, BigInts, Errors)." + +#~ msgid "" +#~ "There are no guarantees for every dynamic JavaScript pattern (the " +#~ "[Limitations](limitations.md) still apply — no `eval`, no general dynamic " +#~ "`require()`), but most plain JavaScript projects compile and run." +#~ msgstr "" +#~ "Es gibt keine Garantie für jedes dynamische JavaScript-Muster (das " +#~ "[Limitations](limitations.md) gilt immer noch — kein `eval`, kein " +#~ "allgemeines dynamisches `require()`), aber die meisten einfachen " +#~ "JavaScript-Projekte kompilieren und laufen." + +#~ msgid "" +#~ "Perry erases types at compile time, similar to how `tsc` removes type " +#~ "annotations when emitting JavaScript. However, Perry also performs type " +#~ "inference to generate efficient native code." +#~ msgstr "" +#~ "Perry löscht Typen zur Compile-Zeit, ähnlich wie `tsc` Typ-Annotationen " +#~ "beim Emittieren von JavaScript entfernt. Perry führt jedoch zusätzlich " +#~ "Typinferenz durch, um effizienten nativen Code zu generieren." + +#~ msgid "" +#~ "Common TypeScript utility types are erased at compile time (they don't " +#~ "affect code generation):" +#~ msgstr "" +#~ "Übliche TypeScript-Utility-Typen werden zur Compile-Zeit gelöscht (sie " +#~ "beeinflussen die Code-Generierung nicht):" + +#~ msgid "" +#~ "At runtime, all values are NaN-boxed — the generic parameter doesn't " +#~ "affect code generation." +#~ msgstr "" +#~ "Zur Laufzeit sind alle Werte NaN-geboxt — der generische Parameter " +#~ "beeinflusst die Code-Generierung nicht." + +#~ msgid "" +#~ "This resolves cross-file types, interfaces, and generics via an IPC " +#~ "protocol. It falls back gracefully if the type checker is not installed." +#~ msgstr "" +#~ "Dies löst dateiübergreifende Typen, Interfaces und Generics über ein IPC-" +#~ "Protokoll auf. Es fällt elegant zurück, wenn der Typprüfer nicht " +#~ "installiert ist." + +#~ msgid "" +#~ "Union types are recognized syntactically but don't affect code generation:" +#~ msgstr "" +#~ "Union-Typen werden syntaktisch erkannt, beeinflussen aber nicht die Code-" +#~ "Generierung:" + +#~ msgid "No eval() or Dynamic Code" +#~ msgstr "Kein eval() oder dynamischer Code" + +#~ msgid "" +#~ "Perry compiles to native code ahead of time. It cannot evaluate a code " +#~ "string that is only known at runtime. A _constant_ code string is the " +#~ "exception — `eval(\"1 + 1\")` and `new Function(\"a\", \"b\", \"return a " +#~ "+ b\")` are compiled to real native functions (#1679). Only a body built " +#~ "from runtime data hits this limit:" +#~ msgstr "" +#~ "Perry kompiliert im Voraus zu nativem Code. Es kann keinen Code String " +#~ "auswerten, der nur zur Laufzeit bekannt ist. Ein _constant_-Codestring " +#~ "ist die Ausnahme — `eval(\"1 + 1\")` und `new Function(\"a\", \"b\", " +#~ "\"return a + b\")` werden zu echten nativen Funktionen kompiliert " +#~ "(#1679). Nur ein aus Laufzeitdaten gebauter Körper trifft dieses Limit:" + +#~ msgid "" +#~ "```typescript,no-test\n" +#~ "// Constant body → compiled natively (works)\n" +#~ "const add = new Function(\"a\", \"b\", \"return a + b\");\n" +#~ "\n" +#~ "// Runtime-built body → cannot be compiled ahead of time\n" +#~ "function run(src: string) { return new Function(`return (${src})`)(); }\n" +#~ "```" +#~ msgstr "" +#~ "```typescript,no-test\n" +#~ "// Constant body → compiled natively (works)\n" +#~ "const add = new Function(\"a\", \"b\", \"return a + b\");\n" +#~ "\n" +#~ "// Runtime-built body → cannot be compiled ahead of time\n" +#~ "function run(src: string) { return new Function(`return (${src})`)(); }\n" +#~ "```" + +#~ msgid "Default: deferred runtime error + compile-time notice (#5206)" +#~ msgstr "" +#~ "Voreinstellung: Latend Runtime Fehler + Compil-Time Ankündigung (#5206)" + +#~ msgid "" +#~ "By default a runtime-unknown `eval(...)` / `new Function()` " +#~ "site does **not** block the build. Perry compiles it to a value that " +#~ "throws a descriptive `Error` _only if it is actually reached_ (an " +#~ "`eval(...)` throws when evaluated; a `new Function(...)` returns a " +#~ "function that throws when called), and prints a single end-of-compile " +#~ "notice listing every degraded site:" +#~ msgstr "" +#~ "Standardmäßig ist eine runtime-unbekannte `eval(...)` / `new " +#~ "Function()`-Site dies **nicht** Blockieren Sie den Bau. " +#~ "Perry kompiliert es zu einem Wert, der eine beschreibende `Error` _nur " +#~ "dann wirft, wenn es tatsächlich erreicht wird_ (ein `eval(...)` wirft, " +#~ "wenn ausgewertet; ein `new Function(...)` gibt eine Funktion zurück, die " +#~ "beim Aufruf wirft), und druckt eine einzelne End-of-Compile-Notiz, die " +#~ "jede degradierte Site auflistet:" + +#~ msgid "" +#~ "This lets a single such call in a cold path ship without aborting the " +#~ "whole build, while still failing loudly (and catchably) if that path runs." +#~ msgstr "" +#~ "Dies lässt einen einzigen solchen Anruf in einem kalten Pfad Schiff, ohne " +#~ "den gesamten Bau zu beenden, während immer noch laut (und eingängig) " +#~ "scheitern, wenn dieser Pfad läuft." + +#~ msgid "Dynamic `import()` with a runtime-computed specifier (#5230)" +#~ msgstr "" +#~ "Dynamisches `import()` mit einem Laufzeit-Computed Specifier (#5230)" + +#, fuzzy +#~ msgid "" +#~ "For code modules, a dynamic `import(spec)` whose `spec` is only known at " +#~ "runtime (a plugin loader building a path from a variable) is subject to " +#~ "the **same defer/notice/strict policy** as `eval`. By default it compiles " +#~ "to a rejected `Promise` carrying a descriptive `Error` (so `await " +#~ "import(spec)` throws _only if reached_), is listed in the shared notice " +#~ "above under the `import(...)` kind, and does **not** abort the build. " +#~ "This lets an app with a plugin-loader path compile and run its core, with " +#~ "only the plugin-load path throwing if exercised." +#~ msgstr "" +#~ "Ein dynamischer `import(spec)`, dessen `spec` nur zur Laufzeit bekannt " +#~ "ist (ein Plugin Loader, der einen Pfad aus einer Variable baut) " +#~ "unterliegt dem **gleiche defer/notice/strict-Richtlinie** als `eval`. . " +#~ "Standardmäßig kompiliert es zu einem abgelehnten `Promise` mit einer " +#~ "beschreibenden `Error` (so `await import(spec)` wirft _nur wenn " +#~ "erreicht_), ist in der oben genannten gemeinsamen Mitteilung unter der " +#~ "Art `import(...)` aufgeführt, und tut **nicht** Abbruch des Baus. Dadurch " +#~ "kann eine App mit einem Plugin-loader-Pfad kompilieren und ihren Kern " +#~ "ausführen, wobei nur der Plugin-load-Pfad werfen, wenn ausgeübt." + +#~ msgid "" +#~ "Resolvable specifiers are unaffected and still compile + load: string " +#~ "literals (`import(\"./mod.js\")`), ternaries of resolvable arms, template " +#~ "literals over `const` locals (``import(`./${KIND}.js`)``), finite string-" +#~ "literal-union parameters, and directory globs." +#~ msgstr "" +#~ "Lösbare Spezifikatoren sind unbeeinflusst und kompilieren immer noch + " +#~ "Last: String-Literative (`import(\"./mod.js\")`), Ternarien von " +#~ "resolvablen Armen, Template-Literative über `const`-Locals (``import(`./" +#~ "${KIND}.js`)``), endliche String-Literar-Union-Parameter und Verzeichnis-" +#~ "Globs." + +#~ msgid "" +#~ "```typescript,no-test\n" +#~ "// Resolvable → compiled + loaded as today.\n" +#~ "const real = await import(\"./real.js\");\n" +#~ "\n" +#~ "// Runtime-computed → deferred (default): throws only if this line runs.\n" +#~ "async function loadPlugin(name: string) {\n" +#~ " return await import(name + \".js\");\n" +#~ "}\n" +#~ "```" +#~ msgstr "" +#~ "```typescript,no-test\n" +#~ "// Resolvable → compiled + loaded as today.\n" +#~ "const real = await import(\"./real.js\");\n" +#~ "\n" +#~ "// Runtime-computed → deferred (default): throws only if this line runs.\n" +#~ "async function loadPlugin(name: string) {\n" +#~ " return await import(name + \".js\");\n" +#~ "}\n" +#~ "```" + +#~ msgid "Strict mode: refuse at compile time" +#~ msgstr "Strenger Modus: zur Kompilierung Zeit ablehnen" + +#~ msgid "" +#~ "To make every runtime-unknown site a hard compile-time error instead, opt " +#~ "into strict-eval mode by any of:" +#~ msgstr "" +#~ "Um jede Runtime-Unbekannte Site stattdessen zu einem harten Compil-Time-" +#~ "Fehler zu machen, wählen Sie in den strengen Auswertmodus durch einen der:" + +#~ msgid "the `--strict-eval` flag on `perry compile`," +#~ msgstr "die `--strict-eval`-Flagge auf `perry compile`," + +#~ msgid "" +#~ "`\"perry\": { \"eval\": \"error\" }` (or `\"perry\": { \"strict\": true }" +#~ "`) in `package.json`, or" +#~ msgstr "" +#~ "`\"perry\": { \"eval\": \"error\" }` (oder `\"perry\": { \"strict\": " +#~ "true }`) in `package.json` oder" + +#~ msgid "`[perry]` `eval = \"error\"` (or `strict = true`) in `perry.toml`." +#~ msgstr "" +#~ "`[perry]` `eval = \"error\"` (oder `strict = true`) in `perry.toml`." + +#~ msgid "" +#~ "`perry.eval` accepts `\"defer\"` (the default) or `\"error\"`. Precedence " +#~ "is package.json/perry.toml config → `--strict-eval` (opts in). The legacy " +#~ "`PERRY_ALLOW_EVAL=1` environment variable still works: it forces non-" +#~ "strict (defer) mode for a one-off build, overriding any strict flag/" +#~ "config." +#~ msgstr "" +#~ "`perry.eval` akzeptiert `\"defer\"` (der Standard) oder `\"error\"`. " +#~ "Vorrang ist package.json/perry.toml config → `--strict-eval` (opts in). " +#~ "Die alte `PERRY_ALLOW_EVAL=1` Umgebungsvariable funktioniert noch: Sie " +#~ "zwingt den nicht-strikten (Defer) Modus für einen einmaligen Build und " +#~ "überschreibt jeglichen strengen flag/config" + +#~ msgid "" +#~ "The same strict controls apply to runtime-computed dynamic `import()` " +#~ "(#5230). The broad `perry.strict = true` covers both eval and dynamic " +#~ "import. For a knob scoped to dynamic imports only, use `--strict-dynamic-" +#~ "import` or `\"perry\": { \"dynamicImport\": \"error\" }` (accepts `\"defer" +#~ "\"` (default) or `\"error\"`); the dedicated knob overrides the broad " +#~ "`perry.strict` for import sites. `PERRY_ALLOW_EVAL=1` is the shared AOT " +#~ "escape hatch — it forces defer for both eval and dynamic import." +#~ msgstr "" +#~ "Die gleichen strengen Kontrollen gelten für die Laufzeit berechnet " +#~ "dynamische `import()` (#5230). Der breite `perry.strict = true` deckt " +#~ "sowohl Auswert als auch dynamischen Import ab. Für einen Knopf, der nur " +#~ "für dynamische Importe ausgelegt ist, verwenden Sie `--strict-dynamic-" +#~ "import` oder `\"perry\": { \"dynamicImport\": \"error\" }` (akzeptiert `" +#~ "\"defer\"` (Standard) oder `\"error\"`); der dedizierte Knopf " +#~ "überschreibt den breiten `perry.strict` für Importseiten. " +#~ "`PERRY_ALLOW_EVAL=1` ist die geteilte AOT-Ausweichluke – es Kräfte " +#~ "verschieben sich sowohl für Evaluierung und dynamischen Import." + +#~ msgid "" +#~ "Test262 rows that only observe parsing or executing a code string remain " +#~ "intentional AOT exclusions, not runtime dynamic-code work. This includes " +#~ "the `language/white-space/comment-{multi,single}-{form-feed,horizontal-" +#~ "tab,nbsp,space,vertical-tab}.js` rows and the direct-eval reference row " +#~ "`language/types/reference/8.7.2-1-s.js`; they map to the AOT eval tracker " +#~ "(#1677), eval classifier diagnostics (#1678), and the limited literal " +#~ "`Function` folding work (#1679)." +#~ msgstr "" +#~ "Test262-Zeilen, die nur das Parsen oder Ausführen eines Code-Strings " +#~ "beobachten, sind bewusste AOT-Ausschlüsse und keine dynamischen Code-" +#~ "Laufzeitarbeiten. Dazu gehören die Zeilen `language/white-space/comment-" +#~ "{multi,single}-{form-feed,horizontal-tab,nbsp,space,vertical-tab}.js` " +#~ "sowie die Direct-Eval-Referenzzeile `language/types/reference/8.7.2-1-s." +#~ "js`; sie sind dem AOT-Eval-Tracker (#1677), den Eval-Classifier-Diagnosen " +#~ "(#1678) und der eingeschränkten literalen `Function`-Faltungsarbeit " +#~ "(#1679) zugeordnet." + +#~ msgid "No User-Space CommonJS require()" +#~ msgstr "Kein Benutzer-Raum CommonJS require()" + +#~ msgid "Use static ESM imports in Perry source:" +#~ msgstr "Verwende statische ESM-Imports in Perry-Quellcode:" + +#~ msgid "" +#~ "```text\n" +#~ "// Supported\n" +#~ "import { foo } from \"./module\";\n" +#~ "\n" +#~ "// Not supported\n" +#~ "const mod = require(\"./module\");\n" +#~ "const mod = await import(\"./module\");\n" +#~ "```" +#~ msgstr "" +#~ "```text\n" +#~ "// Supported\n" +#~ "import { foo } from \"./module\";\n" +#~ "\n" +#~ "// Not supported\n" +#~ "const mod = require(\"./module\");\n" +#~ "const mod = await import(\"./module\");\n" +#~ "```" + +#~ msgid "" +#~ "Perry has internal CommonJS compatibility paths for some npm package " +#~ "wrappers, but user-written modules should use static `import` " +#~ "declarations." +#~ msgstr "" +#~ "Perry verfügt über interne CommonJS-Kompatibilitätspfade für einige npm-" +#~ "Package-Wrapper, aber benutzerdefinierte Module sollten statische " +#~ "`import`-Deklarationen verwenden." + +#~ msgid "" +#~ "**JavaScript source compiles too.** Perry accepts `.js`, `.cjs`, `.mjs`, " +#~ "and `.jsx` files as compiler input — they are parsed as JavaScript and " +#~ "lowered through the same native pipeline as TypeScript, so no type " +#~ "annotations are required. The limitations on this page still apply (no " +#~ "`eval`, no general dynamic `require()`, etc.), but plain JavaScript " +#~ "projects compile and run in most cases." +#~ msgstr "" +#~ "**JavaScript-Quelle kompiliert auch.** Perry akzeptiert `.js`, `.cjs`, `." +#~ "mjs` und `.jsx`-Dateien als Compiler-Eingabe – sie werden als JavaScript " +#~ "geparst und durch dieselbe native Pipeline wie TypeScript abgesenkt, so " +#~ "dass keine Typ-Annotationen erforderlich sind. Die Einschränkungen auf " +#~ "dieser Seite gelten immer noch (kein `eval`, keine allgemeine dynamische " +#~ "`require()`, etc.), aber einfache JavaScript-Projekte kompilieren und " +#~ "laufen in den meisten Fällen." + +#~ msgid "Weak References Retain Their Targets" +#~ msgstr "Weak References behalten ihre Ziele" + +#~ msgid "" +#~ "`WeakMap`, `WeakSet`, `WeakRef`, and `FinalizationRegistry` are " +#~ "implemented and their APIs behave as expected — `set` / `get` / `has` / " +#~ "`delete`, `add`, `deref()`, and `register` / `unregister` all work and " +#~ "return the right values. `WeakMap` and `WeakSet` use **reference** " +#~ "equality, so two distinct objects never collide on the same slot." +#~ msgstr "" +#~ "`WeakMap`, `WeakSet`, `WeakRef` und `FinalizationRegistry` sind " +#~ "implementiert und ihre APIs verhalten sich wie erwartet — `set` / `get` / " +#~ "`has` / `delete`, `add`, `deref()` sowie `register` / `unregister` " +#~ "funktionieren alle und liefern die richtigen Werte. `WeakMap` und " +#~ "`WeakSet` verwenden **Referenz**-Gleichheit, sodass zwei verschiedene " +#~ "Objekte nie denselben Slot belegen." + +#~ msgid "" +#~ "The one caveat is that Perry's garbage collector does not yet treat these " +#~ "references as _weak_, so targets are **retained rather than collected**. " +#~ "The current runtime stores `WeakRef` targets and `FinalizationRegistry` " +#~ "registrations in ordinary object/array fields (`crates/perry-runtime/src/" +#~ "weakref.rs`), and the adjacent GC root scanners do not have a weak-slot " +#~ "clearing/finalizer queue hook yet. In practice:" +#~ msgstr "" +#~ "Der einzige Vorbehalt ist, dass Perrys Garbage Collector diese Referenzen " +#~ "noch nicht als _schwach_ behandelt, sodass Ziele **behalten statt " +#~ "eingesammelt** werden. Die aktuelle Runtime speichert `WeakRef`-Ziele und " +#~ "`FinalizationRegistry`-Registrierungen in gewöhnlichen Objekt-/Array-" +#~ "Feldern (`crates/perry-runtime/src/weakref.rs`), und die benachbarten GC-" +#~ "Root-Scanner verfügen noch nicht über einen Weak-Slot-Clearing-/Finalizer-" +#~ "Queue-Hook. In der Praxis bedeutet das:" + +#~ msgid "" +#~ "`WeakRef.deref()` always returns the original target (it is never " +#~ "reported as collected)." +#~ msgstr "" +#~ "`WeakRef.deref()` gibt immer das ursprüngliche Ziel zurück (es wird nie " +#~ "als eingesammelt gemeldet)." + +#~ msgid "" +#~ "`FinalizationRegistry` records registrations but never fires its cleanup " +#~ "callback." +#~ msgstr "" +#~ "`FinalizationRegistry` zeichnet Registrierungen auf, löst aber niemals " +#~ "seinen Cleanup-Callback aus." + +#~ msgid "" +#~ "`WeakMap` / `WeakSet` keep their keys alive (they behave like a reference-" +#~ "keyed `Map` / `Set`)." +#~ msgstr "" +#~ "`WeakMap` / `WeakSet` halten ihre Schlüssel am Leben (sie verhalten sich " +#~ "wie eine referenzgeschlüsselte `Map` / `Set`)." + +#~ msgid "" +#~ "This is safe for **correctness** — code that reads through these APIs " +#~ "gets the right values. It only matters if you depend on collection " +#~ "_timing_ to reclaim memory or to run finalizer side effects." +#~ msgstr "" +#~ "Dies ist **korrekt** im Sinne der Funktionalität — Code, der über diese " +#~ "APIs liest, erhält die richtigen Werte. Es ist nur dann relevant, wenn " +#~ "man auf den Einsammlungs-_Zeitpunkt_ angewiesen ist, um Speicher " +#~ "freizugeben oder Finalizer-Nebeneffekte auszulösen." + +#~ msgid "" +#~ "**Not supported**: Packages requiring native addons (`.node` files), " +#~ "`eval()`, dynamic `require()`, or Node.js internals." +#~ msgstr "" +#~ "**Nicht unterstützt**: Pakete, die native Add-ons (`.node`-Dateien), " +#~ "`eval()`, dynamisches `require()` oder Node.js-Interna benötigen." + +#~ msgid "Dynamic Behavior" +#~ msgstr "Dynamisches Verhalten" + +#~ msgid "" +#~ "For cases where you need dynamic behavior, use the JavaScript runtime " +#~ "fallback:" +#~ msgstr "" +#~ "Für Fälle, in denen Sie dynamisches Verhalten benötigen, nutzen Sie den " +#~ "JavaScript-Runtime-Fallback:" + +#~ msgid "" +#~ "```text\n" +#~ "import { jsEval } from \"perry/jsruntime\";\n" +#~ "// Routes specific code through QuickJS for dynamic evaluation\n" +#~ "```" +#~ msgstr "" +#~ "```text\n" +#~ "import { jsEval } from \"perry/jsruntime\";\n" +#~ "// Routes specific code through QuickJS for dynamic evaluation\n" +#~ "```" + +#~ msgid "" +#~ "Perry gives you real OS threads with a one-line API. No worker setup, no " +#~ "message ports, no structured clone overhead. Just `parallelMap`, " +#~ "`parallelFilter`, and `spawn`." +#~ msgstr "" +#~ "Perry bietet Ihnen echte OS-Threads mit einer Einzeilen-API. Keine Worker-" +#~ "Einrichtung, keine Message Ports, kein Structured-Clone-Overhead. Nur " +#~ "`parallelMap`, `parallelFilter` und `spawn`." + +#~ msgid "" +#~ "This is something **no JavaScript runtime can do**. V8, Bun, and Deno are " +#~ "all locked to one thread per isolate. Perry compiles to native code — " +#~ "there are no isolates, no GIL, no structural limitations. Your code runs " +#~ "on real OS threads with the full power of every CPU core." +#~ msgstr "" +#~ "Dies ist etwas, das **keine JavaScript-Laufzeit kann**. V8, Bun und Deno " +#~ "sind alle auf einen Thread pro Isolate beschränkt. Perry kompiliert zu " +#~ "nativem Code — es gibt keine Isolate, keinen GIL, keine strukturellen " +#~ "Einschränkungen. Ihr Code läuft auf echten OS-Threads mit der vollen " +#~ "Leistung jedes CPU-Kerns." + +#~ msgid "Why This Matters" +#~ msgstr "Warum das wichtig ist" + +#~ msgid "" +#~ "JavaScript's single-threaded model is its biggest performance bottleneck. " +#~ "Here's how runtimes try to work around it:" +#~ msgstr "" +#~ "JavaScripts Single-Thread-Modell ist sein größter Performance-Engpass. So " +#~ "versuchen Laufzeiten, es zu umgehen:" + +#~ msgid "Runtime" +#~ msgstr "Laufzeit" + +#~ msgid "\"Multi-threading\"" +#~ msgstr "\"Multi-Threading\"" + +#~ msgid "Reality" +#~ msgstr "Realität" + +#~ msgid "**Node.js**" +#~ msgstr "**Node.js**" + +#~ msgid "" +#~ "Separate V8 isolates. Data copied via structured clone. ~2MB RAM per " +#~ "worker. Complex API." +#~ msgstr "" +#~ "Separate V8-Isolate. Daten werden via Structured Clone kopiert. ~2 MB RAM " +#~ "pro Worker. Komplexe API." + +#~ msgid "**Deno**" +#~ msgstr "**Deno**" + +#~ msgid "Same as Node — isolated heaps, message passing only." +#~ msgstr "Genauso wie Node — isolierte Heaps, nur Nachrichtenaustausch." + +#~ msgid "**Bun**" +#~ msgstr "**Bun**" + +#~ msgid "Same architecture. Faster structured clone, still isolated." +#~ msgstr "" +#~ "Gleiche Architektur. Schnellerer Structured Clone, weiterhin isoliert." + +#~ msgid "**Perry**" +#~ msgstr "**Perry**" + +#~ msgid "`parallelMap` / `spawn`" +#~ msgstr "`parallelMap` / `spawn`" + +#~ msgid "" +#~ "Real OS threads. Lightweight (8MB stack). One-line API. Compile-time " +#~ "safety." +#~ msgstr "" +#~ "Echte OS-Threads. Leichtgewichtig (8 MB Stack). Einzeilen-API. Compile-" +#~ "Zeit-Sicherheit." + +#~ msgid "" +#~ "The fundamental problem: V8 uses a garbage-collected heap that **cannot " +#~ "be shared** between threads. Every \"worker\" is an entirely separate " +#~ "JavaScript engine instance with its own heap, its own GC, and its own " +#~ "copy of your data." +#~ msgstr "" +#~ "Das fundamentale Problem: V8 verwendet einen Garbage-Collected-Heap, der " +#~ "**nicht zwischen Threads geteilt werden kann**. Jeder \"Worker\" ist eine " +#~ "vollständig separate JavaScript-Engine-Instanz mit eigenem Heap, eigenem " +#~ "GC und eigener Kopie Ihrer Daten." + +#~ msgid "" +#~ "Perry doesn't have this limitation. It compiles TypeScript to native " +#~ "machine code. Values are transferred between threads using zero-cost " +#~ "copies for numbers and efficient serialization for objects — no separate " +#~ "engine instances, no multi-megabyte overhead per thread." +#~ msgstr "" +#~ "Perry hat diese Einschränkung nicht. Es kompiliert TypeScript zu nativem " +#~ "Maschinencode. Werte werden zwischen Threads mit kostenlosen Kopien für " +#~ "Zahlen und effizienter Serialisierung für Objekte übertragen — keine " +#~ "separaten Engine-Instanzen, kein Multi-Megabyte-Overhead pro Thread." + +#~ msgid "" +#~ "Perry enforces thread safety **at compile time**. You don't need to think " +#~ "about race conditions, mutexes, or data corruption." +#~ msgstr "" +#~ "Perry erzwingt Thread-Sicherheit **zur Compile-Zeit**. Sie müssen nicht " +#~ "über Race Conditions, Mutexes oder Datenkorruption nachdenken." + +#~ msgid "" +#~ "Each worker thread has its own memory arena. Objects created on one " +#~ "thread can never be accessed from another thread. Values cross thread " +#~ "boundaries only through deep-copy serialization, which Perry handles " +#~ "automatically and invisibly." +#~ msgstr "" +#~ "Jeder Worker-Thread hat seine eigene Speicher-Arena. Auf einem Thread " +#~ "erstellte Objekte können nie von einem anderen Thread aus zugegriffen " +#~ "werden. Werte überschreiten Thread-Grenzen nur durch Deep-Copy-" +#~ "Serialisierung, die Perry automatisch und unsichtbar handhabt." + +#~ msgid "" +#~ "Fail the build if any runtime-unknown `eval(...)` / `new " +#~ "Function()` site is reachable. By default such a site is " +#~ "compiled to a deferred runtime error (throws only if reached) and a " +#~ "compile-time notice is printed. Also settable via `perry.eval = \"error" +#~ "\"` / `perry.strict = true` (package.json or perry.toml). " +#~ "`PERRY_ALLOW_EVAL=1` forces it off. See [Limitations](../language/" +#~ "limitations.md#no-eval-or-dynamic-code)." +#~ msgstr "" +#~ "Fehler beim Bauen, wenn irgendeine Runtime-unbekannte `eval(...)` / `new " +#~ "Function()`-Site erreichbar ist. Standardmäßig wird eine " +#~ "solche Site zu einem latenten Laufzeitfehler kompiliert (wird nur dann " +#~ "angezeigt, wenn sie erreicht wird) und eine Compil-Zeitanzeige wird " +#~ "gedruckt. Auch über `perry.eval = \"error\"` / `perry.strict = true` " +#~ "(package.json oder perry.toml) einstellbar. `PERRY_ALLOW_EVAL=1` zwingt " +#~ "es ab. Siehe [Limitations](../language/limitations.md#no-eval-or-dynamic-" +#~ "code)." + +#~ msgid "" +#~ "Fail the build if a dynamic `import(...)` has a runtime-computed (non-" +#~ "resolvable) specifier. By default such a site is compiled to a rejected " +#~ "`Promise` that throws a descriptive `Error` only if reached, and is " +#~ "listed in the same end-of-build notice as deferred eval sites. Also " +#~ "settable via `perry.dynamicImport = \"error\"` / `perry.strict = true` " +#~ "(package.json or perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. " +#~ "Resolvable forms (string literals, ternaries of resolvable arms, template " +#~ "literals over const locals, finite union-typed params, glob) are " +#~ "unaffected. See [Limitations](../language/limitations.md#no-eval-or-" +#~ "dynamic-code)." +#~ msgstr "" +#~ "Fehler beim Bauen, wenn ein dynamischer `import(...)` einen Runtime-" +#~ "Computed (nicht resolvable) Spekifikator hat. Standardmäßig wird eine " +#~ "solche Site zu einer abgelehnten `Promise` kompiliert, die nur dann eine " +#~ "beschreibende `Error` wirft, wenn sie erreicht wird, und wird in der " +#~ "gleichen End-of-Build-Notice als latente Eval-Sites aufgeführt. Auch über " +#~ "`perry.dynamicImport = \"error\"` / `perry.strict = true` (package.json " +#~ "oder perry.toml) einstellbar. `PERRY_ALLOW_EVAL=1` zwingt es ab. " +#~ "Auflösbare Formen (Zeichenketten-Literaturen, Ternaren der auflösbaren " +#~ "Arme, Schablone-Literaturen über Const-Lokale, endliche Unions-Typ-" +#~ "Params, Glob) bleiben unberührt. Siehe [Limitations](../language/" +#~ "limitations.md#no-eval-or-dynamic-code)." + +#~ msgid "" +#~ "If you've ever wondered \"does Perry use reference counting?\" — no. " +#~ "There is no `Rc` at runtime. Perry has a real tracing GC, described below." +#~ msgstr "" +#~ "Falls Sie sich jemals gefragt haben: „Verwendet Perry Reference " +#~ "Counting?“ — nein. Es gibt kein `Rc` zur Laufzeit. Perry besitzt einen " +#~ "echten Tracing-GC, der im Folgenden beschrieben wird." + +#~ msgid "" +#~ "Perry is single-threaded by default, and each thread owns its own heap. " +#~ "Sharing across threads happens via deep copy (`SerializedValue`), not " +#~ "shared memory, so the GC never has to synchronize across threads." +#~ msgstr "" +#~ "Perry ist standardmäßig single-threaded, und jeder Thread besitzt seinen " +#~ "eigenen Heap. Die Weitergabe zwischen Threads erfolgt über Deep Copy " +#~ "(`SerializedValue`), nicht über Shared Memory, sodass der GC nie " +#~ "threadübergreifend synchronisieren muss." + +#~ msgid "Within a thread, the heap is two arenas:" +#~ msgstr "Innerhalb eines Threads besteht der Heap aus zwei Arenas:" + +#~ msgid "" +#~ "**`ARENA`** — the nursery. New allocations land here. Carved into 1 MB " +#~ "blocks (since v0.5.196)." +#~ msgstr "" +#~ "**`ARENA`** — die Nursery. Neue Allokationen landen hier. Aufgeteilt in 1-" +#~ "MB-Blöcke (seit v0.5.196)." + +#~ msgid "" +#~ "**`OLD_ARENA`** — the old generation. Holds objects that have survived " +#~ "enough minor GCs to be tenured." +#~ msgstr "" +#~ "**`OLD_ARENA`** — die Old Generation. Enthält Objekte, die genug Minor-GC-" +#~ "Zyklen überlebt haben, um tenured zu werden." + +#~ msgid "" +#~ "Allocation goes through `gc_malloc(size, obj_type)` in the `gc/` module " +#~ "tree. LLVM-generated code emits calls to this for every object literal, " +#~ "array literal, closure capture, string concat, BigInt operation, etc. " +#~ "Going through the GC allocation funnel is how the collector accounts for " +#~ "memory and decides when to collect." +#~ msgstr "" +#~ "Die Zuordnung erfolgt durch `gc_malloc(size, obj_type)` im `gc/` " +#~ "Modulbaum. LLVM-generierter Code sendet Anrufe zu diesem für jedes Objekt " +#~ "wörtlich, Array wörtlich, Schließung Capture, String Concat, BigInt " +#~ "Betrieb, etc. Durch den GC-Zuweisungs-Trichter zu gehen ist, wie der " +#~ "Kollektor für Speicher Konten und entscheidet, wann zu sammeln." + +#~ msgid "" +#~ "One pointer-local analysis feeds two correct lowerings. On supported 64-" +#~ "bit AArch64/arm64 and x86-64 targets, native RS4GC statepoints plus " +#~ "Perry's compact stack map are the default. `arm64_32` watchOS, ARM64 " +#~ "Windows, and unsupported architectures use Perry's heap-backed shadow " +#~ "frames. The fallback is a root lowering, not an unrooted mode; see [the " +#~ "current GC page](garbage-collector.md#roots-by-target)." +#~ msgstr "" +#~ "Eine Pointer-lokale Analyse liefert zwei korrekte Absenkungen. Auf " +#~ "unterstützten 64-Bit AArch64/arm64 und x86-64 Targets sind native RS4GC " +#~ "Statepoints sowie Perry's kompakte Stack-Map die Standardeinstellung. " +#~ "`arm64_32` watchOS, ARM64 Windows und nicht unterstützte Architekturen " +#~ "verwenden Perrys hap-backed-Schattenrahmen. Der Fallback ist ein Root-" +#~ "Downing, kein unrooted Modus; siehe [die aktuelle GC-Seite](garbage-" +#~ "collector.md#roots-by-target)." + +#~ msgid "" +#~ "**The non-copying minor** uses two flag bits in `gc_flags` (`crates/perry-" +#~ "runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` on the first minor an " +#~ "object survives, `GC_FLAG_TENURED` on the second, at which point the " +#~ "object is logically promoted. The two-bit scheme avoids a counter field " +#~ "in the header, and a tenured object initially stays physically where it " +#~ "is — that promotion is a flag flip, not a copy. Whether such objects are " +#~ "then evacuated into `OLD_ARENA` (with every reference rewritten) is a " +#~ "policy decision taken per cycle from nursery/RSS pressure and measured " +#~ "movable candidates, and it requires generated write barriers to be active." +#~ msgstr "" +#~ "**Der nicht-kopierende Minderjährige** verwendet zwei Flag-Bits in " +#~ "`gc_flags` (`crates/perry-runtime/src/gc/types.rs`): " +#~ "`GC_FLAG_HAS_SURVIVED` bei der ersten Moll überlebt ein Objekt, " +#~ "`GC_FLAG_TENURED` bei der zweiten, an dem Punkt, an dem das Objekt " +#~ "logisch gefördert wird. Das Zwei-Bit-Schema vermeidet ein Gegenfeld im " +#~ "Header, und ein Tenured-Objekt bleibt zunächst physisch dort, wo es ist – " +#~ "dass Promotion ein Flag Flip ist, nicht eine Kopie. Ob solche Objekte " +#~ "dann in `OLD_ARENA` evakuiert werden (mit jeder Referenz neu geschrieben) " +#~ "ist eine politische Entscheidung, die pro Zyklus von nursery/RSS Druck " +#~ "und gemessen beweglichen Kandidaten getroffen wird, und es erfordert " +#~ "generierte Schreibbarrieren aktiv zu sein." + +#~ msgid "" +#~ "With generated write barriers active and policy evacuation allowed, " +#~ "stress-copy every marked non-pinned nursery object instead of only " +#~ "tenured survivors." +#~ msgstr "" +#~ "Wenn generierte Write Barriers aktiv und Policy-Evakuierung erlaubt sind, " +#~ "wird jedes markierte, nicht angepinnte Nursery-Objekt per Stress-Copy " +#~ "kopiert, anstatt nur tenurierte Überlebende." + +#~ msgid "" +#~ "**Current as of 2026-08-12.** This is the source of truth for the " +#~ "collector that ships. The [generational plan](https://github.com/PerryTS/" +#~ "perry/blob/main/docs/generational-gc-plan.md) and [statepoint experiment]" +#~ "(https://github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment." +#~ "md) are chronological evidence; their opening decisions describe the date " +#~ "they were written, not today's defaults." +#~ msgstr "" +#~ "**Aktuell ab 2026-08-12.** Dies ist die Quelle der Wahrheit für den " +#~ "Sammler, der Schiffe. Die [Erzeugungsplan](https://github.com/PerryTS/" +#~ "perry/blob/main/docs/generational-gc-plan.md) und [Statepoint-Experiment]" +#~ "(https://github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment." +#~ "md) sind chronologische Belege; ihre Eröffnungsentscheidungen beschreiben " +#~ "das Datum, an dem sie geschrieben wurden, nicht die heutigen " +#~ "Voreinstellungen." + +#~ msgid "" +#~ "This page deliberately names **no issue numbers**. A sentence of the form " +#~ "\"that phase is still unsliced (see the tracker)\" becomes false the " +#~ "moment the tracker closes, and nothing in the tree notices — which is " +#~ "exactly how three paragraphs here went stale inside one landing train. " +#~ "State what the code does; the tracker's state is not in this repository. " +#~ "Every number below is bound to the constant it came from by a `gc-fact` " +#~ "marker and re-derived by `scripts/check_gc_doc_claims.py`, which `lint` " +#~ "runs." +#~ msgstr "" +#~ "Diese Seite nennt absichtlich **keine Ausgabenummern**. Ein Satz der Form " +#~ "\"die Phase ist noch ungeschnitten (siehe den Tracker)\" wird falsch, " +#~ "sobald der Tracker schließt, und nichts im Baum bemerkt – genau so sind " +#~ "hier drei Absätze in einem Landezug verharren. Geben Sie an, was der Code " +#~ "tut; der Status des Trackers ist nicht in diesem Projektarchiv. Jede Zahl " +#~ "unten ist an die Konstante gebunden, die sie von einem `gc-fact`-Marker " +#~ "stammte und von `scripts/check_gc_doc_claims.py` wieder abgeleitet wurde, " +#~ "welches `lint` läuft." + +#~ msgid "" +#~ "**Born-old thresholds are type-dependent.** A pointer-free allocation " +#~ "above 16 KiB" +#~ msgstr "" +#~ "**Born-old Schwellen sind typabhängig.** Eine pointer-freie Zuweisung " +#~ "über 16 KiB" + +#~ msgid "" +#~ "is born in old-gen; a pointer-bearing one stays nursery-resident until " +#~ "128 KiB" +#~ msgstr "" +#~ "wird im alten Gen geboren; ein Zeiger tragender bleibt kinderheimisch bis " +#~ "128 KiB" + +#~ msgid "" +#~ "The split matters when reading a survival ratio: a flat threshold sends " +#~ "the intermediate backing stores of a growing array straight to old-gen, " +#~ "so the garbage they abandon never appears in the young generation at all." +#~ msgstr "" +#~ "Die Spaltung spielt beim Lesen eines Überlebensverhältnisses eine Rolle: " +#~ "Eine flache Schwelle sendet die Zwischenspeicher einer wachsenden Array " +#~ "direkt zu alten Genen, so dass der Müll, den sie aufgeben, in der jungen " +#~ "Generation überhaupt nicht erscheint." + +#~ msgid "" +#~ "All JavaScript values are represented as 64-bit NaN-boxed values. The " +#~ "upper 16 bits encode the type tag:" +#~ msgstr "" +#~ "Alle JavaScript-Werte werden als 64-Bit-NaN-geboxte Werte dargestellt. " +#~ "Die oberen 16 Bits kodieren das Typ-Tag:" + +#~ msgid "" +#~ "Generational mark-sweep GC, per-thread arena split into nursery + old-" +#~ "gen. Roots come from a precise shadow stack (emitted by codegen), a " +#~ "conservative native-stack scan, and 9 registered runtime scanners. Two-" +#~ "bit aging tenures objects after surviving 2 minor cycles; a write barrier " +#~ "maintains a remembered set for old → young pointers." +#~ msgstr "" +#~ "Generationaler Mark-Sweep-GC, Thread-lokale Arena aufgeteilt in Nursery + " +#~ "Old-Gen. Roots stammen von einem präzisen Shadow Stack (vom Codegen " +#~ "emittiert), einem konservativen nativen Stack-Scan und 9 registrierten " +#~ "Runtime-Scannern. Zwei-Bit-Aging tenuriert Objekte nach dem Überleben von " +#~ "2 Minor-Zyklen; eine Write Barrier pflegt einen Remembered Set für Old-→-" +#~ "Young-Zeiger." + +#~ msgid "The codegen crate is organized into focused modules:" +#~ msgstr "Die Codegen-Crate ist in fokussierte Module organisiert:" + +#~ msgid "The HIR lowering was split into 8 modules:" +#~ msgstr "Das HIR-Lowering wurde in 8 Module aufgeteilt:" + #~ msgid "`perry-ext-events`" #~ msgstr "`perry-ext-events`" @@ -92582,8 +93345,8 @@ msgstr "" #~ "\n" #~ "function commanderExample(): void {\n" #~ " const program = new Command()\n" -#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI " -#~ "tool\")\n" +#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI tool" +#~ "\")\n" #~ "\n" #~ " program\n" #~ " .command(\"serve\")\n" @@ -92602,8 +93365,8 @@ msgstr "" #~ "\n" #~ "function commanderExample(): void {\n" #~ " const program = new Command()\n" -#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI " -#~ "tool\")\n" +#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI tool" +#~ "\")\n" #~ "\n" #~ " program\n" #~ " .command(\"serve\")\n" @@ -93951,8 +94714,8 @@ msgstr "" #~ msgstr "Dann stoßen und tag:" #~ msgid "" -#~ "# Bump [workspace.package] version in Cargo.toml AND the \"Current " -#~ "Version\"" +#~ "# Bump [workspace.package] version in Cargo.toml AND the \"Current Version" +#~ "\"" #~ msgstr "" #~ "# Bump [workspace.package] Version in Cargo.toml UND die \"aktuelle " #~ "Version\"" @@ -94946,9 +95709,8 @@ msgstr "" #~ msgid "" #~ "The result: at any GC safepoint, the collector can walk the shadow stack " #~ "and see the live NaN-boxed value of every TS-level local in every active " -#~ "frame, regardless of what LLVM did with registers. This is the " -#~ "\"precise\" half of the root scan — `shadow_stack_root_scanner` (`gc." -#~ "rs:3860`)." +#~ "frame, regardless of what LLVM did with registers. This is the \"precise" +#~ "\" half of the root scan — `shadow_stack_root_scanner` (`gc.rs:3860`)." #~ msgstr "" #~ "Das Ergebnis: An jedem GC-Safepoint kann der Collector den Shadow Stack " #~ "durchlaufen und den live NaN-geboxt-en Wert jeder TS-lokalen Variable in " @@ -95211,9 +95973,6 @@ msgstr "" #~ msgid "No WeakMap/WeakRef" #~ msgstr "Kein WeakMap/WeakRef" -#~ msgid "Weak references are not implemented:" -#~ msgstr "Schwache Referenzen sind nicht implementiert:" - #~ msgid "No Proxy" #~ msgstr "Kein Proxy" diff --git a/docs/po/es.po b/docs/po/es.po index bb89c7b9b15..15a91a4216d 100644 --- a/docs/po/es.po +++ b/docs/po/es.po @@ -65,7 +65,7 @@ msgid "Native Layout Values" msgstr "Valores con disposición de memoria nativa" #: src/SUMMARY.md:21 src/language/decorators.md:1 -#: src/language/limitations.md:176 src/packages/porting.md:144 +#: src/language/limitations.md:85 src/packages/porting.md:144 msgid "Decorators" msgstr "Decoradores" @@ -222,7 +222,7 @@ msgstr "Hooks" #: src/SUMMARY.md:68 src/threading/parallel-map.md:157 #: src/threading/parallel-filter.md:78 src/threading/spawn.md:191 -#: src/tui/examples.md:1 src/cli/flags.md:508 +#: src/tui/examples.md:1 src/cli/flags.md:530 msgid "Examples" msgstr "Ejemplos" @@ -246,7 +246,7 @@ msgstr "Plataformas" #: src/system/audio.md:99 src/system/media.md:86 src/system/other.md:20 #: src/system/other.md:37 src/plugins/native-extensions.md:302 #: src/plugins/appstore-review.md:96 src/testing/geisterhand.md:259 -#: src/testing/geisterhand.md:331 src/cli/flags.md:103 +#: src/testing/geisterhand.md:331 src/cli/flags.md:125 #: src/cli/app-updates.md:125 msgid "macOS" msgstr "macOS" @@ -315,7 +315,7 @@ msgstr "Publicación en el App Store" #: src/widgets/platforms.md:18 src/widgets/platforms.md:25 #: src/widgets/platforms.md:49 src/widgets/platforms.md:76 #: src/plugins/native-extensions.md:303 src/plugins/appstore-review.md:109 -#: src/testing/geisterhand.md:261 src/cli/flags.md:108 src/cli/flags.md:109 +#: src/testing/geisterhand.md:261 src/cli/flags.md:130 src/cli/flags.md:131 #: src/cli/perry-toml.md:528 msgid "Android" msgstr "Android" @@ -326,7 +326,7 @@ msgstr "Android" msgid "Wear OS" msgstr "Wear OS" -#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:117 +#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:139 msgid "HarmonyOS NEXT" msgstr "HarmonyOS NEXT" @@ -345,7 +345,7 @@ msgstr "HarmonyOS NEXT" #: src/system/keychain.md:28 src/system/notifications.md:123 #: src/system/audio.md:103 src/system/media.md:92 src/system/other.md:23 #: src/system/other.md:40 src/testing/geisterhand.md:263 -#: src/testing/geisterhand.md:392 src/cli/flags.md:121 +#: src/testing/geisterhand.md:392 src/cli/flags.md:143 #: src/cli/app-updates.md:127 msgid "Windows" msgstr "Windows" @@ -368,11 +368,11 @@ msgstr "Linux (GTK4)" #: src/system/preferences.md:39 src/system/keychain.md:30 #: src/system/notifications.md:125 src/system/audio.md:104 #: src/system/audio_module.md:99 src/system/media.md:95 src/system/other.md:25 -#: src/system/other.md:42 src/cli/flags.md:120 +#: src/system/other.md:42 src/cli/flags.md:142 msgid "Web" msgstr "Web" -#: src/SUMMARY.md:86 src/cli/flags.md:119 +#: src/SUMMARY.md:86 src/cli/flags.md:141 msgid "WebAssembly" msgstr "WebAssembly" @@ -642,11 +642,11 @@ msgstr "`PERRY_SANDBOX_BUILDRS`" msgid "`--emit-attest` (binary attestation sidecar)" msgstr "`--emit-attest` (sidecar de atestación binaria)" -#: src/SUMMARY.md:178 src/cli/flags.md:428 +#: src/SUMMARY.md:178 src/cli/flags.md:450 msgid "`--emit-sandbox`" msgstr "`--emit-sandbox`" -#: src/SUMMARY.md:179 src/cli/flags.md:429 +#: src/SUMMARY.md:179 src/cli/flags.md:451 msgid "`--lockdown`" msgstr "`--lockdown`" @@ -719,53 +719,57 @@ msgstr "Gestión explícita de memoria" msgid "The GC rooting invariant (codegen)" msgstr "La invariante de enraizamiento del GC (generación de código)" -#: src/SUMMARY.md:201 src/internals/local-binding-type-evidence.md:1 +#: src/SUMMARY.md:201 src/internals/program-facts.md:1 +msgid "Program facts" +msgstr "Hechos del programa" + +#: src/SUMMARY.md:202 src/internals/local-binding-type-evidence.md:1 msgid "Local binding type evidence" msgstr "Evidencia de tipo para bindings locales" -#: src/SUMMARY.md:202 src/internals/codegen-mechanisms.md:1 +#: src/SUMMARY.md:203 src/internals/codegen-mechanisms.md:1 msgid "Codegen mechanisms and workload evidence" msgstr "" -#: src/SUMMARY.md:203 +#: src/SUMMARY.md:204 msgid "Incremental GC step bounds" msgstr "Límites de los pasos del GC incremental" -#: src/SUMMARY.md:204 src/internals/rfc-rooting-by-construction.md:1 +#: src/SUMMARY.md:205 src/internals/rfc-rooting-by-construction.md:1 msgid "RFC: rooting by construction" msgstr "RFC: enraizamiento por construcción" -#: src/SUMMARY.md:205 src/internals/node-api-host.md:1 +#: src/SUMMARY.md:206 src/internals/node-api-host.md:1 msgid "Node-API host design" msgstr "Node-API host design" -#: src/SUMMARY.md:206 src/internals/fallback-diagnostics.md:1 +#: src/SUMMARY.md:207 src/internals/fallback-diagnostics.md:1 #, fuzzy msgid "Generic fallback diagnostics" msgstr "Semántica de objeto general" -#: src/SUMMARY.md:208 +#: src/SUMMARY.md:209 msgid "Contributing" msgstr "Contribuir" -#: src/SUMMARY.md:210 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 +#: src/SUMMARY.md:211 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 #: src/testing/geisterhand.md:683 src/contributing/architecture.md:1 msgid "Architecture" msgstr "Arquitectura" -#: src/SUMMARY.md:211 src/contributing/crate-policy.md:1 +#: src/SUMMARY.md:212 src/contributing/crate-policy.md:1 msgid "Crate policy" msgstr "Política de crates" -#: src/SUMMARY.md:212 src/contributing/building.md:1 +#: src/SUMMARY.md:213 src/contributing/building.md:1 msgid "Building from Source" msgstr "Compilar desde el código fuente" -#: src/SUMMARY.md:213 src/contributing/build-storage.md:1 +#: src/SUMMARY.md:214 src/contributing/build-storage.md:1 msgid "Linux agent build storage" msgstr "" -#: src/SUMMARY.md:214 src/contributing/releasing.md:1 +#: src/SUMMARY.md:215 src/contributing/releasing.md:1 msgid "Releasing Perry" msgstr "Publicar una versión de Perry" @@ -1008,7 +1012,7 @@ msgstr "" "plataforma — sin Electron, sin WebView." #: src/introduction.md:81 src/getting-started/first-app.md:41 -#: src/threading/overview.md:323 src/threading/parallel-map.md:18 +#: src/threading/overview.md:308 src/threading/parallel-map.md:18 #: src/threading/parallel-filter.md:18 src/platforms/watchos.md:214 #: src/platforms/wasm.md:29 src/stdlib/overview.md:8 src/i18n/overview.md:78 #: src/widgets/overview.md:36 src/plugins/overview.md:7 @@ -1031,8 +1035,8 @@ msgstr "" #: src/introduction.md:96 src/getting-started/hello-world.md:155 #: src/getting-started/first-app.md:184 #: src/getting-started/project-config.md:327 -#: src/language/supported-features.md:631 src/language/type-system.md:149 -#: src/language/limitations.md:393 src/threading/overview.md:350 +#: src/language/supported-features.md:629 src/language/type-system.md:153 +#: src/language/limitations.md:250 src/threading/overview.md:392 #: src/ui/overview.md:239 src/ui/widgets.md:706 src/ui/layout.md:393 #: src/ui/styling.md:387 src/ui/state.md:226 src/ui/events.md:167 #: src/ui/canvas.md:93 src/ui/menus.md:119 src/ui/tray.md:106 @@ -1043,7 +1047,7 @@ msgstr "" #: src/platforms/watchos.md:316 src/platforms/android.md:97 #: src/platforms/wearos.md:208 src/platforms/windows.md:144 #: src/platforms/linux.md:84 src/platforms/web.md:21 src/platforms/wasm.md:191 -#: src/stdlib/overview.md:128 src/stdlib/fs.md:98 src/stdlib/http.md:408 +#: src/stdlib/overview.md:128 src/stdlib/fs.md:105 src/stdlib/http.md:408 #: src/stdlib/database.md:112 src/stdlib/crypto.md:87 #: src/stdlib/utilities.md:96 src/stdlib/other.md:333 src/i18n/overview.md:108 #: src/updater/overview.md:445 src/system/overview.md:77 @@ -1055,8 +1059,8 @@ msgstr "" #: src/plugins/overview.md:96 src/plugins/creating-plugins.md:162 #: src/plugins/hooks-and-events.md:144 src/plugins/native-extensions.md:324 #: src/plugins/appstore-review.md:199 src/cli/commands.md:437 -#: src/cli/flags.md:545 src/contributing/architecture.md:109 -#: src/contributing/building.md:201 +#: src/cli/flags.md:567 src/contributing/architecture.md:85 +#: src/contributing/building.md:233 msgid "Next Steps" msgstr "Próximos pasos" @@ -1239,7 +1243,7 @@ msgstr "" #: src/system/background.md:78 src/system/other.md:18 src/system/other.md:35 #: src/widgets/data-fetching.md:142 src/widgets/platforms.md:14 #: src/plugins/native-extensions.md:298 src/testing/geisterhand.md:257 -#: src/cli/flags.md:96 src/cli/function-order.md:54 +#: src/cli/flags.md:118 src/cli/function-order.md:54 #: src/contributing/releasing.md:168 msgid "Platform" msgstr "Plataforma" @@ -1264,7 +1268,7 @@ msgstr "macOS x64 (Intel)" msgid "`@perryts/perry-darwin-x64`" msgstr "`@perryts/perry-darwin-x64`" -#: src/getting-started/installation.md:72 src/cli/flags.md:126 +#: src/getting-started/installation.md:72 src/cli/flags.md:148 msgid "Linux x64 (glibc)" msgstr "Linux x64 (glibc)" @@ -1272,7 +1276,7 @@ msgstr "Linux x64 (glibc)" msgid "`@perryts/perry-linux-x64`" msgstr "`@perryts/perry-linux-x64`" -#: src/getting-started/installation.md:73 src/cli/flags.md:127 +#: src/getting-started/installation.md:73 src/cli/flags.md:149 msgid "Linux arm64 (glibc)" msgstr "Linux arm64 (glibc)" @@ -1296,7 +1300,7 @@ msgstr "Linux arm64 (musl / Alpine)" msgid "`@perryts/perry-linux-arm64-musl`" msgstr "`@perryts/perry-linux-arm64-musl`" -#: src/getting-started/installation.md:76 src/cli/flags.md:122 +#: src/getting-started/installation.md:76 src/cli/flags.md:144 msgid "Windows x64" msgstr "Windows x64" @@ -1531,7 +1535,7 @@ msgstr "" #: src/i18n/overview.md:94 src/updater/overview.md:271 #: src/system/preferences.md:38 src/system/keychain.md:29 #: src/system/notifications.md:124 src/system/audio.md:102 -#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:125 +#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:147 #: src/cli/app-updates.md:126 msgid "Linux" msgstr "Linux" @@ -2591,11 +2595,11 @@ msgstr "" #: src/getting-started/project-config.md:53 msgid "" -"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": " -"\"default\"` disables automatic Solid detection. A host setting overrides " -"per-file detection. The equivalent TOML setting is `[perry]` with `jsx = " -"{ runtime = \"@opentui/solid\" }`; TOML overrides the host's package " -"setting. React/Preact import sources keep their existing JSX behavior." +"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": \"default" +"\"` disables automatic Solid detection. A host setting overrides per-file " +"detection. The equivalent TOML setting is `[perry]` with `jsx = { runtime = " +"\"@opentui/solid\" }`; TOML overrides the host's package setting. React/" +"Preact import sources keep their existing JSX behavior." msgstr "" #: src/getting-started/project-config.md:59 @@ -2637,8 +2641,8 @@ msgid "" "Compiling third-party TypeScript into your binary is a privileged operation, " "so every entry in `compilePackages` must _also_ be matched by an entry in " "`perry.allow.compilePackages` in the same `package.json` — otherwise the " -"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/" -"*\"`), or the universal `\"*\"`:" +"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/*" +"\"`), or the universal `\"*\"`:" msgstr "" "**Dos-claves de admisión.** La inclusión de un paquete no es suficiente por " "sí sola. Compilar TypeScript de terceros en su binario es una operación " @@ -3127,10 +3131,10 @@ msgstr "Campo" #: src/cli/commands.md:48 src/cli/commands.md:92 src/cli/commands.md:142 #: src/cli/commands.md:183 src/cli/commands.md:216 src/cli/commands.md:230 #: src/cli/commands.md:268 src/cli/commands.md:283 src/cli/commands.md:295 -#: src/cli/flags.md:80 src/cli/flags.md:135 src/cli/flags.md:143 -#: src/cli/flags.md:191 src/cli/flags.md:291 src/cli/flags.md:379 -#: src/cli/flags.md:399 src/cli/flags.md:412 src/cli/flags.md:419 -#: src/cli/flags.md:435 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 +#: src/cli/flags.md:102 src/cli/flags.md:157 src/cli/flags.md:165 +#: src/cli/flags.md:213 src/cli/flags.md:313 src/cli/flags.md:401 +#: src/cli/flags.md:421 src/cli/flags.md:434 src/cli/flags.md:441 +#: src/cli/flags.md:457 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 #: src/cli/perry-toml.md:156 src/cli/perry-toml.md:172 #: src/cli/perry-toml.md:182 src/cli/perry-toml.md:188 #: src/cli/perry-toml.md:198 src/cli/perry-toml.md:248 @@ -3338,7 +3342,7 @@ msgstr "" "paquetes puros de TS/JS, o el respaldo del runtime de JavaScript para " "paquetes complejos." -#: src/getting-started/project-config.md:289 src/contributing/building.md:177 +#: src/getting-started/project-config.md:289 src/contributing/building.md:209 msgid "Project Structure" msgstr "Estructura del proyecto" @@ -3537,10 +3541,11 @@ msgstr "Notificación local" #: src/platforms/windows-7.md:25 src/container/overview.md:71 #: src/container/security.md:105 src/container/determinism.md:59 #: src/testing/geisterhand.md:87 src/testing/geisterhand.md:101 -#: src/cli/flags.md:562 src/cli/flags.md:668 src/cli/flags.md:669 +#: src/cli/flags.md:584 src/cli/flags.md:690 src/cli/flags.md:691 #: src/cli/dynamic-dispatch.md:123 src/cli/allow-js-runtime.md:67 #: src/cli/sandbox-buildrs.md:25 src/cli/sandbox-buildrs.md:53 -#: src/cli/perry-toml.md:431 src/internals/gc-rooting-invariant.md:285 +#: src/cli/perry-toml.md:431 src/internals/garbage-collector.md:463 +#: src/internals/gc-rooting-invariant.md:285 #: src/internals/codegen-mechanisms.md:56 #: src/internals/rfc-rooting-by-construction.md:354 #: src/contributing/releasing.md:202 @@ -3665,8 +3670,8 @@ msgid "" " // Arrow functions\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" msgstr "" @@ -3689,33 +3694,33 @@ msgstr "" " // Arrow functions\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" -#: src/language/supported-features.md:60 src/api/reference.md:223 -#: src/api/reference.md:257 src/api/reference.md:291 src/api/reference.md:349 -#: src/api/reference.md:373 src/api/reference.md:462 src/api/reference.md:509 -#: src/api/reference.md:530 src/api/reference.md:555 src/api/reference.md:830 -#: src/api/reference.md:913 src/api/reference.md:961 src/api/reference.md:981 -#: src/api/reference.md:1090 src/api/reference.md:1168 -#: src/api/reference.md:1207 src/api/reference.md:1256 -#: src/api/reference.md:1284 src/api/reference.md:1445 -#: src/api/reference.md:1653 src/api/reference.md:1676 -#: src/api/reference.md:1730 src/api/reference.md:1761 -#: src/api/reference.md:1815 src/api/reference.md:1859 -#: src/api/reference.md:1993 src/api/reference.md:2135 -#: src/api/reference.md:2371 src/api/reference.md:2860 -#: src/api/reference.md:2873 src/api/reference.md:2944 -#: src/api/reference.md:3006 src/api/reference.md:3115 -#: src/api/reference.md:3141 src/api/reference.md:3154 -#: src/api/reference.md:3165 src/api/reference.md:3292 -#: src/api/reference.md:3337 src/api/reference.md:3378 -#: src/api/reference.md:3398 src/api/reference.md:3427 -#: src/api/reference.md:3523 src/api/reference.md:3581 -#: src/api/reference.md:3617 src/api/reference.md:3635 -#: src/api/reference.md:3683 src/api/reference.md:3723 +#: src/language/supported-features.md:60 src/api/reference.md:224 +#: src/api/reference.md:258 src/api/reference.md:292 src/api/reference.md:351 +#: src/api/reference.md:375 src/api/reference.md:464 src/api/reference.md:511 +#: src/api/reference.md:532 src/api/reference.md:557 src/api/reference.md:832 +#: src/api/reference.md:915 src/api/reference.md:963 src/api/reference.md:983 +#: src/api/reference.md:1092 src/api/reference.md:1170 +#: src/api/reference.md:1209 src/api/reference.md:1258 +#: src/api/reference.md:1286 src/api/reference.md:1447 +#: src/api/reference.md:1655 src/api/reference.md:1678 +#: src/api/reference.md:1732 src/api/reference.md:1763 +#: src/api/reference.md:1817 src/api/reference.md:1861 +#: src/api/reference.md:1995 src/api/reference.md:2137 +#: src/api/reference.md:2376 src/api/reference.md:2869 +#: src/api/reference.md:2882 src/api/reference.md:2953 +#: src/api/reference.md:3015 src/api/reference.md:3124 +#: src/api/reference.md:3150 src/api/reference.md:3163 +#: src/api/reference.md:3174 src/api/reference.md:3301 +#: src/api/reference.md:3357 src/api/reference.md:3398 +#: src/api/reference.md:3418 src/api/reference.md:3447 +#: src/api/reference.md:3543 src/api/reference.md:3601 +#: src/api/reference.md:3637 src/api/reference.md:3655 +#: src/api/reference.md:3703 src/api/reference.md:3743 msgid "Classes" msgstr "Clases" @@ -3883,7 +3888,7 @@ msgstr "" "(como en `tsc`). Existen únicamente para documentación y para herramientas " "del editor." -#: src/language/supported-features.md:144 src/threading/overview.md:343 +#: src/language/supported-features.md:144 src/threading/overview.md:328 #: src/threading/parallel-map.md:58 msgid "Arrays" msgstr "Arrays" @@ -3928,11 +3933,11 @@ msgid "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" msgstr "" @@ -3974,15 +3979,15 @@ msgstr "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" -#: src/language/supported-features.md:188 src/threading/overview.md:344 +#: src/language/supported-features.md:188 src/threading/overview.md:329 #: src/threading/parallel-map.md:59 msgid "Objects" msgstr "Objetos" @@ -4071,8 +4076,8 @@ msgid "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" msgstr "" @@ -4100,8 +4105,8 @@ msgstr "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" @@ -4121,8 +4126,8 @@ msgid "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" msgstr "" @@ -4136,8 +4141,8 @@ msgstr "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" @@ -4192,7 +4197,7 @@ msgstr "" "```" #: src/language/supported-features.md:282 src/native-libraries/overview.md:190 -#: src/native-libraries/abi.md:76 src/threading/overview.md:345 +#: src/native-libraries/abi.md:76 src/threading/overview.md:330 msgid "Closures" msgstr "Closures" @@ -4576,7 +4581,7 @@ msgstr "" "```" #: src/language/supported-features.md:470 src/native-libraries/overview.md:192 -#: src/internals/memory-model.md:22 +#: src/internals/memory-model.md:24 msgid "BigInt" msgstr "BigInt" @@ -4706,19 +4711,19 @@ msgstr "" "}\n" "```" -#: src/language/supported-features.md:563 src/contributing/architecture.md:57 +#: src/language/supported-features.md:563 src/contributing/architecture.md:62 msgid "Garbage Collection" msgstr "Recolección de basura" #: src/language/supported-features.md:565 msgid "" -"Perry includes a mark-sweep garbage collector. It runs automatically when " -"memory pressure is detected (~8MB arena blocks), but you can also trigger it " -"manually:" +"Perry uses a tracing generational collector with automatic allocation-" +"pressure and safepoint scheduling. You can also request collection " +"explicitly:" msgstr "" -"Perry incluye un recolector de basura de tipo mark-sweep. Se ejecuta " -"automáticamente cuando se detecta presión de memoria (bloques de arena de ~8 " -"MB), pero también puedes activarlo manualmente:" +"Perry usa un recolector generacional por trazado con planificación " +"automática por presión de asignación y safepoints. También puede solicitar " +"recolección explícitamente:" #: src/language/supported-features.md:567 msgid "" @@ -4736,13 +4741,15 @@ msgstr "" #: src/language/supported-features.md:573 msgid "" -"The GC uses conservative stack scanning to find roots and supports arena-" -"allocated objects (arrays, objects) and malloc-allocated objects (strings, " -"closures, promises, BigInts, errors)." +"Copying minors, non-moving fallbacks and full sweeps cooperate with precise " +"generated roots and runtime root scanners. Conservative stack scanning is " +"diagnostic-only. See the [memory model](../internals/memory-model.md) for " +"allocation and rooting details." msgstr "" -"El GC utiliza escaneo conservador de la pila para encontrar raíces y admite " -"objetos asignados en arena (arrays, objetos) y objetos asignados con malloc " -"(strings, closures, promesas, BigInts, errores)." +"Las recolecciones menores por copia, alternativas no móviles y barridos " +"completos cooperan con raíces generadas precisas y escáneres del runtime. El" +" escaneo conservador es solo diagnóstico. Consulte el [modelo de " +"memoria](../internals/memory-model.md) para asignación y raíces." #: src/language/supported-features.md:575 msgid "JSX/TSX" @@ -4831,20 +4838,19 @@ msgstr "" #: src/language/supported-features.md:615 msgid "" -"There are no guarantees for every dynamic JavaScript pattern (the " -"[Limitations](limitations.md) still apply — no `eval`, no general dynamic " -"`require()`), but most plain JavaScript projects compile and run." +"Dynamic evaluation and CommonJS have supported paths, with limits on " +"interpreter behavior and runtime module loading. See " +"[Limitations](limitations.md) for the current boundaries." msgstr "" -"No hay garantías para todos los patrones dinámicos JavaScript (el " -"[Limitations](limitations.md) todavía se aplica — no `eval`, no `require()` " -"dinámico general), pero la mayoría de los proyectos JavaScript simples " -"compilar y ejecutar." +"La evaluación dinámica y CommonJS tienen rutas admitidas, con límites en el " +"intérprete y la carga de módulos en ejecución. Consulte los límites actuales" +" en [Limitaciones](limitations.md)." -#: src/language/supported-features.md:619 +#: src/language/supported-features.md:617 msgid "Node.js Compatibility" msgstr "Node.js Compatibility" -#: src/language/supported-features.md:621 +#: src/language/supported-features.md:619 msgid "" "Perry implements a large, real (non-stub) slice of the Node.js standard " "library — `fs`, `http`/`https`/`http2`, `net`/`tls`, `dns`/`dgram`, " @@ -4867,25 +4873,27 @@ msgstr "" "95%. La superficie por módulo y los huecos restantes se rastrean en `docs/" "runtime-parity-gaps.md`." -#: src/language/supported-features.md:633 +#: src/language/supported-features.md:631 msgid "[Type System](type-system.md) — Type inference and checking" msgstr "" "[Sistema de tipos](type-system.md) — Inferencia y verificación de tipos" -#: src/language/supported-features.md:634 +#: src/language/supported-features.md:632 msgid "[Limitations](limitations.md) — What's not supported yet" msgstr "[Limitaciones](limitations.md) — Qué aún no es compatible" #: src/language/type-system.md:3 msgid "" -"Perry erases types at compile time, similar to how `tsc` removes type " -"annotations when emitting JavaScript. However, Perry also performs type " -"inference to generate efficient native code." +"TypeScript annotations describe source-level intent; they do not validate " +"runtime values. Perry combines local evidence, program facts and runtime " +"guards to select native representations. Decorator metadata is an explicit " +"exception to annotation erasure." msgstr "" -"Perry borra los tipos en tiempo de compilación, de forma similar a como " -"`tsc` elimina las anotaciones de tipo al emitir JavaScript. Sin embargo, " -"Perry también realiza inferencia de tipos para generar código nativo " -"eficiente." +"Las anotaciones TypeScript expresan intención en el código; no validan " +"valores en ejecución. Perry combina evidencia local, hechos del programa y " +"comprobaciones en ejecución para elegir representaciones nativas. Los " +"metadatos de decoradores son una excepción explícita al borrado de " +"anotaciones." #: src/language/type-system.md:5 msgid "Type Inference" @@ -5036,11 +5044,11 @@ msgstr "Tipos utilitarios" #: src/language/type-system.md:64 msgid "" -"Common TypeScript utility types are erased at compile time (they don't " -"affect code generation):" +"Utility types describe static relationships and do not introduce runtime " +"wrappers or validation:" msgstr "" -"Los tipos utilitarios comunes de TypeScript se borran en tiempo de " -"compilación (no afectan a la generación de código):" +"Los tipos utilitarios describen relaciones estáticas y no introducen " +"envoltorios ni validación en ejecución:" #: src/language/type-system.md:75 msgid "" @@ -5098,11 +5106,13 @@ msgstr "" #: src/language/type-system.md:100 msgid "" -"At runtime, all values are NaN-boxed — the generic parameter doesn't affect " -"code generation." +"A generic parameter alone is not a runtime representation proof. Generic " +"value boundaries use NaN-boxing, while proven computations can use native " +"scalars and specialized storage." msgstr "" -"En tiempo de ejecución, todos los valores están NaN-boxed — el parámetro " -"genérico no afecta a la generación de código." +"Un parámetro genérico por sí solo no demuestra una representación en " +"ejecución. Las interfaces genéricas usan NaN-boxing; los cálculos " +"demostrados pueden usar escalares nativos y almacenamiento especializado." #: src/language/type-system.md:102 msgid "Type Checking with `--type-check`" @@ -5118,12 +5128,17 @@ msgstr "" #: src/language/type-system.md:110 msgid "" -"This resolves cross-file types, interfaces, and generics via an IPC " -"protocol. It falls back gracefully if the type checker is not installed." +"This starts `tsgo`, loads the project tsconfig and requests diagnostics and " +"resolved types over IPC. Reported type errors stop compilation. Missing " +"tooling, project-load failures or unavailable diagnostics can leave " +"compilation running without checking; this option is not a guaranteed " +"standalone type-check gate." msgstr "" -"Esto resuelve tipos, interfaces y genéricos entre archivos mediante un " -"protocolo IPC. Se degrada de forma elegante si el verificador de tipos no " -"está instalado." +"Inicia `tsgo`, carga la tsconfig del proyecto y solicita diagnósticos y " +"tipos resueltos mediante IPC. Los errores de tipos detectados detienen la " +"compilación. La falta de herramientas, los fallos de carga o diagnósticos no" +" disponibles pueden dejar que continúe sin comprobación; esta opción no " +"garantiza por sí sola una barrera de validación de tipos." #: src/language/type-system.md:112 msgid "" @@ -5140,10 +5155,13 @@ msgstr "Tipos unión e intersección" #: src/language/type-system.md:116 msgid "" -"Union types are recognized syntactically but don't affect code generation:" +"Unions and intersections do not create runtime tags of their own. " +"Specialization requires evidence about the actual value, such as a validated" +" guard:" msgstr "" -"Los tipos unión se reconocen sintácticamente pero no afectan a la generación " -"de código:" +"Las uniones e intersecciones no crean etiquetas propias en ejecución. La " +"especialización requiere evidencia sobre el valor real, como una " +"comprobación validada:" #: src/language/type-system.md:118 msgid "" @@ -5217,12 +5235,31 @@ msgstr "" "La anotación `value is string` se borra, pero la comprobación `typeof` " "funciona en tiempo de ejecución." +#: src/language/type-system.md:149 +msgid "Program facts and specialization" +msgstr "Hechos del programa y especialización" + #: src/language/type-system.md:151 +msgid "" +"The default facts pass analyzes value flow across modules. It can prove " +"receiver sets, field layouts and call targets; unknown behavior retains " +"checks or the generic path. See [Program facts](../internals/program-" +"facts.md) and [Local binding type evidence](../internals/local-binding-type-" +"evidence.md)." +msgstr "" +"El pase de hechos predeterminado analiza el flujo de valores entre módulos. " +"Puede demostrar conjuntos de receptores, layouts de campos y destinos de " +"llamada; el comportamiento desconocido conserva comprobaciones o la ruta " +"genérica. Véanse [Hechos del programa](../internals/program-facts.md) y " +"[Evidencia de tipos de enlaces locales](../internals/local-binding-type-" +"evidence.md)." + +#: src/language/type-system.md:155 msgid "[Supported Features](supported-features.md) — Complete feature list" msgstr "" "[Funciones admitidas](supported-features.md) — Lista completa de funciones" -#: src/language/type-system.md:152 +#: src/language/type-system.md:156 msgid "[Limitations](limitations.md) — What's not supported" msgstr "[Limitaciones](limitations.md) — Qué no es compatible" @@ -5359,7 +5396,7 @@ msgstr "" #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 #: src/widgets/creating-widgets.md:48 src/widgets/configuration.md:70 -#: src/testing/geisterhand.md:84 src/cli/flags.md:135 src/cli/perry-toml.md:117 +#: src/testing/geisterhand.md:84 src/cli/flags.md:157 src/cli/perry-toml.md:117 #: src/cli/perry-toml.md:128 src/cli/perry-toml.md:156 #: src/cli/perry-toml.md:172 src/cli/perry-toml.md:182 #: src/cli/perry-toml.md:188 src/cli/perry-toml.md:198 @@ -5369,8 +5406,8 @@ msgstr "" #: src/cli/perry-toml.md:334 src/cli/perry-toml.md:353 #: src/cli/perry-toml.md:365 src/cli/perry-toml.md:375 #: src/cli/perry-toml.md:385 src/cli/perry-toml.md:414 -#: src/cli/perry-toml.md:441 src/internals/memory-model.md:15 -#: src/contributing/architecture.md:48 +#: src/cli/perry-toml.md:441 src/internals/memory-model.md:17 +#: src/contributing/architecture.md:51 msgid "Type" msgstr "Tipo" @@ -5467,7 +5504,7 @@ msgid "IEEE-754 binary32" msgstr "IEEE-754 binary32" #: src/language/native-values.md:75 src/native-libraries/manifest-v1.md:223 -#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:27 +#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:30 msgid "`f64`" msgstr "`f64`" @@ -5510,8 +5547,8 @@ msgstr "" #: src/language/native-values.md:90 msgid "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -5525,8 +5562,8 @@ msgid "" "```" msgstr "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -6138,154 +6175,42 @@ msgstr "" "comprobaciones explícitas con `typeof` e `instanceof`." #: src/language/limitations.md:29 -msgid "No eval() or Dynamic Code" -msgstr "Sin eval() ni código dinámico" +msgid "Dynamic code and module loading" +msgstr "Código dinámico y carga de módulos" #: src/language/limitations.md:31 msgid "" -"Perry compiles to native code ahead of time. It cannot evaluate a code " -"string that is only known at runtime. A _constant_ code string is the " -"exception — `eval(\"1 + 1\")` and `new Function(\"a\", \"b\", \"return a + " -"b\")` are compiled to real native functions (#1679). Only a body built from " -"runtime data hits this limit:" -msgstr "" -"Perry se compila con el código nativo antes de tiempo. No puede evaluar una " -"cadena de código que sólo se conoce en tiempo de ejecución. Una cadena de " -"código _constant_ es la excepción — `eval(\"1 + 1\")` y `new Function(\"a\", " -"\"b\", \"return a + b\")` se compilan con funciones nativas reales (#1679). " -"Sólo un cuerpo construido a partir de datos de tiempo de ejecución alcanza " -"este límite:" - -#: src/language/limitations.md:37 -msgid "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" - -#: src/language/limitations.md:45 -msgid "Default: deferred runtime error + compile-time notice (#5206)" -msgstr "" -"Predeterminado: error de tiempo de ejecución diferido + aviso de tiempo de " -"compilación (#5206)" - -#: src/language/limitations.md:47 -msgid "" -"By default a runtime-unknown `eval(...)` / `new Function()` " -"site does **not** block the build. Perry compiles it to a value that throws " -"a descriptive `Error` _only if it is actually reached_ (an `eval(...)` " -"throws when evaluated; a `new Function(...)` returns a function that throws " -"when called), and prints a single end-of-compile notice listing every " -"degraded site:" -msgstr "" -"Por defecto, un sitio `eval(...)` / `new Function()` " -"desconocido hace **no** bloquear la construcción. Perry lo compila a un " -"valor que arroja una `Error` descriptiva _solo si realmente se alcanza_ (un " -"`eval(...)` lanza cuando se evalúa; un `new Function(...)` devuelve una " -"función que lanza cuando se llama), e imprime un solo aviso de final de " -"compilación que enumera cada sitio degradado:" - -#: src/language/limitations.md:61 -msgid "" -"This lets a single such call in a cold path ship without aborting the whole " -"build, while still failing loudly (and catchably) if that path runs." -msgstr "" -"Esto permite que una sola llamada de este tipo en una nave de camino frío " -"sin abortar toda la construcción, mientras que todavía falla en voz alta (y " -"atrapable) si ese camino corre." - -#: src/language/limitations.md:64 -msgid "Dynamic `import()` with a runtime-computed specifier (#5230)" -msgstr "" -"`import()` dinámico con un especificador computado en tiempo de ejecución " -"(#5230)" - -#: src/language/limitations.md:66 -#, fuzzy -msgid "" -"For code modules, a dynamic `import(spec)` whose `spec` is only known at " -"runtime (a plugin loader building a path from a variable) is subject to the " -"**same defer/notice/strict policy** as `eval`. By default it compiles to a " -"rejected `Promise` carrying a descriptive `Error` (so `await import(spec)` " -"throws _only if reached_), is listed in the shared notice above under the " -"`import(...)` kind, and does **not** abort the build. This lets an app with " -"a plugin-loader path compile and run its core, with only the plugin-load " -"path throwing if exercised." -msgstr "" -"Un dinámico `import(spec)` cuyo `spec` sólo se conoce en tiempo de ejecución " -"(un cargador de plugins que construye una ruta desde una variable) está " -"sujeto a la **la misma política de defer/notice/strict** como `eval`. De " -"forma predeterminada compila a un `Promise` rechazado que lleva un `Error` " -"descriptivo (por lo que `await import(spec)` lanza _solo si se alcanza_), se " -"enumera en el aviso compartido arriba bajo el tipo `import(...)`, y lo hace " -"**no** abortar la construcción. Esto permite que una aplicación con un " -"plugin-loader path compile y ejecute su núcleo, con sólo el plugin-loader " -"path lanzar si se ejercita." - -#: src/language/limitations.md:74 -msgid "" -"If the runtime specifier names a supported Node builtin, Perry resolves its " -"native namespace. Otherwise, when it has no compiled target, the rejected " -"`Error` retains `code: \"ERR_MODULE_NOT_FOUND\"` for existing optional-" -"dependency handlers. Its message names the requested module, explains that " -"runtime-loaded JavaScript is unavailable in the native build, and suggests " -"the application's Bun/Node distribution or a statically resolvable import " -"followed by recompilation. Deferred sites also retain their source location. " -"Perry does not print a second runtime warning: the application's catch/" -"report path owns the diagnostic." -msgstr "" - -#: src/language/limitations.md:83 -msgid "" -"Resolvable specifiers are unaffected and still compile + load: string " -"literals (`import(\"./mod.js\")`), ternaries of resolvable arms, template " -"literals over `const` locals (``import(`./${KIND}.js`)``), finite string-" -"literal-union parameters, and directory globs." -msgstr "" -"Los especificadores resolubles no se ven afectados y aún así compilan + " -"carga: literales de cadena (`import(\"./mod.js\")`), ternarios de brazos " -"resolubles, literales de plantilla sobre locales de `const` (``import(`./" -"${KIND}.js`)``), parámetros finitos de unión de cadena-literal y globs de " -"directorio." - -#: src/language/limitations.md:88 -msgid "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" +"Constant `eval` and `Function` bodies can compile ahead of time. Runtime-" +"generated bodies reach Perry’s linked interpreter; they are not " +"automatically deferred errors. Interpreter support does not imply full " +"engine parity or the ability to load arbitrary JavaScript packages from " +"disk." +msgstr "" +"Los cuerpos constantes de `eval` y `Function` pueden compilarse " +"anticipadamente. Los cuerpos generados en ejecución llegan al intérprete " +"enlazado de Perry; no se convierten automáticamente en errores diferidos. " +"Admitir interpretación no implica paridad completa ni cargar paquetes " +"JavaScript arbitrarios desde disco." + +#: src/language/limitations.md:33 +msgid "" +"Dynamic `import()` and `require()` can resolve supported builtins and " +"modules included in the compiled graph. Installing a package after " +"compilation does not add it to that graph. Unavailable code targets fail at " +"runtime; resolvable literals and supported finite-specifier forms can be " +"compiled into the executable." +msgstr "" +"`import()` dinámico y `require()` pueden resolver builtins admitidos y " +"módulos del grafo compilado. Instalar un paquete después no lo añade al " +"grafo. Los destinos no disponibles fallan en ejecución; los literales " +"resolubles y las formas finitas admitidas pueden compilarse en el " +"ejecutable." -#: src/language/limitations.md:98 +#: src/language/limitations.md:35 msgid "Native OpenCode plugins (#10105)" msgstr "" -#: src/language/limitations.md:100 +#: src/language/limitations.md:37 msgid "" "The first native OpenCode deliverable follows **option A** from [\\#10105]" "(https://github.com/PerryTS/perry/issues/10105), tracked in [\\#10107]" @@ -6296,7 +6221,7 @@ msgid "" "providers reached through static imports remain supported." msgstr "" -#: src/language/limitations.md:108 +#: src/language/limitations.md:45 msgid "" "Use the Bun distribution when those extensions are required. Including " "extensions in a statically reachable import graph and recompiling is a build-" @@ -6307,7 +6232,7 @@ msgid "" "as TOML are a separate feature (#10104)." msgstr "" -#: src/language/limitations.md:115 +#: src/language/limitations.md:52 msgid "" "OpenCode v1.18.30's `PluginLoader.load` catches import rejections, and its " "caller reports a load failure without retrying that stage. The TUI plugin " @@ -6318,58 +6243,58 @@ msgid "" "remains part of the integration acceptance in #10107." msgstr "" -#: src/language/limitations.md:123 +#: src/language/limitations.md:60 msgid "" "Data files can be loaded at runtime using import attributes (#10104). The " "specifier must be an absolute filesystem path or a `file://` URL, and the " "result has a `default` export:" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 msgid "Import attribute `type`" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 #, fuzzy msgid "Default export" msgstr "Comportamiento predeterminado" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 #, fuzzy msgid "`\"toml\"`" msgstr "`perry.toml`" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 msgid "Parsed TOML table, using the same parser as `Bun.TOML.parse`" msgstr "" -#: src/language/limitations.md:130 src/native-libraries/manifest-v1.md:225 +#: src/language/limitations.md:67 src/native-libraries/manifest-v1.md:225 msgid "`\"json\"`" msgstr "`\"json\"`" -#: src/language/limitations.md:130 +#: src/language/limitations.md:67 msgid "Parsed JSON value" msgstr "" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 #, fuzzy msgid "`\"text\"`" msgstr "\"text\"" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 msgid "File contents as a string" msgstr "" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 #, fuzzy msgid "`\"file\"`" msgstr "`filename`" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 msgid "Filesystem path as a string" msgstr "" -#: src/language/limitations.md:134 +#: src/language/limitations.md:71 msgid "" "```typescript,no-test\n" "import { pathToFileURL } from \"node:url\";\n" @@ -6379,7 +6304,7 @@ msgid "" "```" msgstr "" -#: src/language/limitations.md:141 +#: src/language/limitations.md:78 msgid "" "TOML and JSON parse failures reject with `SyntaxError`. Missing files reject " "with an I/O error. These loaders do not load runtime code modules or resolve " @@ -6387,88 +6312,21 @@ msgid "" "runtime-computed specifiers at compile time as described below." msgstr "" -#: src/language/limitations.md:146 -msgid "Strict mode: refuse at compile time" -msgstr "Modo estricto: rechazar en el momento de la compilación" - -#: src/language/limitations.md:148 -msgid "" -"To make every runtime-unknown site a hard compile-time error instead, opt " -"into strict-eval mode by any of:" -msgstr "" -"Para hacer de cada sitio de ejecución desconocido un error duro de " -"compilación-tiempo en su lugar, opte por el modo estricto-eval por " -"cualquiera de:" - -#: src/language/limitations.md:151 -msgid "the `--strict-eval` flag on `perry compile`," -msgstr "el indicador `--strict-eval` en `perry compile`," - -#: src/language/limitations.md:152 -msgid "" -"`\"perry\": { \"eval\": \"error\" }` (or `\"perry\": { \"strict\": true }`) " -"in `package.json`, or" -msgstr "" -"`\"perry\": { \"eval\": \"error\" }` (o `\"perry\": { \"strict\": true }`) " -"en `package.json`, o" - -#: src/language/limitations.md:154 -msgid "`[perry]` `eval = \"error\"` (or `strict = true`) in `perry.toml`." -msgstr "`[perry]` `eval = \"error\"` (o `strict = true`) en `perry.toml`" - -#: src/language/limitations.md:156 +#: src/language/limitations.md:83 msgid "" -"`perry.eval` accepts `\"defer\"` (the default) or `\"error\"`. Precedence is " -"package.json/perry.toml config → `--strict-eval` (opts in). The legacy " -"`PERRY_ALLOW_EVAL=1` environment variable still works: it forces non-strict " -"(defer) mode for a one-off build, overriding any strict flag/config." +"`--strict-eval` remains accepted but does not reject bodies sent to the " +"interpreter. `--strict-dynamic-import` still rejects unresolved asynchronous" +" import specifiers during module collection; synchronous `require()` takes " +"its runtime resolution path. `PERRY_ALLOW_EVAL=1` disables that strict " +"import check." msgstr "" -"`perry.eval` acepta `\"defer\"` (el predeterminado) o `\"error\"`. " -"Precedencia es package.json/perry.toml config → `--strict-eval` (opciones " -"en). La variable de entorno `PERRY_ALLOW_EVAL=1` sigue funcionando: fuerza " -"el modo no restringido para una construcción única, superando cualquier flag/" -"config" +"`--strict-eval` se acepta pero no rechaza cuerpos enviados al intérprete. " +"`--strict-dynamic-import` sigue rechazando especificadores asíncronos no " +"resueltos durante la recopilación de módulos; `require()` síncrono usa " +"resolución en ejecución. `PERRY_ALLOW_EVAL=1` desactiva esa comprobación " +"estricta." -#: src/language/limitations.md:161 -msgid "" -"The same strict controls apply to runtime-computed dynamic `import()` " -"(#5230). The broad `perry.strict = true` covers both eval and dynamic " -"import. For a knob scoped to dynamic imports only, use `--strict-dynamic-" -"import` or `\"perry\": { \"dynamicImport\": \"error\" }` (accepts " -"`\"defer\"` (default) or `\"error\"`); the dedicated knob overrides the " -"broad `perry.strict` for import sites. `PERRY_ALLOW_EVAL=1` is the shared " -"AOT escape hatch — it forces defer for both eval and dynamic import." -msgstr "" -"Los mismos controles estrictos se aplican a la dinámica `import()` computada " -"en tiempo de ejecución (#5230). La amplia `perry.strict = true` cubre tanto " -"la importación eval como la dinámica. Para una perilla con alcances de " -"importación dinámicos, utilice `--strict-dynamic-import` o `\"perry\": " -"{ \"dynamicImport\": \"error\" }` (acepta `\"defer\"` (por defecto) o " -"`\"error\"`); la perilla dedicada sobrescribe la amplia `perry.strict` para " -"sitios de importación. `PERRY_ALLOW_EVAL=1` es la escotilla de escape AOT " -"compartida, que obliga a diferir tanto para la importación eval como " -"dinámica." - -#: src/language/limitations.md:169 -msgid "" -"Test262 rows that only observe parsing or executing a code string remain " -"intentional AOT exclusions, not runtime dynamic-code work. This includes the " -"`language/white-space/comment-{multi,single}-{form-feed,horizontal-tab,nbsp," -"space,vertical-tab}.js` rows and the direct-eval reference row `language/" -"types/reference/8.7.2-1-s.js`; they map to the AOT eval tracker (#1677), " -"eval classifier diagnostics (#1678), and the limited literal `Function` " -"folding work (#1679)." -msgstr "" -"Las filas de Test262 que solo observan el análisis sintáctico o la ejecución " -"de una cadena de código siguen siendo exclusiones AOT intencionales, no " -"trabajo de código dinámico en tiempo de ejecución. Esto incluye las filas " -"`language/white-space/comment-{multi,single}-{form-feed,horizontal-tab,nbsp," -"space,vertical-tab}.js` y la fila de referencia de eval directo `language/" -"types/reference/8.7.2-1-s.js`; se corresponden con el rastreador de eval AOT " -"(#1677), los diagnósticos del clasificador de eval (#1678) y el trabajo de " -"plegado limitado de `Function` literal (#1679)." - -#: src/language/limitations.md:178 +#: src/language/limitations.md:87 msgid "" "Perry parses decorator syntax, supports compile-time-only transforms (see " "the bundled `@log` example), and has a reduced legacy TypeScript " @@ -6491,7 +6349,7 @@ msgstr "" "`Reflect.getMetadataKeys`, `Reflect.getOwnMetadataKeys`, `Reflect." "deleteMetadata` y `@Reflect.metadata(...)`." -#: src/language/limitations.md:190 +#: src/language/limitations.md:99 msgid "" "Accessor decorators, descriptor replacement, general `Reflect.metadata(...)` " "calls outside decorator syntax, `Symbol` metadata keys, and full Angular / " @@ -6504,11 +6362,11 @@ msgstr "" "tiempo de ejecución de Angular / NestJS / TypeORM no están soportados. Ver " "[Decorators](decorators.md) para detalles y una guía de migración detallada." -#: src/language/limitations.md:196 +#: src/language/limitations.md:105 msgid "No Runtime Metadata Reflection" msgstr "Sin reflexión de metadatos en tiempo de ejecución" -#: src/language/limitations.md:198 +#: src/language/limitations.md:107 msgid "" "Perry implements a small metadata subset for legacy decorators. General " "runtime reflection is not supported:" @@ -6516,7 +6374,7 @@ msgstr "" "Perry implementa un subconjunto reducido de metadatos para decoradores " "heredados. La reflexión general en tiempo de ejecución no está soportada:" -#: src/language/limitations.md:202 +#: src/language/limitations.md:111 msgid "" "```text\n" "Reflect.getMetadata(\"design:type\", target, key);\n" @@ -6532,71 +6390,33 @@ msgstr "" "Reflect.metadata(\"design:type\", String)(target, key);\n" "```" -#: src/language/limitations.md:209 -msgid "No User-Space CommonJS require()" -msgstr "Sin espacio de usuario CommonJS require()" - -#: src/language/limitations.md:211 -msgid "Use static ESM imports in Perry source:" -msgstr "Usa importaciones ESM estáticas en código fuente Perry:" - -#: src/language/limitations.md:214 -msgid "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" -msgstr "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" - -#: src/language/limitations.md:223 -msgid "" -"Perry has internal CommonJS compatibility paths for some npm package " -"wrappers, but user-written modules should use static `import` declarations." -msgstr "" -"Perry tiene rutas de compatibilidad internas con CommonJS para algunos " -"wrappers de paquetes npm, pero los módulos escritos por el usuario deben " -"usar declaraciones `import` estáticas." +#: src/language/limitations.md:118 +msgid "JavaScript and CommonJS" +msgstr "JavaScript y CommonJS" -#: src/language/limitations.md:226 +#: src/language/limitations.md:120 msgid "" -"**JavaScript source compiles too.** Perry accepts `.js`, `.cjs`, `.mjs`, and " -"`.jsx` files as compiler input — they are parsed as JavaScript and lowered " -"through the same native pipeline as TypeScript, so no type annotations are " -"required. The limitations on this page still apply (no `eval`, no general " -"dynamic `require()`, etc.), but plain JavaScript projects compile and run in " -"most cases." +"Perry accepts `.js`, `.cjs`, `.mjs` and `.jsx` through the native pipeline. " +"CommonJS `require()` is supported for compiled modules and supported native " +"modules. It does not turn the executable into a general filesystem " +"JavaScript loader." msgstr "" -"**La fuente JavaScript también se compila.** Perry acepta archivos `.js`, `." -"cjs`, `.mjs` y `.jsx` como entrada del compilador, que son analizados como " -"JavaScript y bajados a través de la misma tubería nativa como TypeScript, " -"por lo que no se requieren anotaciones de tipo. Las limitaciones en esta " -"página siguen siendo aplicables (no `eval`, no `require()` dinámico general, " -"etc.), pero los proyectos JavaScript simples compilar y ejecutar en la " -"mayoría de los casos." +"Perry acepta `.js`, `.cjs`, `.mjs` y `.jsx` por la canalización nativa. " +"CommonJS `require()` admite módulos compilados y módulos nativos " +"compatibles. No convierte el ejecutable en un cargador JavaScript general " +"del sistema de archivos." -#: src/language/limitations.md:233 +#: src/language/limitations.md:122 msgid "Prototype Manipulation" msgstr "Manipulación del prototipo" -#: src/language/limitations.md:235 +#: src/language/limitations.md:124 msgid "Dynamic prototype manipulation is supported for the common patterns:" msgstr "" "La manipulación dinámica de prototipos es compatible con los patrones " "comunes:" -#: src/language/limitations.md:237 +#: src/language/limitations.md:126 msgid "" "```typescript,no-test\n" "// Supported\n" @@ -6620,7 +6440,7 @@ msgstr "" "Object.getPrototypeOf(obj); // inspection\n" "```" -#: src/language/limitations.md:245 +#: src/language/limitations.md:134 msgid "" "Prototype-method assignment routes through a dedicated registry " "(`CLASS_PROTOTYPE_METHODS`) and `Object.setPrototypeOf` performs a real, " @@ -6631,11 +6451,11 @@ msgstr "" "prototipo real, controlada por ciclo (un ciclo genuino todavía lanza " "`TypeError`)." -#: src/language/limitations.md:249 +#: src/language/limitations.md:138 msgid "Remaining known gaps:" msgstr "Faltas conocidas:" -#: src/language/limitations.md:251 +#: src/language/limitations.md:140 msgid "" "the assignment form `obj.__proto__ = x` on the dynamic write path stores a " "literal `\"__proto__\"` property instead of setting the prototype (use " @@ -6645,7 +6465,7 @@ msgstr "" "dinámica almacena una propiedad literal `\"__proto__\"` en lugar de " "configurar el prototipo (utiliza `Object.setPrototypeOf`);" -#: src/language/limitations.md:254 +#: src/language/limitations.md:143 msgid "" "an object-literal `{ __proto__: [] }` sets the prototype without throwing, " "but the result is not yet visible to `instanceof` (`… instanceof Array` " @@ -6655,81 +6475,31 @@ msgstr "" "pero el resultado no es aún visible para `instanceof` (`… instanceof Array` " "informa `false`)." -#: src/language/limitations.md:258 -msgid "Weak References Retain Their Targets" -msgstr "Las referencias débiles retienen sus objetivos" - -#: src/language/limitations.md:260 -msgid "" -"`WeakMap`, `WeakSet`, `WeakRef`, and `FinalizationRegistry` are implemented " -"and their APIs behave as expected — `set` / `get` / `has` / `delete`, `add`, " -"`deref()`, and `register` / `unregister` all work and return the right " -"values. `WeakMap` and `WeakSet` use **reference** equality, so two distinct " -"objects never collide on the same slot." -msgstr "" -"`WeakMap`, `WeakSet`, `WeakRef` y `FinalizationRegistry` están implementados " -"y sus APIs se comportan como se espera — `set` / `get` / `has` / `delete`, " -"`add`, `deref()` y `register` / `unregister` funcionan y devuelven los " -"valores correctos. `WeakMap` y `WeakSet` usan igualdad por **referencia**, " -"por lo que dos objetos distintos nunca colisionan en el mismo slot." - -#: src/language/limitations.md:266 -msgid "" -"The one caveat is that Perry's garbage collector does not yet treat these " -"references as _weak_, so targets are **retained rather than collected**. The " -"current runtime stores `WeakRef` targets and `FinalizationRegistry` " -"registrations in ordinary object/array fields (`crates/perry-runtime/src/" -"weakref.rs`), and the adjacent GC root scanners do not have a weak-slot " -"clearing/finalizer queue hook yet. In practice:" -msgstr "" -"La única advertencia es que el recolector de basura de Perry aún no trata " -"estas referencias como _débiles_, por lo que los objetivos se **retienen en " -"lugar de recolectarse**. El runtime actual almacena los objetivos de " -"`WeakRef` y los registros de `FinalizationRegistry` en campos ordinarios de " -"objetos/arrays (`crates/perry-runtime/src/weakref.rs`), y los escáneres de " -"raíces de GC adyacentes aún no tienen un gancho de cola de limpieza/" -"finalización de slots débiles. En la práctica:" - -#: src/language/limitations.md:273 -msgid "" -"`WeakRef.deref()` always returns the original target (it is never reported " -"as collected)." -msgstr "" -"`WeakRef.deref()` siempre devuelve el objetivo original (nunca se reporta " -"como recolectado)." - -#: src/language/limitations.md:275 -msgid "" -"`FinalizationRegistry` records registrations but never fires its cleanup " -"callback." -msgstr "" -"`FinalizationRegistry` registra las inscripciones pero nunca ejecuta su " -"callback de limpieza." - -#: src/language/limitations.md:277 -msgid "" -"`WeakMap` / `WeakSet` keep their keys alive (they behave like a reference-" -"keyed `Map` / `Set`)." -msgstr "" -"`WeakMap` / `WeakSet` mantienen vivas sus claves (se comportan como un " -"`Map` / `Set` indexado por referencia)." +#: src/language/limitations.md:147 +msgid "Weak references and finalization" +msgstr "Referencias débiles y finalización" -#: src/language/limitations.md:280 +#: src/language/limitations.md:149 msgid "" -"This is safe for **correctness** — code that reads through these APIs gets " -"the right values. It only matters if you depend on collection _timing_ to " -"reclaim memory or to run finalizer side effects." +"WeakMap, WeakSet, WeakRef and FinalizationRegistry participate in weak " +"processing. Weak targets are excluded from the strong trace, forwarded " +"addresses are repaired, dead targets can be cleared and cleanup callbacks " +"can be queued. Collection and callback timing are not deterministic. See [GC" +" weak processing](../internals/garbage-collector.md#barriers-and-weak-" +"references)." msgstr "" -"Esto es seguro en cuanto a **corrección** — el código que lee a través de " -"estas APIs obtiene los valores correctos. Solo importa si dependes del " -"_momento_ de recolección para liberar memoria o ejecutar efectos secundarios " -"de finalizadores." +"WeakMap, WeakSet, WeakRef y FinalizationRegistry participan en el " +"procesamiento débil. Sus destinos se excluyen del trazado fuerte; se reparan" +" direcciones reenviadas, pueden borrarse destinos muertos y encolarse " +"callbacks de limpieza. El momento de recolección y callbacks no es " +"determinista. Véase [procesamiento débil del GC](../internals/garbage-" +"collector.md#barriers-and-weak-references)." -#: src/language/limitations.md:284 +#: src/language/limitations.md:151 msgid "Limited Proxy Trapping" msgstr "Interceptación de Proxy limitada" -#: src/language/limitations.md:286 +#: src/language/limitations.md:153 msgid "" "Proxy support is not a full engine-level trap layer for every possible " "dynamic object access. Prefer plain objects and explicit APIs unless a " @@ -6740,11 +6510,11 @@ msgstr "" "explícitas a menos que un paquete solo necesite la superficie de Proxy " "soportada por Perry." -#: src/language/limitations.md:290 +#: src/language/limitations.md:157 msgid "Threading Model" msgstr "Modelo de hilos" -#: src/language/limitations.md:292 +#: src/language/limitations.md:159 msgid "" "Perry supports real multi-threading via `parallelMap` and `spawn` from " "`perry/thread`. See [Multi-Threading](../threading/overview.md)." @@ -6752,7 +6522,7 @@ msgstr "" "Perry admite multihilo real mediante `parallelMap` y `spawn` de `perry/" "thread`. Consulta [Multihilo](../threading/overview.md)." -#: src/language/limitations.md:294 +#: src/language/limitations.md:161 msgid "" "Threads do not share mutable state by default — closures passed to thread " "primitives cannot capture mutable variables (enforced at compile time), and " @@ -6776,11 +6546,11 @@ msgstr "" "matriz mecanografiada _view_ capturada directamente aún copias profundas, " "por lo que construir la vista por agente desde el SAB compartido." -#: src/language/limitations.md:305 +#: src/language/limitations.md:172 msgid "WebAssembly (#6558)" msgstr "WebAssembly (#6558)" -#: src/language/limitations.md:307 +#: src/language/limitations.md:174 msgid "" "Perry ships **no WebAssembly engine** in the default build. The adopted " "policy is that **WASM-dependent features degrade gracefully**. Closed issue " @@ -6795,7 +6565,7 @@ msgstr "" "por módulo; no es una promesa activa de añadir un motor completo al binario " "predeterminado." -#: src/language/limitations.md:313 +#: src/language/limitations.md:180 msgid "" "The `WebAssembly` global is spec-shaped — every standard member exists with " "the correct type and arity — and fails cleanly rather than crashing:" @@ -6804,7 +6574,7 @@ msgstr "" "estándar existe con el tipo y la aridad correctos— y falla limpiamente en " "lugar de estrellarse:" -#: src/language/limitations.md:316 +#: src/language/limitations.md:183 msgid "" "`WebAssembly.compile` / `compileStreaming` / `instantiate` / " "`instantiateStreaming` return a Promise **rejected** with a `WebAssembly." @@ -6814,7 +6584,7 @@ msgstr "" "`instantiateStreaming` devolver una promesa **rechazada** con un " "`WebAssembly.CompileError` cuyo mensaje apunta al #6558." -#: src/language/limitations.md:319 +#: src/language/limitations.md:186 msgid "" "`WebAssembly.validate(bytes)` returns `false` — the honest answer for \"can " "I run this module here\"." @@ -6822,7 +6592,7 @@ msgstr "" "`WebAssembly.validate(bytes)` devuelve `false` — la respuesta honesta para " "\"puedo ejecutar este módulo aquí\"." -#: src/language/limitations.md:321 +#: src/language/limitations.md:188 msgid "" "`new WebAssembly.Module(...)` throws `CompileError` synchronously; " "`Instance` throws `LinkError`; `Table` / `Global` throw `RuntimeError`." @@ -6830,7 +6600,7 @@ msgstr "" "`new WebAssembly.Module(...)` lanza `CompileError` sincrónicamente; " "`Instance` lanza `LinkError`; `Table` / `Global` lanza `RuntimeError`." -#: src/language/limitations.md:323 +#: src/language/limitations.md:190 msgid "" "`new WebAssembly.Memory({ initial })` genuinely works (a real zero-filled " "`ArrayBuffer` backs it, and `grow()` is supported), so feature-detection " @@ -6840,7 +6610,7 @@ msgstr "" "real lleno cero lo respalda, y `grow()` es compatible), por lo que el código " "de detección de características que asigna una página tiene éxito." -#: src/language/limitations.md:326 +#: src/language/limitations.md:193 msgid "" "`CompileError` / `LinkError` / `RuntimeError` are real error constructors " "(`instanceof Error` and `instanceof WebAssembly.CompileError` both work)." @@ -6849,7 +6619,7 @@ msgstr "" "reales (`instanceof Error` y `instanceof WebAssembly.CompileError` ambos " "funcionan)." -#: src/language/limitations.md:329 +#: src/language/limitations.md:196 msgid "" "In practice this means lazy wasm consumers — wasm-bindgen loaders like " "`@silvia-odwyer/photon-node`, `@jsquash/webp` decoders, undici's llhttp " @@ -6861,7 +6631,7 @@ msgstr "" "`@jsquash/webp`, la sonda lhttp de undici— golpean sus propias rutas catch/" "fallback y la aplicación sigue ejecutándose con esa característica degradada." -#: src/language/limitations.md:334 +#: src/language/limitations.md:201 msgid "" "When the wasmi host is linked, `new WebAssembly.Module(bytes)` and the " "synchronous `new WebAssembly.Instance(module, imports)` constructor execute " @@ -6884,11 +6654,11 @@ msgstr "" "de funciones de referencia general y streaming permanecen fuera de ese " "subconjunto." -#: src/language/limitations.md:344 +#: src/language/limitations.md:211 msgid "Two spellings, two paths:" msgstr "Dos ortografías, dos caminos:" -#: src/language/limitations.md:346 +#: src/language/limitations.md:213 msgid "" "**Namespace access** (`const WA = globalThis.WebAssembly; WA.compile(...)`, " "which is also what minified bundles evaluate through a namespace value) uses " @@ -6899,7 +6669,7 @@ msgstr "" "través de un valor de espacio de nombres) utiliza la superficie elegante de " "arriba y no necesita nada en el momento del enlace." -#: src/language/limitations.md:349 +#: src/language/limitations.md:216 #, fuzzy msgid "" "**Literal static calls** (`WebAssembly.instantiate(bytes)` written against " @@ -6917,15 +6687,15 @@ msgstr "" "build --release -p perry-wasm-host`); sin él la construcción falla con esa " "instrucción." -#: src/language/limitations.md:356 +#: src/language/limitations.md:223 msgid "npm Package Compatibility" msgstr "Compatibilidad con paquetes npm" -#: src/language/limitations.md:358 +#: src/language/limitations.md:225 msgid "Not all npm packages work with Perry:" msgstr "No todos los paquetes npm funcionan con Perry:" -#: src/language/limitations.md:360 +#: src/language/limitations.md:227 msgid "" "**Natively supported**: ~50 popular packages (fastify, mysql2, redis, etc.) " "— these are compiled natively. See [Standard Library](../stdlib/overview.md)." @@ -6934,7 +6704,7 @@ msgstr "" "redis, etc.) — estos se compilan de forma nativa. Ver [Standard Library](../" "stdlib/overview.md)." -#: src/language/limitations.md:361 +#: src/language/limitations.md:228 msgid "" "**`compilePackages`**: Pure TS/JS packages can be compiled natively via " "[configuration](../getting-started/project-config.md)." @@ -6942,53 +6712,33 @@ msgstr "" "**`compilePackages`**: Los paquetes TS/JS puros pueden compilarse de forma " "nativa mediante [configuración](../getting-started/project-config.md)." -#: src/language/limitations.md:362 +#: src/language/limitations.md:229 msgid "" -"**Not supported**: Packages requiring native addons (`.node` files), " -"`eval()`, dynamic `require()`, or Node.js internals." +"Native addons require the supported [Node-API host](../internals/node-api-" +"host.md), explicit authorization and a matching target binary. V8/NAN/Node " +"C++ addons are not interchangeable with Node-API addons. Dynamic loading " +"remains constrained by the compiled graph." msgstr "" -"**No soportados**: Paquetes que requieren addons nativos (archivos `.node`), " -"`eval()`, `require()` dinámico o elementos internos de Node.js." +"Los addons nativos requieren el [host Node-API](../internals/node-api-" +"host.md) admitido, autorización explícita y un binario adecuado al destino. " +"Los addons V8/NAN/Node C++ no son intercambiables con Node-API. La carga " +"dinámica sigue limitada por el grafo compilado." -#: src/language/limitations.md:364 +#: src/language/limitations.md:231 msgid "Workarounds" msgstr "Alternativas" -#: src/language/limitations.md:366 -msgid "Dynamic Behavior" -msgstr "Comportamiento dinámico" - -#: src/language/limitations.md:368 -msgid "" -"For cases where you need dynamic behavior, use the JavaScript runtime " -"fallback:" -msgstr "" -"Para los casos en que necesitas comportamiento dinámico, usa el fallback del " -"runtime de JavaScript:" - -#: src/language/limitations.md:371 -msgid "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" -msgstr "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" - -#: src/language/limitations.md:376 +#: src/language/limitations.md:233 msgid "Type Narrowing" msgstr "Estrechamiento de tipos" -#: src/language/limitations.md:378 +#: src/language/limitations.md:235 msgid "Since there's no runtime type checking, use explicit checks:" msgstr "" "Como no hay comprobación de tipos en tiempo de ejecución, usa comprobaciones " "explícitas:" -#: src/language/limitations.md:380 +#: src/language/limitations.md:237 msgid "" "```typescript\n" "function processValue(value: string | number) {\n" @@ -7016,11 +6766,11 @@ msgstr "" "}\n" "```" -#: src/language/limitations.md:395 +#: src/language/limitations.md:252 msgid "[Supported Features](supported-features.md) — What does work" msgstr "[Funcionalidades soportadas](supported-features.md) — Qué sí funciona" -#: src/language/limitations.md:396 +#: src/language/limitations.md:253 msgid "[Type System](type-system.md) — How types are handled" msgstr "[Sistema de tipos](type-system.md) — Cómo se gestionan los tipos" @@ -7176,8 +6926,8 @@ msgstr "" #: src/packages/porting.md:35 msgid "" -"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a " -"`\"gypfile\"` package marker are compiled against Node's native addon ABI. " +"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a `" +"\"gypfile\"` package marker are compiled against Node's native addon ABI. " "The JavaScript entry point is usually just loader glue around a shared " "library that expects Node-API/N-API, NAN, V8, libuv, or Node internals. " "Perry does not host that ABI as part of `compilePackages`. Common examples " @@ -8092,7 +7842,7 @@ msgid "Documented at" msgstr "Documentado en" #: src/native-libraries/overview.md:186 src/native-libraries/abi.md:82 -#: src/threading/overview.md:342 src/threading/parallel-map.md:57 +#: src/threading/overview.md:327 src/threading/parallel-map.md:57 msgid "Strings" msgstr "Cadenas" @@ -8576,14 +8326,14 @@ msgstr "" msgid "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" msgstr "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" #: src/native-libraries/overview.md:351 @@ -8911,7 +8661,7 @@ msgstr "" "`well_known_bindings.toml`. Regenerar esta tabla con `python3 scripts/" "binding_governance.py --table`." -#: src/native-libraries/governance.md:99 src/contributing/architecture.md:24 +#: src/native-libraries/governance.md:99 src/contributing/architecture.md:25 msgid "Crate" msgstr "Crate" @@ -8936,7 +8686,7 @@ msgstr "Situación actual" msgid "`perry-ext-ads`" msgstr "`perry-ext-ads`" -#: src/native-libraries/governance.md:101 src/api/reference.md:2172 +#: src/native-libraries/governance.md:101 src/api/reference.md:2174 msgid "`perry/ads`" msgstr "`perry/ads`" @@ -8956,7 +8706,7 @@ msgstr "Agrupado; pendiente de expulsión" msgid "`perry-ext-argon2`" msgstr "`perry-ext-argon2`" -#: src/native-libraries/governance.md:102 src/api/reference.md:214 +#: src/native-libraries/governance.md:102 src/api/reference.md:215 msgid "`argon2`" msgstr "`argon2`" @@ -8996,7 +8746,7 @@ msgstr "Agrupado; migración pendiente" msgid "`perry-ext-bcrypt`" msgstr "`perry-ext-bcrypt`" -#: src/native-libraries/governance.md:103 src/api/reference.md:322 +#: src/native-libraries/governance.md:103 src/api/reference.md:323 msgid "`bcrypt`" msgstr "`bcrypt`" @@ -9004,7 +8754,7 @@ msgstr "`bcrypt`" msgid "`perry-ext-better-sqlite3`" msgstr "`perry-ext-better-sqlite3`" -#: src/native-libraries/governance.md:104 src/api/reference.md:329 +#: src/native-libraries/governance.md:104 src/api/reference.md:330 msgid "`better-sqlite3`" msgstr "`better-sqlite3`" @@ -9012,7 +8762,7 @@ msgstr "`better-sqlite3`" msgid "`perry-ext-cheerio`" msgstr "`perry-ext-cheerio`" -#: src/native-libraries/governance.md:105 src/api/reference.md:489 +#: src/native-libraries/governance.md:105 src/api/reference.md:491 msgid "`cheerio`" msgstr "`cheerio`" @@ -9036,7 +8786,7 @@ msgstr "Compilar la fuente del paquete" msgid "`perry-ext-ethers`" msgstr "`perry-ext-ethers`" -#: src/native-libraries/governance.md:106 src/api/reference.md:1194 +#: src/native-libraries/governance.md:106 src/api/reference.md:1196 msgid "`ethers`" msgstr "`ethers`" @@ -9073,7 +8823,7 @@ msgstr "Agrupados; retenidos" msgid "`perry-ext-net`" msgstr "`perry-ext-net`" -#: src/native-libraries/governance.md:108 src/api/reference.md:1857 +#: src/native-libraries/governance.md:108 src/api/reference.md:1859 msgid "`net`" msgstr "`net`" @@ -9081,7 +8831,7 @@ msgstr "`net`" msgid "`perry-ext-nodemailer`" msgstr "`perry-ext-nodemailer`" -#: src/native-libraries/governance.md:109 src/api/reference.md:2015 +#: src/native-libraries/governance.md:109 src/api/reference.md:2017 msgid "`nodemailer`" msgstr "`nodemailer`" @@ -9105,7 +8855,7 @@ msgstr "" msgid "`perry-ext-pdf`" msgstr "`perry-ext-pdf`" -#: src/native-libraries/governance.md:111 src/api/reference.md:192 +#: src/native-libraries/governance.md:111 src/api/reference.md:193 msgid "`@perryts/pdf`" msgstr "`@perryts/pdf`" @@ -9113,7 +8863,7 @@ msgstr "`@perryts/pdf`" msgid "`perry-ext-sharp`" msgstr "`perry-ext-sharp`" -#: src/native-libraries/governance.md:112 src/api/reference.md:2914 +#: src/native-libraries/governance.md:112 src/api/reference.md:2923 msgid "`sharp`" msgstr "`sharp`" @@ -9121,7 +8871,7 @@ msgstr "`sharp`" msgid "`perry-ext-typescript`" msgstr "`perry-ext-typescript`" -#: src/native-libraries/governance.md:113 src/api/reference.md:3363 +#: src/native-libraries/governance.md:113 src/api/reference.md:3383 msgid "`typescript`" msgstr "`typescript`" @@ -9129,7 +8879,7 @@ msgstr "`typescript`" msgid "`perry-ext-undici`" msgstr "`perry-ext-undici`" -#: src/native-libraries/governance.md:114 src/api/reference.md:3376 +#: src/native-libraries/governance.md:114 src/api/reference.md:3396 msgid "`undici`" msgstr "`undici`" @@ -9137,7 +8887,7 @@ msgstr "`undici`" msgid "`perry-ext-ws`" msgstr "`perry-ext-ws`" -#: src/native-libraries/governance.md:115 src/api/reference.md:3681 +#: src/native-libraries/governance.md:115 src/api/reference.md:3701 msgid "`ws`" msgstr "`ws`" @@ -9145,7 +8895,7 @@ msgstr "`ws`" msgid "`perry-ext-zlib`" msgstr "`perry-ext-zlib`" -#: src/native-libraries/governance.md:116 src/api/reference.md:3721 +#: src/native-libraries/governance.md:116 src/api/reference.md:3741 msgid "`zlib`" msgstr "`zlib`" @@ -9375,7 +9125,7 @@ msgstr "\"js_pdf_parse\"" #: src/native-libraries/manifest-v1.md:155 #: src/native-libraries/manifest-v1.md:161 #: src/native-libraries/manifest-v1.md:192 -#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:596 +#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:618 msgid "\"kind\"" msgstr "\"malos\"" @@ -9468,20 +9218,20 @@ msgstr "" #: src/native-libraries/authoring-guide.md:150 msgid "" "Manifest descriptors are native ABI descriptors, not TypeScript surface " -"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and " -"`\"void\"` still work. Use the explicit vocabulary when you need native " -"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, " -"`\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " -"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": " -"\"jsvalue\" }`." +"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and `" +"\"void\"` still work. Use the explicit vocabulary when you need native " +"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, `" +"\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " +"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": \"jsvalue" +"\" }`." msgstr "" "Los descriptores del manifiesto son descriptores ABI nativos, no tipos de " -"superficie TypeScript. Las cadenas existentes como `\"string\"`, " -"`\"number\"`, `\"i64\"` y `\"void\"` siguen funcionando. Usa el vocabulario " -"explícito cuando necesites precisión nativa o metadatos: `\"f32\"`, " -"`\"u32\"`, `\"u64\"`, `\"usize\"`, `\"buffer_len\"`, `{ \"kind\": " -"\"buffer+len\" }`, `{ \"kind\": \"handle\", \"type\": \"MyThing\" }` y " -"`{ \"kind\": \"promise\", \"result\": \"jsvalue\" }`." +"superficie TypeScript. Las cadenas existentes como `\"string\"`, `\"number" +"\"`, `\"i64\"` y `\"void\"` siguen funcionando. Usa el vocabulario explícito " +"cuando necesites precisión nativa o metadatos: `\"f32\"`, `\"u32\"`, `" +"\"u64\"`, `\"usize\"`, `\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, " +"`{ \"kind\": \"handle\", \"type\": \"MyThing\" }` y `{ \"kind\": \"promise" +"\", \"result\": \"jsvalue\" }`." #: src/native-libraries/authoring-guide.md:158 msgid "" @@ -9491,10 +9241,10 @@ msgid "" "thread affinity, debug names, and an owned-return finalizer:" msgstr "" "Los handles son objetos handle nativos opacos de Perry en JavaScript, no " -"números sin procesar. Los descriptores heredados `\"handle\"` y " -"`\"handle\"` se siguen interpretando como handles prestados; los " -"descriptores estructurados pueden agregar propiedad, anulabilidad, afinidad " -"de hilo, nombres de depuración y un finalizador para returns de tipo owned:" +"números sin procesar. Los descriptores heredados `\"handle\"` y `\"handle" +"\"` se siguen interpretando como handles prestados; los descriptores " +"estructurados pueden agregar propiedad, anulabilidad, afinidad de hilo, " +"nombres de depuración y un finalizador para returns de tipo owned:" #: src/native-libraries/authoring-guide.md:166 #: src/native-libraries/manifest-v1.md:142 @@ -9566,8 +9316,8 @@ msgid "" msgstr "" "El símbolo del finalizador debe usar el ABI `void(ptr, ptr)` y solo debe " "liberar recursos nativos. Puede ejecutarse desde la finalización del GC, por " -"lo que no debe llamar a las APIs JS de Perry ni asignar valores JS. Usa " -"`\"ptr\"` cuando una API acepta intencionalmente un payload de puntero sin " +"lo que no debe llamar a las APIs JS de Perry ni asignar valores JS. Usa `" +"\"ptr\"` cuando una API acepta intencionalmente un payload de puntero sin " "procesar en lugar de un handle administrado." #: src/native-libraries/authoring-guide.md:181 @@ -10691,8 +10441,8 @@ msgid "" "Map TypeScript-side function calls onto the right `extern \"C\"` symbol with " "the right calling convention." msgstr "" -"Mapear las llamadas a funciones del lado TypeScript al símbolo `extern " -"\"C\"` correcto con la convención de llamada adecuada." +"Mapear las llamadas a funciones del lado TypeScript al símbolo `extern \"C" +"\"` correcto con la convención de llamada adecuada." #: src/native-libraries/manifest-v1.md:16 msgid "" @@ -10889,7 +10639,7 @@ msgstr "Requerido" #: src/tui/widgets.md:55 src/tui/hooks.md:169 src/platforms/tvos.md:50 #: src/platforms/tvos.md:149 src/platforms/watchos.md:137 #: src/platforms/wearos.md:23 src/container/containers.md:133 -#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:96 +#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:118 #: src/cli/cache-dir.md:42 src/internals/node-api-host.md:588 #: src/internals/node-api-host.md:624 src/internals/node-api-host.md:638 #: src/internals/node-api-host.md:649 @@ -10955,17 +10705,18 @@ msgstr "string" #: src/testing/http2-conformance.md:424 src/testing/http2-conformance.md:425 #: src/testing/http2-conformance.md:426 src/testing/http2-conformance.md:427 #: src/testing/http2-conformance.md:428 src/testing/http2-conformance.md:429 -#: src/testing/ci-tiers.md:39 src/testing/ci-tiers.md:40 -#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:42 -#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:45 -#: src/testing/ci-tiers.md:46 src/testing/ci-tiers.md:48 -#: src/testing/ci-tiers.md:49 src/testing/ci-tiers.md:50 -#: src/testing/ci-tiers.md:51 src/testing/ci-tiers.md:52 +#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:45 src/testing/ci-tiers.md:46 +#: src/testing/ci-tiers.md:47 src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:50 src/testing/ci-tiers.md:52 #: src/testing/ci-tiers.md:53 src/testing/ci-tiers.md:54 #: src/testing/ci-tiers.md:55 src/testing/ci-tiers.md:56 #: src/testing/ci-tiers.md:57 src/testing/ci-tiers.md:58 #: src/testing/ci-tiers.md:59 src/testing/ci-tiers.md:60 #: src/testing/ci-tiers.md:61 src/testing/ci-tiers.md:62 +#: src/testing/ci-tiers.md:63 src/testing/ci-tiers.md:64 +#: src/testing/ci-tiers.md:65 src/testing/ci-tiers.md:66 +#: src/testing/ci-tiers.md:67 src/testing/ci-tiers.md:68 #: src/internals/rfc-rooting-by-construction.md:277 #: src/internals/rfc-rooting-by-construction.md:278 #: src/internals/rfc-rooting-by-construction.md:279 @@ -11040,8 +10791,8 @@ msgid "" "Unlike handles and promises, `pod` has no string-only spelling; use object " "form so the field order and scalar ABI types are explicit. `json` is " "parameter-only: it serializes its argument with `JSON.stringify` at the " -"callsite and passes the result in a `string`\\-shaped slot (see \"Param " -"types\")." +"callsite and passes the result in a `string`\\-shaped slot (see \"Param types" +"\")." msgstr "" "`number` es un alias de compatibilidad para `f64`; `js_value` y `boolean` " "son alias de compatibilidad para `jsvalue` y `bool`. El `handle` desnudo es " @@ -11203,7 +10954,7 @@ msgstr "boolean" #: src/testing/http2-conformance.md:289 src/cli/perry-toml.md:178 #: src/cli/perry-toml.md:257 src/cli/perry-toml.md:312 #: src/cli/perry-toml.md:369 src/cli/updates.md:38 -#: src/internals/memory-model.md:19 +#: src/internals/memory-model.md:21 msgid "`false`" msgstr "`false`" @@ -11714,7 +11465,7 @@ msgstr "`\"void\"`" msgid "`-> ()`" msgstr "`-> ()`" -#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:17 +#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:19 msgid "`undefined`" msgstr "`undefined`" @@ -11732,29 +11483,29 @@ msgstr "" #: src/native-libraries/manifest-v1.md:276 msgid "" "Note on `\"string\"` vs. `\"i64_str\"`: both produce a string on the " -"TypeScript side, but they differ in how Rust returns the pointer. Use " -"`\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " +"TypeScript side, but they differ in how Rust returns the pointer. Use `" +"\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " "u8` (or `*const StringHeader`); use `\"i64_str\"` when it's `-> i64` and the " "value happens to be a `StringHeader` address (closes [\\#222](https://github." "com/PerryTS/perry/issues/222))." msgstr "" "Nota sobre `\"string\"` vs. `\"i64_str\"`: ambos producen una cadena en el " -"lado TypeScript, pero difieren en cómo Rust devuelve el puntero. Use " -"`\"string\"` / `\"ptr\"` cuando su `extern \"C\" fn` esté declarada como `-> " +"lado TypeScript, pero difieren en cómo Rust devuelve el puntero. Use `" +"\"string\"` / `\"ptr\"` cuando su `extern \"C\" fn` esté declarada como `-> " "*const u8` (o `*const StringHeader`); use `\"i64_str\"` cuando sea `-> i64` " "y el valor sea una dirección de `StringHeader` (cierra [\\#222](https://" "github.com/PerryTS/perry/issues/222))." #: src/native-libraries/manifest-v1.md:283 msgid "" -"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, " -"`\"json\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " +"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, `\"json" +"\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " "descriptors: `\"buffer+len\"` expands one JavaScript argument into two " "native ABI slots, while `pod` lowers one object-shaped argument to a pointer " "to verifier-backed C-layout storage." msgstr "" -"`\"void\"` es válido sólo como descriptor de retorno. `\"buffer+len\"`, " -"`\"json\"` y `{ \"kind\": \"pod\", ... }` son válidos sólo como descriptores " +"`\"void\"` es válido sólo como descriptor de retorno. `\"buffer+len\"`, `" +"\"json\"` y `{ \"kind\": \"pod\", ... }` son válidos sólo como descriptores " "de parámetros: `\"buffer+len\"` expande un argumento JavaScript en dos " "ranuras ABI nativas, mientras que `pod` reduce un argumento en forma de " "objeto a un puntero para el almacenamiento de C-layout con respaldo " @@ -12138,7 +11889,7 @@ msgid "`d3d12`" msgstr "`d3d12`" #: src/native-libraries/manifest-v1.md:350 src/testing/geisterhand.md:780 -#: src/cli/flags.md:121 +#: src/cli/flags.md:143 msgid "`windows`" msgstr "`windows`" @@ -13017,13 +12768,11 @@ msgstr "" #: src/threading/overview.md:3 msgid "" -"Perry gives you real OS threads with a one-line API. No worker setup, no " -"message ports, no structured clone overhead. Just `parallelMap`, " -"`parallelFilter`, and `spawn`." +"Use `parallelMap`, `parallelFilter` and `spawn` to run native work on OS " +"threads." msgstr "" -"Perry te ofrece threads reales del sistema operativo con una API de una sola " -"línea. Sin configuración de workers, sin puertos de mensajes, sin overhead " -"de structured clone. Solo `parallelMap`, `parallelFilter` y `spawn`." +"Use `parallelMap`, `parallelFilter` y `spawn` para ejecutar trabajo nativo " +"en hilos del SO." #: src/threading/overview.md:5 msgid "" @@ -13085,129 +12834,29 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:32 +#: src/threading/overview.md:32 src/threading/overview.md:300 msgid "" -"This is something **no JavaScript runtime can do**. V8, Bun, and Deno are " -"all locked to one thread per isolate. Perry compiles to native code — there " -"are no isolates, no GIL, no structural limitations. Your code runs on real " -"OS threads with the full power of every CPU core." +"Perry workers run native code on OS threads, each with its own arena and " +"collector. Ordinary captures and results are serialized between heaps; this " +"has a cost proportional to the transferred data. SharedArrayBuffer and " +"supported ownership transfers are explicit exceptions, not permission to " +"share arbitrary heap pointers." msgstr "" -"Esto es algo que **ningún runtime de JavaScript puede hacer**. V8, Bun y " -"Deno están todos bloqueados a un solo hilo por isolate. Perry compila a " -"código nativo — no hay isolates, no hay GIL, no hay limitaciones " -"estructurales. Tu código se ejecuta en hilos reales del sistema operativo " -"con toda la potencia de cada núcleo de CPU." +"Los workers Perry ejecutan código nativo en hilos del SO, cada uno con arena" +" y recolector propios. Capturas y resultados ordinarios se serializan entre " +"heaps, con un coste proporcional a los datos. SharedArrayBuffer y las " +"transferencias de propiedad admitidas son excepciones explícitas, no permiso" +" para compartir punteros arbitrarios." #: src/threading/overview.md:34 -msgid "Why This Matters" -msgstr "Por Qué Esto Importa" - -#: src/threading/overview.md:36 -msgid "" -"JavaScript's single-threaded model is its biggest performance bottleneck. " -"Here's how runtimes try to work around it:" -msgstr "" -"El modelo de un solo hilo de JavaScript es su mayor cuello de botella de " -"rendimiento. Así es como los runtimes intentan trabajar alrededor de ello:" - -#: src/threading/overview.md:38 -msgid "Runtime" -msgstr "Runtime" - -#: src/threading/overview.md:38 -msgid "\"Multi-threading\"" -msgstr "\"Multi-threading\"" - -#: src/threading/overview.md:38 -msgid "Reality" -msgstr "Realidad" - -#: src/threading/overview.md:40 -msgid "**Node.js**" -msgstr "**Node.js**" - -#: src/threading/overview.md:40 src/api/reference.md:3633 -msgid "`worker_threads`" -msgstr "`worker_threads`" - -#: src/threading/overview.md:40 -msgid "" -"Separate V8 isolates. Data copied via structured clone. ~2MB RAM per worker. " -"Complex API." -msgstr "" -"Isolates de V8 separados. Datos copiados mediante clonación estructurada. " -"~2MB de RAM por worker. API compleja." - -#: src/threading/overview.md:41 -msgid "**Deno**" -msgstr "**Deno**" - -#: src/threading/overview.md:41 src/threading/overview.md:42 -#: src/api/reference.md:532 src/api/reference.md:3640 -msgid "`Worker`" -msgstr "`Worker`" - -#: src/threading/overview.md:41 -msgid "Same as Node — isolated heaps, message passing only." -msgstr "Igual que Node — heaps aislados, solo paso de mensajes." - -#: src/threading/overview.md:42 -msgid "**Bun**" -msgstr "**Bun**" - -#: src/threading/overview.md:42 -msgid "Same architecture. Faster structured clone, still isolated." -msgstr "" -"Misma arquitectura. Clonación estructurada más rápida, sigue siendo aislado." - -#: src/threading/overview.md:43 -msgid "**Perry**" -msgstr "**Perry**" - -#: src/threading/overview.md:43 -msgid "`parallelMap` / `spawn`" -msgstr "`parallelMap` / `spawn`" - -#: src/threading/overview.md:43 -msgid "" -"Real OS threads. Lightweight (8MB stack). One-line API. Compile-time safety." -msgstr "" -"Hilos reales del sistema operativo. Ligeros (stack de 8MB). API de una " -"línea. Seguridad en tiempo de compilación." - -#: src/threading/overview.md:45 -msgid "" -"The fundamental problem: V8 uses a garbage-collected heap that **cannot be " -"shared** between threads. Every \"worker\" is an entirely separate " -"JavaScript engine instance with its own heap, its own GC, and its own copy " -"of your data." -msgstr "" -"El problema fundamental: V8 usa un heap con recolección de basura que **no " -"puede compartirse** entre hilos. Cada \"worker\" es una instancia " -"completamente separada del motor de JavaScript con su propio heap, su propio " -"GC y su propia copia de los datos." - -#: src/threading/overview.md:47 -msgid "" -"Perry doesn't have this limitation. It compiles TypeScript to native machine " -"code. Values are transferred between threads using zero-cost copies for " -"numbers and efficient serialization for objects — no separate engine " -"instances, no multi-megabyte overhead per thread." -msgstr "" -"Perry no tiene esta limitación. Compila TypeScript a código máquina nativo. " -"Los valores se transfieren entre hilos usando copias de costo cero para " -"números y serialización eficiente para objetos — sin instancias de motor " -"separadas, sin sobrecarga de varios megabytes por hilo." - -#: src/threading/overview.md:49 msgid "Three Primitives" msgstr "Tres Primitivas" -#: src/threading/overview.md:51 +#: src/threading/overview.md:36 msgid "`parallelMap` — Data-Parallel Processing" msgstr "`parallelMap` — Procesamiento Paralelo de Datos" -#: src/threading/overview.md:53 +#: src/threading/overview.md:38 msgid "" "Split an array across all CPU cores. Each element is processed " "independently. Results are collected in order." @@ -13215,7 +12864,7 @@ msgstr "" "Divide un array entre todos los núcleos de CPU. Cada elemento se procesa de " "forma independiente. Los resultados se recogen en orden." -#: src/threading/overview.md:55 +#: src/threading/overview.md:40 msgid "" "```typescript\n" "async function overviewParallelMap(): Promise {\n" @@ -13249,31 +12898,31 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:71 src/plugins/creating-plugins.md:37 +#: src/threading/overview.md:56 src/plugins/creating-plugins.md:37 msgid "Perry automatically:" msgstr "Perry automáticamente:" -#: src/threading/overview.md:72 +#: src/threading/overview.md:57 msgid "Detects the number of CPU cores" msgstr "Detecta el número de núcleos de CPU" -#: src/threading/overview.md:73 +#: src/threading/overview.md:58 msgid "Splits the array into chunks (one per core)" msgstr "Divide el array en fragmentos (uno por núcleo)" -#: src/threading/overview.md:74 +#: src/threading/overview.md:59 msgid "Spawns OS threads to process each chunk" msgstr "Lanza hilos del sistema operativo para procesar cada fragmento" -#: src/threading/overview.md:75 +#: src/threading/overview.md:60 msgid "Collects results in the original order" msgstr "Recoge los resultados en el orden original" -#: src/threading/overview.md:76 +#: src/threading/overview.md:61 msgid "Returns a new array" msgstr "Devuelve un nuevo array" -#: src/threading/overview.md:78 +#: src/threading/overview.md:63 msgid "" "For small arrays, Perry skips threading entirely and processes inline — no " "overhead for trivial cases." @@ -13281,18 +12930,18 @@ msgstr "" "Para arrays pequeños, Perry omite el threading por completo y procesa en " "línea — sin sobrecarga para casos triviales." -#: src/threading/overview.md:80 +#: src/threading/overview.md:65 msgid "`parallelFilter` — Data-Parallel Filtering" msgstr "`parallelFilter` — Filtrado Paralelo de Datos" -#: src/threading/overview.md:82 +#: src/threading/overview.md:67 msgid "" "Filter a large array across all CPU cores. Like `.filter()` but parallel:" msgstr "" "Filtra un array grande en todos los núcleos de CPU. Como `.filter()` pero en " "paralelo:" -#: src/threading/overview.md:84 +#: src/threading/overview.md:69 msgid "" "```typescript\n" "async function overviewParallelFilter(): Promise {\n" @@ -13332,7 +12981,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:102 +#: src/threading/overview.md:87 msgid "" "Same rules as `parallelMap`: closures cannot capture mutable variables " "(compile-time enforced), and values are deep-copied between threads." @@ -13341,11 +12990,11 @@ msgstr "" "mutables (aplicado en tiempo de compilación), y los valores se copian en " "profundidad entre hilos." -#: src/threading/overview.md:104 +#: src/threading/overview.md:89 msgid "`spawn` — Background Threads" msgstr "`spawn` — Hilos en Segundo Plano" -#: src/threading/overview.md:106 +#: src/threading/overview.md:91 msgid "" "Run any computation in the background and get a Promise back. The main " "thread continues immediately." @@ -13353,7 +13002,7 @@ msgstr "" "Ejecuta cualquier cómputo en segundo plano y obtiene una Promise de vuelta. " "El hilo principal continúa inmediatamente." -#: src/threading/overview.md:108 +#: src/threading/overview.md:93 msgid "" "```typescript\n" "async function overviewSpawnBg(): Promise {\n" @@ -13395,7 +13044,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:128 +#: src/threading/overview.md:113 msgid "" "`spawn` returns a standard Promise. You can `await` it, pass it to `Promise." "all`, or chain `.then()` — it works exactly like any other async operation." @@ -13404,15 +13053,15 @@ msgstr "" "`Promise.all`, o encadenar `.then()` — funciona exactamente como cualquier " "otra operación asíncrona." -#: src/threading/overview.md:130 +#: src/threading/overview.md:115 msgid "Practical Examples" msgstr "Ejemplos Prácticos" -#: src/threading/overview.md:132 +#: src/threading/overview.md:117 msgid "Parallel Image Processing" msgstr "Procesamiento de Imágenes en Paralelo" -#: src/threading/overview.md:134 +#: src/threading/overview.md:119 msgid "" "```typescript\n" "async function overviewImage(): Promise {\n" @@ -13456,11 +13105,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:154 +#: src/threading/overview.md:139 msgid "Parallel Cryptographic Hashing" msgstr "Hashing Criptográfico en Paralelo" -#: src/threading/overview.md:156 +#: src/threading/overview.md:141 msgid "" "```typescript\n" "async function overviewCrypto(): Promise {\n" @@ -13498,11 +13147,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:174 +#: src/threading/overview.md:159 msgid "Multiple Independent Computations" msgstr "Múltiples Cómputos Independientes" -#: src/threading/overview.md:176 +#: src/threading/overview.md:161 msgid "" "```typescript\n" "async function overviewMultiple(): Promise {\n" @@ -13564,11 +13213,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:205 +#: src/threading/overview.md:190 msgid "Keeping UI Responsive" msgstr "Mantener la UI Responsiva" -#: src/threading/overview.md:207 +#: src/threading/overview.md:192 msgid "" "```typescript\n" "const responsiveButton = Button(\"Start Analysis\", async () => {\n" @@ -13606,11 +13255,11 @@ msgstr "" "const responsiveText = Text(`Status: ${status.value}`)\n" "```" -#: src/threading/overview.md:225 +#: src/threading/overview.md:210 msgid "Captured Variables" msgstr "Variables capturadas" -#: src/threading/overview.md:227 +#: src/threading/overview.md:212 msgid "" "Closures can capture outer variables. Captured values are automatically deep-" "copied to each worker thread:" @@ -13618,7 +13267,7 @@ msgstr "" "Los closures pueden capturar variables externas. Los valores capturados se " "copian en profundidad automáticamente a cada hilo de trabajo:" -#: src/threading/overview.md:229 +#: src/threading/overview.md:214 msgid "" "```typescript\n" "async function overviewCaptured(): Promise {\n" @@ -13652,7 +13301,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:245 +#: src/threading/overview.md:230 msgid "" "Numbers and booleans are zero-cost copies (just 64-bit values). Strings, " "arrays, and objects are deep-copied automatically." @@ -13661,23 +13310,25 @@ msgstr "" "bits). Las cadenas, arrays y objetos se copian en profundidad " "automáticamente." -#: src/threading/overview.md:247 +#: src/threading/overview.md:232 msgid "Safety" msgstr "Seguridad" -#: src/threading/overview.md:249 +#: src/threading/overview.md:234 msgid "" -"Perry enforces thread safety **at compile time**. You don't need to think " -"about race conditions, mutexes, or data corruption." +"Compile-time admission checks restrict worker closures and module-global " +"access. Shared memory still requires synchronization, and native " +"integrations must respect thread ownership." msgstr "" -"Perry aplica la seguridad de hilos **en tiempo de compilación**. No es " -"necesario pensar en condiciones de carrera, mutexes ni corrupción de datos." +"Las comprobaciones de admisión al compilar restringen closures de workers y " +"accesos a globales de módulo. La memoria compartida requiere sincronización " +"y las integraciones nativas deben respetar la propiedad de los hilos." -#: src/threading/overview.md:251 +#: src/threading/overview.md:236 msgid "No Shared Mutable State" msgstr "Sin estado mutable compartido" -#: src/threading/overview.md:253 +#: src/threading/overview.md:238 msgid "" "Closures passed to `parallelMap` and `spawn` **cannot capture mutable " "variables**. The compiler rejects this:" @@ -13685,7 +13336,7 @@ msgstr "" "Los closures pasados a `parallelMap` y `spawn` **no pueden capturar " "variables mutables**. El compilador rechaza esto:" -#: src/threading/overview.md:255 +#: src/threading/overview.md:240 msgid "" "```text\n" "// Reject example — Perry rejects this at compile time:\n" @@ -13713,7 +13364,7 @@ msgstr "" "});\n" "```" -#: src/threading/overview.md:268 +#: src/threading/overview.md:253 msgid "" "This eliminates data races by design. If you need to aggregate results, use " "the return values:" @@ -13721,7 +13372,7 @@ msgstr "" "Esto elimina las carreras de datos por diseño. Si necesitas agregar " "resultados, utiliza los valores de retorno:" -#: src/threading/overview.md:270 +#: src/threading/overview.md:255 msgid "" "```typescript\n" "async function overviewReduceInstead(): Promise {\n" @@ -13747,7 +13398,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:282 +#: src/threading/overview.md:267 msgid "" "The one explicit shared-state escape hatch is `SharedArrayBuffer`: a SAB " "captured into a `spawn` / `parallelMap` closure aliases the same physical " @@ -13766,11 +13417,11 @@ msgstr "" "matriz mecanografiada sobre él por agente en lugar de capturar la vista " "directamente." -#: src/threading/overview.md:289 +#: src/threading/overview.md:274 msgid "Workers Are Synchronous" msgstr "Los trabajadores son sincronizados" -#: src/threading/overview.md:291 +#: src/threading/overview.md:276 msgid "" "The closure passed to `parallelMap`, `parallelFilter`, or `spawn` cannot be " "`async`, contain `await` (directly or in a nested closure), or call another " @@ -13790,11 +13441,11 @@ msgstr "" "resultado de `spawn` allí — el patrón estándar mostrado arriba no se ve " "afectado." -#: src/threading/overview.md:299 +#: src/threading/overview.md:284 msgid "Module-Scope Objects Stay Home" msgstr "Objetos del módulo-ámbito de visión" -#: src/threading/overview.md:301 +#: src/threading/overview.md:286 msgid "" "Worker closures may read module-scope **primitives** (numbers, strings, " "booleans), but not module-scope bindings that hold heap objects — object and " @@ -13822,23 +13473,11 @@ msgstr "" "desde un trabajador). Los globales de módulos `SharedArrayBuffer` están " "exentos: compartir hilos cruzados es su propósito." -#: src/threading/overview.md:313 +#: src/threading/overview.md:298 msgid "Independent Thread Arenas" msgstr "Arenas de memoria independientes por hilo" -#: src/threading/overview.md:315 -msgid "" -"Each worker thread has its own memory arena. Objects created on one thread " -"can never be accessed from another thread. Values cross thread boundaries " -"only through deep-copy serialization, which Perry handles automatically and " -"invisibly." -msgstr "" -"Cada hilo de trabajo tiene su propia arena de memoria. Los objetos creados " -"en un hilo nunca pueden ser accedidos desde otro hilo. Los valores cruzan " -"los límites de los hilos únicamente mediante serialización por copia " -"profunda, que Perry gestiona de forma automática e invisible." - -#: src/threading/overview.md:317 +#: src/threading/overview.md:302 msgid "" "File-system descriptors are also thread-affine. Numeric fds from `fs." "openSync` are just copied numbers in another thread, where the fd registry " @@ -13855,43 +13494,42 @@ msgstr "" "rutas de archivo a `spawn`/`parallelMap` y reabre los archivos dentro del " "worker cuando necesite realizar E/S de archivos." -#: src/threading/overview.md:325 +#: src/threading/overview.md:310 msgid "Perry's threading model is built on three pillars:" msgstr "El modelo de threading de Perry se basa en tres pilares:" -#: src/threading/overview.md:327 -msgid "**1. Native Code, Not Interpreted**" -msgstr "**1. Código nativo, no interpretado**" +#: src/threading/overview.md:312 +msgid "**1. Native Code**" +msgstr "**1. Código nativo**" -#: src/threading/overview.md:329 +#: src/threading/overview.md:314 msgid "" -"Perry compiles TypeScript to native machine code via LLVM. There's no " -"interpreter, no VM, no isolate. A function pointer is just a function " -"pointer — it's valid on any thread." +"Perry compiles TypeScript to native machine code via LLVM. Native worker " +"execution still requires the ownership and transfer rules described above." msgstr "" -"Perry compila TypeScript a código máquina nativo a través de LLVM. No hay " -"intérprete, ni VM, ni isolate. Un puntero a función es simplemente un " -"puntero a función — es válido en cualquier hilo." +"Perry compila TypeScript a código máquina nativo mediante LLVM. Los workers " +"nativos también deben respetar las reglas de propiedad y transferencia " +"anteriores." -#: src/threading/overview.md:331 +#: src/threading/overview.md:316 msgid "**2. Thread-Local Memory**" msgstr "**2. Memoria local por hilo**" -#: src/threading/overview.md:333 +#: src/threading/overview.md:318 msgid "" -"Each thread gets its own memory arena (bump allocator) and garbage " -"collector. No synchronization overhead during computation. When a thread " -"finishes, its arena is freed automatically." +"Each worker has its own arena and collector. This avoids a shared JavaScript" +" heap; explicit shared memory and runtime coordination still require " +"synchronization." msgstr "" -"Cada hilo obtiene su propia arena de memoria (allocator por bump) y " -"recolector de basura. Sin sobrecarga de sincronización durante el cómputo. " -"Cuando un hilo termina, su arena se libera automáticamente." +"Cada worker tiene arena y recolector propios. Esto evita un heap JavaScript " +"compartido; la memoria compartida explícita y la coordinación del runtime " +"siguen necesitando sincronización." -#: src/threading/overview.md:335 +#: src/threading/overview.md:320 msgid "**3. Serialized Transfer**" msgstr "**3. Transferencia serializada**" -#: src/threading/overview.md:337 +#: src/threading/overview.md:322 msgid "" "Values crossing thread boundaries are serialized to a thread-safe " "intermediate format and deserialized on the target thread. The cost depends " @@ -13901,47 +13539,47 @@ msgstr "" "intermedio seguro para hilos y se deserializan en el hilo de destino. El " "costo depende del tipo de valor:" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Value Type" msgstr "Tipo de valor" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Transfer Cost" msgstr "Costo de transferencia" -#: src/threading/overview.md:341 +#: src/threading/overview.md:326 msgid "Numbers, booleans, null, undefined" msgstr "Números, booleanos, null, undefined" -#: src/threading/overview.md:341 src/threading/parallel-map.md:55 +#: src/threading/overview.md:326 src/threading/parallel-map.md:55 msgid "Zero-cost (64-bit copy)" msgstr "Costo cero (copia de 64 bits)" -#: src/threading/overview.md:342 +#: src/threading/overview.md:327 msgid "O(n) byte copy" msgstr "Copia de bytes O(n)" -#: src/threading/overview.md:343 +#: src/threading/overview.md:328 msgid "O(n) deep copy of elements" msgstr "Copia profunda O(n) de elementos" -#: src/threading/overview.md:344 +#: src/threading/overview.md:329 msgid "O(n) deep copy of fields" msgstr "Copia profunda O(n) de campos" -#: src/threading/overview.md:345 +#: src/threading/overview.md:330 msgid "Pointer + captured values" msgstr "Puntero + valores capturados" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "`fs` numeric fds / `FileHandle`" msgstr "`fs` fds numéricos / `FileHandle`" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "Thread-affine; reopen by path" msgstr "Afinado al hilo; reabrir por ruta" -#: src/threading/overview.md:348 +#: src/threading/overview.md:333 msgid "" "For numeric workloads — the most common parallelizable tasks — the threading " "overhead is negligible." @@ -13949,21 +13587,89 @@ msgstr "" "Para cargas de trabajo numéricas — las tareas paralelizables más comunes — " "la sobrecarga del threading es insignificante." -#: src/threading/overview.md:352 +#: src/threading/overview.md:335 +#, fuzzy +msgid "Node.js Worker Constructor Compatibility" +msgstr "Node.js Compatibility" + +#: src/threading/overview.md:337 +msgid "" +"For `node:worker_threads`, a truthy `Worker` `name` must be a string; " +"invalid names throw `TypeError` with `ERR_INVALID_ARG_TYPE` before " +"transferring data. Falsy names use the default. `transferList` is validated " +"even when `workerData` is undefined, and listed ArrayBuffers are detached " +"after a successful clone whether or not the data references them." +msgstr "" + +#: src/threading/overview.md:343 +msgid "" +"`setEnvironmentData` retains the caller's value until it is replaced or " +"deleted by setting `undefined`. A new worker receives a creation-time clone " +"of that map alongside `workerData`; shared references and cycles are " +"preserved within this snapshot, while later parent changes remain local. " +"Captured namespace methods and CommonJS imports use the same storage as " +"named imports. If cloning either data source fails, construction throws " +"before transferring any buffers. Clone and transfer marks are private, " +"thread-local identity metadata, not object properties. Primitive marks are " +"ignored. Clone marks reject ordinary objects and Errors in both global " +"`structuredClone` and worker messages; builtin clone kinds such as arrays, " +"Maps, Sets and buffers retain their own serialization behavior. Marked " +"transfer buffers are rejected without detachment. The marker table does not " +"keep its targets alive. Inside a Node-compatible Worker, process-wide " +"mutations such as `abort`, `chdir`, credential setters, IPC send/disconnect " +"and setting `umask` are rejected before OS effects. IPC members remain " +"absent unless enabled by the Worker startup environment. Disabled process " +"callables expose `disabled === true`; reading `umask` remains allowed. " +"Worker title writes remain local to the existing title value, and mutable " +"CommonJS/default reads observe the updated title. Named ESM title imports " +"remain a known parity limitation. Worker eval source must be a primitive " +"string. A URL with truthy `eval` throws before reading workerData or " +"detaching buffers; ordinary file URLs use the same registered entry table as " +"dynamically selected files. Literal string eval remains compiled ahead of " +"time; dynamically generated source is still unsupported. Worker `argv` " +"values are converted in constructor order and installed in the child's " +"existing process-argv authority, after execPath and the Worker entry name. " +"MessagePort and BroadcastChannel property reads return stored values without " +"invoking handlers; method calls retain their receiver through ordinary " +"property lookup. Message methods accept transfer iterables directly or " +"through `options.transfer`. The complete sequence is consumed before " +"transfer entries are validated; iterator errors retain their identity, and " +"rejection leaves ArrayBuffers attached. BroadcastChannel ignores its extra " +"argument. Worker constructor transfer admission remains separate; " +"MessagePort endpoint movement and direct-message routing/timeout parity are " +"not provided by this normalization. Main-thread `postMessageToThread` " +"rejects missing or stopped destinations before cloning the payload or " +"detaching transfer buffers. Worker-origin messages retain Node's forwarding " +"order: cloning precedes destination rejection. Admission does not guarantee " +"rollback if a destination exits concurrently after it was admitted. Core-" +"module messaging constructor exports retain the original thread-realm " +"MessageChannel, MessagePort and BroadcastChannel objects even after global " +"reassignment. Explicit CommonJS export overrides and named-export " +"synchronization remain separate; vm/eval global population does not replace " +"these thread-core values. MessagePort core methods (`postMessage`, `start`, " +"`close`, `ref`, `unref`, `hasRef`) operate on their actual receiver, " +"including calls borrowed from another port or its prototype. A private ID " +"supplies the brand without observable getters or proxy traps; closing a port " +"preserves its brand without retaining a closed runtime port in the delivery " +"table. Listener methods and realm movement remain separate parity work. " +"Other worker API parity gaps remain." +msgstr "" + +#: src/threading/overview.md:394 msgid "" "[parallelMap Reference](parallel-map.md) — detailed API and performance tips" msgstr "" "[Referencia de parallelMap](parallel-map.md) — API detallada y consejos de " "rendimiento" -#: src/threading/overview.md:353 +#: src/threading/overview.md:395 msgid "" "[parallelFilter Reference](parallel-filter.md) — parallel array filtering" msgstr "" "[Referencia de parallelFilter](parallel-filter.md) — filtrado paralelo de " "arrays" -#: src/threading/overview.md:354 +#: src/threading/overview.md:396 msgid "" "[spawn Reference](spawn.md) — background threads and Promise integration" msgstr "" @@ -14716,8 +14422,8 @@ msgid "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" msgstr "" @@ -14741,8 +14447,8 @@ msgstr "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" @@ -15256,8 +14962,8 @@ msgid "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" msgstr "" @@ -15274,8 +14980,8 @@ msgstr "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" @@ -15514,7 +15220,7 @@ msgstr "" msgid "Property" msgstr "Propiedad" -#: src/ui/overview.md:79 src/api/reference.md:2799 +#: src/ui/overview.md:79 src/api/reference.md:2804 msgid "`title`" msgstr "`title`" @@ -16042,7 +15748,7 @@ msgstr "NSMenu" #: src/cli/perry-toml.md:389 src/cli/perry-toml.md:418 #: src/cli/app-updates.md:56 src/cli/app-updates.md:57 #: src/cli/app-updates.md:58 src/cli/app-updates.md:60 -#: src/internals/memory-model.md:325 src/internals/memory-model.md:326 +#: src/internals/memory-model.md:309 src/internals/memory-model.md:310 #: src/contributing/building.md:55 msgid "—" msgstr "—" @@ -16642,8 +16348,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -16668,8 +16374,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -18085,8 +17791,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18121,8 +17827,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18279,7 +17985,7 @@ msgstr "Nombre" #: src/container/overview.md:112 src/container/containers.md:46 #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 -#: src/internals/memory-model.md:168 src/internals/memory-model.md:185 +#: src/internals/memory-model.md:153 src/internals/memory-model.md:170 #: src/internals/weakmap-owned-index-validation.md:78 #: src/internals/weakmap-owned-index-validation.md:100 msgid "Effect" @@ -18444,7 +18150,7 @@ msgstr "Todos los hijos obtienen el mismo tamaño" #: src/ui/styling-matrix.md:97 src/tui/widgets.md:60 #: src/platforms/windows-7.md:26 src/container/overview.md:72 #: src/container/determinism.md:60 src/testing/geisterhand.md:88 -#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:190 +#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:197 #: src/cli/perry-toml.md:430 src/internals/gc-rooting-invariant.md:358 #: src/internals/gc-rooting-invariant.md:359 #: src/internals/rfc-rooting-by-construction.md:355 @@ -18531,8 +18237,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: web, wasm\n" "\n" -"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/" -"ui\";\n" +"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/ui" +"\";\n" "\n" "const column = VStack(12, [Text(\"Responsive content\")]);\n" "setPadding(column, 32);\n" @@ -23515,17 +23221,15 @@ msgstr "" #: src/ui/multi-window.md:118 msgid "" -"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `\"menu" +"\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground\"`, `" +"\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground\"`, `" +"\"underWindowBackground\"`, `\"underPageBackground\"`" msgstr "" -"**Materiales de macOS:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**Materiales de macOS:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `" +"\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground" +"\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground" +"\"`, `\"underWindowBackground\"`, `\"underPageBackground\"`" #: src/ui/multi-window.md:125 msgid "`NSVisualEffectView` with the specified material" @@ -24521,13 +24225,13 @@ msgid "" "\n" "const auth = WebView({\n" " url: \"https://accounts.google.com/o/oauth2/auth?client_id=..." -"&redirect_uri=https://myapp.com/oauth/" -"callback&response_type=code&scope=email\",\n" +"&redirect_uri=https://myapp.com/oauth/callback&response_type=code&scope=email" +"\",\n" " // Hard host-level allowlist — blocked at the native delegate\n" " // without round-tripping into TS. Exact match or subdomain match\n" " // (so \"google.com\" allows \"accounts.google.com\").\n" -" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp." -"com\"],\n" +" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp.com" +"\"],\n" " onShouldNavigate: (url) => {\n" " if (url.startsWith(\"https://myapp.com/oauth/callback?\")) {\n" " const code = new URL(url).searchParams.get(\"code\") ?? \"\"\n" @@ -24550,13 +24254,13 @@ msgstr "" "\n" "const auth = WebView({\n" " url: \"https://accounts.google.com/o/oauth2/auth?client_id=..." -"&redirect_uri=https://myapp.com/oauth/" -"callback&response_type=code&scope=email\",\n" +"&redirect_uri=https://myapp.com/oauth/callback&response_type=code&scope=email" +"\",\n" " // Hard host-level allowlist — blocked at the native delegate\n" " // without round-tripping into TS. Exact match or subdomain match\n" " // (so \"google.com\" allows \"accounts.google.com\").\n" -" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp." -"com\"],\n" +" allowedDomains: [\"accounts.google.com\", \"google.com\", \"myapp.com" +"\"],\n" " onShouldNavigate: (url) => {\n" " if (url.startsWith(\"https://myapp.com/oauth/callback?\")) {\n" " const code = new URL(url).searchParams.get(\"code\") ?? \"\"\n" @@ -24884,7 +24588,7 @@ msgstr "" msgid "Options" msgstr "Opciones" -#: src/ui/webview.md:186 src/api/reference.md:3396 src/cli/perry-toml.md:443 +#: src/ui/webview.md:186 src/api/reference.md:3416 src/cli/perry-toml.md:443 #: src/cli/app-updates.md:57 src/cli/app-updates.md:80 msgid "`url`" msgstr "`url`" @@ -24939,7 +24643,7 @@ msgstr "`ephemeral`" #: src/platforms/watchos-app-store.md:103 src/testing/http2-conformance.md:286 #: src/testing/http2-conformance.md:287 src/testing/http2-conformance.md:288 #: src/testing/http2-conformance.md:289 src/cli/app-updates.md:67 -#: src/internals/memory-model.md:20 +#: src/internals/memory-model.md:22 msgid "`true`" msgstr "`true`" @@ -25130,11 +24834,10 @@ msgid "" "avoids)." msgstr "" "En el target web, el iframe embebido puede usar `window.parent.postMessage` " -"para enviar mensajes, y el host puede usar `window." -"addEventListener(\"message\", ...)` para recibirlos. Este es un patrón " -"**exclusivo del navegador** — los targets nativos no exponen `postMessage` " -"(ese es el camino de Tauri / Electron que el WebView de Perry evita " -"deliberadamente)." +"para enviar mensajes, y el host puede usar `window.addEventListener(\"message" +"\", ...)` para recibirlos. Este es un patrón **exclusivo del navegador** — " +"los targets nativos no exponen `postMessage` (ese es el camino de Tauri / " +"Electron que el WebView de Perry evita deliberadamente)." #: src/ui/webview.md:215 msgid "The portable contract that works on every target:" @@ -25294,8 +24997,8 @@ msgstr "" #: src/tui/overview.md:18 msgid "" -"The smallest interactive `perry/tui` program — a counter that increments on " -"`+`, decrements on `-`, and quits on `q`:" +"The smallest interactive `perry/tui` program — a counter that increments on `" +"+`, decrements on `-`, and quits on `q`:" msgstr "" "El programa `perry/tui` interactivo más pequeño — un contador que incrementa " "con `+`, decrementa con `-` y sale con `q`:" @@ -26177,8 +25880,8 @@ msgid "" msgstr "" "Las dependencias se comparan por identidad de bits usando un hash FNV-1a de " "los valores NaN-boxed de las deps. Un array vacío `[]` produce un hash " -"estable distinto de cero, dando el comportamiento de React \"ejecutar una " -"vez\"; pasar ningún array ejecuta el efecto en cada renderizado." +"estable distinto de cero, dando el comportamiento de React \"ejecutar una vez" +"\"; pasar ningún array ejecuta el efecto en cada renderizado." #: src/tui/hooks.md:66 msgid "" @@ -26388,8 +26091,8 @@ msgid "" "them to your `useInput` handler." msgstr "" "Tab y Shift-Tab ciclan el foco automáticamente — sin código repetitivo. El " -"drenado de entrada del bucle de ejecución gestiona las secuencias de bytes " -"`\\x09` / `\\x1b[Z` antes de reenviarlas a tu handler de `useInput`." +"drenado de entrada del bucle de ejecución gestiona las secuencias de bytes `" +"\\x09` / `\\x1b[Z` antes de reenviarlas a tu handler de `useInput`." #: src/tui/hooks.md:137 msgid "For imperative focus control, pair with `useFocusManager()`:" @@ -26451,8 +26154,8 @@ msgstr "" #: src/tui/hooks.md:161 msgid "" "The `s` argument is the raw byte chunk as a string. ANSI escape sequences " -"like arrow keys arrive as a single chunk (`\\x1b[A`, `\\x1b[B`, `\\x1b[C`, " -"`\\x1b[D`); printable characters as one byte; control codes (Ctrl+C, Tab, " +"like arrow keys arrive as a single chunk (`\\x1b[A`, `\\x1b[B`, `\\x1b[C`, `" +"\\x1b[D`); printable characters as one byte; control codes (Ctrl+C, Tab, " "Enter, Backspace) as their literal byte." msgstr "" "El argumento `s` es el fragmento de bytes en bruto como string. Las " @@ -26492,7 +26195,7 @@ msgstr "Equivalencia con ink" msgid "ink" msgstr "ink" -#: src/tui/hooks.md:169 src/api/reference.md:2452 +#: src/tui/hooks.md:169 src/api/reference.md:2457 msgid "`perry/tui`" msgstr "`perry/tui`" @@ -29000,7 +28703,7 @@ msgid "Build environment variables" msgstr "Construir variables de entorno" #: src/platforms/watchos.md:82 src/container/overview.md:112 -#: src/cli/flags.md:399 src/cli/flags.md:435 src/cli/perry-toml.md:518 +#: src/cli/flags.md:421 src/cli/flags.md:457 src/cli/perry-toml.md:518 #: src/cli/perry-toml.md:530 src/cli/perry-toml.md:540 msgid "Variable" msgstr "Variable" @@ -29761,8 +29464,8 @@ msgstr "" "App Store Connect no tiene una plataforma independiente \"watchOS\" — mira " "los barcos de software **dentro de un registro de aplicaciones iOS**. Cargar " "un reloj `.app` falla en Transporter con `Unknown platform alias: watchOS`. " -"La aplicación de reloj debe estar anidada en un contenedor mínimo iOS " -"\"tubo\":" +"La aplicación de reloj debe estar anidada en un contenedor mínimo iOS \"tubo" +"\":" #: src/platforms/watchos-app-store.md:80 msgid "" @@ -31276,13 +30979,13 @@ msgstr "" #: src/platforms/windows.md:19 msgid "" -"`perry setup windows` downloads ~700 MB (unpacks to ~1.5 GB) at " -"`%LOCALAPPDATA%\\perry\\windows-sdk` after prompting you to accept the " +"`perry setup windows` downloads ~700 MB (unpacks to ~1.5 GB) at `" +"%LOCALAPPDATA%\\perry\\windows-sdk` after prompting you to accept the " "Microsoft redistributable license. Pass `--accept-license` to skip the " "prompt in CI. Partial downloads resume safely on re-run." msgstr "" -"`perry setup windows` descarga ~700 MB (desempaqueta a ~1,5 GB) en " -"`%LOCALAPPDATA%\\perry\\windows-sdk` tras pedirte que aceptes la licencia " +"`perry setup windows` descarga ~700 MB (desempaqueta a ~1,5 GB) en `" +"%LOCALAPPDATA%\\perry\\windows-sdk` tras pedirte que aceptes la licencia " "redistribuible de Microsoft. Usa `--accept-license` para omitir el aviso en " "CI. Las descargas parciales se reanudan de forma segura al volver a ejecutar." @@ -31492,15 +31195,14 @@ msgstr "`LNK1181: cannot open input file 'user32.lib'`" #: src/platforms/windows.md:115 msgid "" "The linker couldn't find the Windows SDK libraries. Perry probes the " -"registry (`KitsRoot10`) and the standard `Windows " -"Kits\\10\\Lib\\\\um\\x64` install paths; when the probe fails it prints " -"a warning listing the paths it tried. Fixes, in order of preference:" +"registry (`KitsRoot10`) and the standard `Windows Kits\\10\\Lib\\\\um" +"\\x64` install paths; when the probe fails it prints a warning listing the " +"paths it tried. Fixes, in order of preference:" msgstr "" "El linker no pudo encontrar las bibliotecas Windows SDK. Perry sondea el " -"registro (`KitsRoot10`) y las rutas de instalación estándar `Windows " -"Kits\\10\\Lib\\\\um\\x64`; cuando la sonda falla, imprime una " -"advertencia en la que se enumeran las rutas que intentó. Fija, por orden de " -"preferencia:" +"registro (`KitsRoot10`) y las rutas de instalación estándar `Windows Kits" +"\\10\\Lib\\\\um\\x64`; cuando la sonda falla, imprime una advertencia " +"en la que se enumeran las rutas que intentó. Fija, por orden de preferencia:" #: src/platforms/windows.md:117 msgid "" @@ -32024,10 +31726,10 @@ msgid "" "`GetDeviceCaps(LOGPIXELSY)` (Win2000+, dead reliable)." msgstr "" "Retrocede al siguiente nivel en caso de fallo. " -"`set_process_dpi_awareness_compat` termina con `SetProcessDPIAware()` " -"(Vista+, importada de forma directa porque cada versión de Windows " -"compatible la tiene). `get_system_dpi_compat` termina con " -"`GetDeviceCaps(LOGPIXELSY)` (Win2000+, completamente fiable)." +"`set_process_dpi_awareness_compat` termina con `SetProcessDPIAware()` (Vista" +"+, importada de forma directa porque cada versión de Windows compatible la " +"tiene). `get_system_dpi_compat` termina con `GetDeviceCaps(LOGPIXELSY)` " +"(Win2000+, completamente fiable)." #: src/platforms/windows-7.md:88 msgid "" @@ -33414,7 +33116,7 @@ msgstr "" msgid "[File System](fs.md)" msgstr "[Sistema de Archivos](fs.md)" -#: src/stdlib/overview.md:131 src/stdlib/fs.md:100 +#: src/stdlib/overview.md:131 src/stdlib/fs.md:107 msgid "[HTTP & Networking](http.md)" msgstr "[HTTP y Redes](http.md)" @@ -33642,7 +33344,17 @@ msgstr "" "Pasa rutas de archivo entre límites de hilo y reabre los archivos dentro del " "worker." -#: src/stdlib/fs.md:101 src/stdlib/http.md:411 src/stdlib/database.md:115 +#: src/stdlib/fs.md:98 +msgid "" +"`perry/thread` deep-copies function-local captures. In descriptor-isolation " +"tests, create the descriptor and worker closure inside an ordinary function " +"so the test reaches that capture path instead of the compiler's module-" +"global heap guard. The FS parity fixtures use this structure without " +"changing their `EBADF` checks or expected output: this is fixture-structure " +"alignment, not a runtime or compiler descriptor fix." +msgstr "" + +#: src/stdlib/fs.md:108 src/stdlib/http.md:411 src/stdlib/database.md:115 #: src/stdlib/crypto.md:90 src/stdlib/utilities.md:99 src/stdlib/other.md:335 msgid "[Overview](overview.md) — All stdlib modules" msgstr "[Descripción General](overview.md) — Todos los módulos de la stdlib" @@ -34238,8 +33950,8 @@ msgid "" " const data = await response.json()\n" "\n" " // POST request\n" -" const result = await fetch(\"https://jsonplaceholder.typicode.com/" -"posts\", {\n" +" const result = await fetch(\"https://jsonplaceholder.typicode.com/posts" +"\", {\n" " method: \"POST\",\n" " headers: { \"Content-Type\": \"application/json\" },\n" " body: JSON.stringify({ title: \"hello\", body: \"world\", userId: " @@ -34258,8 +33970,8 @@ msgstr "" " const data = await response.json()\n" "\n" " // POST request\n" -" const result = await fetch(\"https://jsonplaceholder.typicode.com/" -"posts\", {\n" +" const result = await fetch(\"https://jsonplaceholder.typicode.com/posts" +"\", {\n" " method: \"POST\",\n" " headers: { \"Content-Type\": \"application/json\" },\n" " body: JSON.stringify({ title: \"hello\", body: \"world\", userId: " @@ -34370,11 +34082,11 @@ msgstr "" #: src/stdlib/http.md:333 msgid "" "For manual routing, create `new WebSocketServer({ noServer: true })` and " -"call `wss.handleUpgrade(req, socket, head, (ws, req) => wss." -"emit(\"connection\", ws, req))` from the HTTP server's `upgrade` listener. " -"The callback runs once; it controls whether the connection event is emitted. " -"The native HTTP transport performs the handshake before dispatching " -"`upgrade`, as described above." +"call `wss.handleUpgrade(req, socket, head, (ws, req) => wss.emit(\"connection" +"\", ws, req))` from the HTTP server's `upgrade` listener. The callback runs " +"once; it controls whether the connection event is emitted. The native HTTP " +"transport performs the handshake before dispatching `upgrade`, as described " +"above." msgstr "" #: src/stdlib/http.md:339 @@ -34774,8 +34486,8 @@ msgid "" " const db = client.db(\"mydb\")\n" " const users = db.collection(\"users\")\n" "\n" -" await users.insertOne({ name: \"Perry\", email: \"perry@example." -"com\" })\n" +" await users.insertOne({ name: \"Perry\", email: \"perry@example.com" +"\" })\n" " const user = await users.findOne({ name: \"Perry\" })\n" " console.log(user)\n" "\n" @@ -34793,8 +34505,8 @@ msgstr "" " const db = client.db(\"mydb\")\n" " const users = db.collection(\"users\")\n" "\n" -" await users.insertOne({ name: \"Perry\", email: \"perry@example." -"com\" })\n" +" await users.insertOne({ name: \"Perry\", email: \"perry@example.com" +"\" })\n" " const user = await users.findOne({ name: \"Perry\" })\n" " console.log(user)\n" "\n" @@ -34949,8 +34661,8 @@ msgid "" "\n" "function cryptoExample(): void {\n" " // Hash\n" -" const hash = crypto.createHash(\"sha256\").update(\"data\")." -"digest(\"hex\")\n" +" const hash = crypto.createHash(\"sha256\").update(\"data\").digest(\"hex" +"\")\n" "\n" " // HMAC\n" " const hmac = crypto.createHmac(\"sha256\", \"secret\").update(\"data\")." @@ -34959,8 +34671,8 @@ msgid "" " // Random bytes\n" " const bytes = crypto.randomBytes(32)\n" "\n" -" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} " -"bytes_len=${bytes.length}`)\n" +" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} bytes_len=" +"${bytes.length}`)\n" "}\n" "```" msgstr "" @@ -34969,8 +34681,8 @@ msgstr "" "\n" "function cryptoExample(): void {\n" " // Hash\n" -" const hash = crypto.createHash(\"sha256\").update(\"data\")." -"digest(\"hex\")\n" +" const hash = crypto.createHash(\"sha256\").update(\"data\").digest(\"hex" +"\")\n" "\n" " // HMAC\n" " const hmac = crypto.createHmac(\"sha256\", \"secret\").update(\"data\")." @@ -34979,8 +34691,8 @@ msgstr "" " // Random bytes\n" " const bytes = crypto.randomBytes(32)\n" "\n" -" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} " -"bytes_len=${bytes.length}`)\n" +" console.log(`hash_len=${hash.length} hmac_len=${hmac.length} bytes_len=" +"${bytes.length}`)\n" "}\n" "```" @@ -35241,9 +34953,9 @@ msgstr "" #: src/stdlib/utilities.md:13 #, fuzzy msgid "" -"Use named imports: the default-import receiver form (`import _ from " -"\"lodash\"; _.chunk(...)`) is not routed to these native signatures because " -"it would pass the module object as an extra receiver argument." +"Use named imports: the default-import receiver form (`import _ from \"lodash" +"\"; _.chunk(...)`) is not routed to these native signatures because it would " +"pass the module object as an extra receiver argument." msgstr "" "Use importaciones con nombre: el formulario receptor de importación por " "defecto (`import _ from \"lodash\"; _.chunk(...)`) no se enruta a estas " @@ -36227,7 +35939,7 @@ msgstr "" #: src/api/reference.md:5 #, fuzzy -msgid "Total: 2794 entries across 112 modules." +msgid "Total: 2802 entries across 113 modules." msgstr "Total: 3019 entradas en 138 módulos." #: src/api/reference.md:9 @@ -36635,4085 +36347,4102 @@ msgid "[`tls`](#tls)" msgstr "[`tls`](#tls)" #: src/api/reference.md:109 +#, fuzzy +msgid "[`trace_events`](#trace_events)" +msgstr "[`events`](#events)" + +#: src/api/reference.md:110 msgid "[`tty`](#tty)" msgstr "[`tty`](#tty)" -#: src/api/reference.md:110 +#: src/api/reference.md:111 msgid "[`typescript`](#typescript)" msgstr "[`typescript`](#typescript)" -#: src/api/reference.md:111 +#: src/api/reference.md:112 msgid "[`undici`](#undici)" msgstr "[`undici`](#undici)" -#: src/api/reference.md:112 +#: src/api/reference.md:113 msgid "[`url`](#url)" msgstr "[`url`](#url)" -#: src/api/reference.md:113 +#: src/api/reference.md:114 msgid "[`util`](#util)" msgstr "[`util`](#util)" -#: src/api/reference.md:114 +#: src/api/reference.md:115 msgid "[`util/types`](#utiltypes)" msgstr "[`util/types`](#utiltypes)" -#: src/api/reference.md:115 +#: src/api/reference.md:116 msgid "[`v8`](#v8)" msgstr "[`v8`](#v8)" -#: src/api/reference.md:116 +#: src/api/reference.md:117 msgid "[`vm`](#vm)" msgstr "[`vm`](#vm)" -#: src/api/reference.md:117 +#: src/api/reference.md:118 msgid "[`wasi`](#wasi)" msgstr "[`wasi`](#wasi)" -#: src/api/reference.md:118 +#: src/api/reference.md:119 msgid "[`worker_threads`](#worker_threads)" msgstr "[`worker_threads`](#worker_threads)" -#: src/api/reference.md:119 +#: src/api/reference.md:120 msgid "[`ws`](#ws)" msgstr "[`ws`](#ws)" -#: src/api/reference.md:120 +#: src/api/reference.md:121 msgid "[`zlib`](#zlib)" msgstr "[`zlib`](#zlib)" -#: src/api/reference.md:124 +#: src/api/reference.md:125 msgid "`@lydell/node-pty`" msgstr "`@lydell/node-pty`" -#: src/api/reference.md:126 src/api/reference.md:136 src/api/reference.md:146 -#: src/api/reference.md:152 src/api/reference.md:158 src/api/reference.md:164 -#: src/api/reference.md:170 src/api/reference.md:176 src/api/reference.md:182 -#: src/api/reference.md:188 src/api/reference.md:194 src/api/reference.md:204 -#: src/api/reference.md:216 src/api/reference.md:228 src/api/reference.md:262 -#: src/api/reference.md:296 src/api/reference.md:324 src/api/reference.md:331 -#: src/api/reference.md:355 src/api/reference.md:377 src/api/reference.md:426 -#: src/api/reference.md:436 src/api/reference.md:456 src/api/reference.md:467 -#: src/api/reference.md:491 src/api/reference.md:513 src/api/reference.md:534 -#: src/api/reference.md:559 src/api/reference.md:840 src/api/reference.md:917 -#: src/api/reference.md:966 src/api/reference.md:985 src/api/reference.md:1094 -#: src/api/reference.md:1172 src/api/reference.md:1196 -#: src/api/reference.md:1212 src/api/reference.md:1264 -#: src/api/reference.md:1270 src/api/reference.md:1295 -#: src/api/reference.md:1400 src/api/reference.md:1459 -#: src/api/reference.md:1658 src/api/reference.md:1682 -#: src/api/reference.md:1734 src/api/reference.md:1765 -#: src/api/reference.md:1781 src/api/reference.md:1820 -#: src/api/reference.md:1867 src/api/reference.md:2001 -#: src/api/reference.md:2007 src/api/reference.md:2017 -#: src/api/reference.md:2025 src/api/reference.md:2057 -#: src/api/reference.md:2083 src/api/reference.md:2109 -#: src/api/reference.md:2145 src/api/reference.md:2163 -#: src/api/reference.md:2174 src/api/reference.md:2186 -#: src/api/reference.md:2215 src/api/reference.md:2223 -#: src/api/reference.md:2238 src/api/reference.md:2252 -#: src/api/reference.md:2280 src/api/reference.md:2294 -#: src/api/reference.md:2302 src/api/reference.md:2315 -#: src/api/reference.md:2327 src/api/reference.md:2347 -#: src/api/reference.md:2375 src/api/reference.md:2392 -#: src/api/reference.md:2446 src/api/reference.md:2454 -#: src/api/reference.md:2519 src/api/reference.md:2648 -#: src/api/reference.md:2672 src/api/reference.md:2678 -#: src/api/reference.md:2692 src/api/reference.md:2711 -#: src/api/reference.md:2805 src/api/reference.md:2820 -#: src/api/reference.md:2836 src/api/reference.md:2865 -#: src/api/reference.md:2878 src/api/reference.md:2902 -#: src/api/reference.md:2916 src/api/reference.md:2951 -#: src/api/reference.md:3015 src/api/reference.md:3091 -#: src/api/reference.md:3106 src/api/reference.md:3158 -#: src/api/reference.md:3172 src/api/reference.md:3213 -#: src/api/reference.md:3251 src/api/reference.md:3265 -#: src/api/reference.md:3280 src/api/reference.md:3296 -#: src/api/reference.md:3342 src/api/reference.md:3365 -#: src/api/reference.md:3383 src/api/reference.md:3405 -#: src/api/reference.md:3434 src/api/reference.md:3475 -#: src/api/reference.md:3531 src/api/reference.md:3585 -#: src/api/reference.md:3621 src/api/reference.md:3642 -#: src/api/reference.md:3689 src/api/reference.md:3746 +#: src/api/reference.md:127 src/api/reference.md:137 src/api/reference.md:147 +#: src/api/reference.md:153 src/api/reference.md:159 src/api/reference.md:165 +#: src/api/reference.md:171 src/api/reference.md:177 src/api/reference.md:183 +#: src/api/reference.md:189 src/api/reference.md:195 src/api/reference.md:205 +#: src/api/reference.md:217 src/api/reference.md:229 src/api/reference.md:263 +#: src/api/reference.md:297 src/api/reference.md:325 src/api/reference.md:332 +#: src/api/reference.md:357 src/api/reference.md:379 src/api/reference.md:428 +#: src/api/reference.md:438 src/api/reference.md:458 src/api/reference.md:469 +#: src/api/reference.md:493 src/api/reference.md:515 src/api/reference.md:536 +#: src/api/reference.md:561 src/api/reference.md:842 src/api/reference.md:919 +#: src/api/reference.md:968 src/api/reference.md:987 src/api/reference.md:1096 +#: src/api/reference.md:1174 src/api/reference.md:1198 +#: src/api/reference.md:1214 src/api/reference.md:1266 +#: src/api/reference.md:1272 src/api/reference.md:1297 +#: src/api/reference.md:1402 src/api/reference.md:1461 +#: src/api/reference.md:1660 src/api/reference.md:1684 +#: src/api/reference.md:1736 src/api/reference.md:1767 +#: src/api/reference.md:1783 src/api/reference.md:1822 +#: src/api/reference.md:1869 src/api/reference.md:2003 +#: src/api/reference.md:2009 src/api/reference.md:2019 +#: src/api/reference.md:2027 src/api/reference.md:2059 +#: src/api/reference.md:2085 src/api/reference.md:2111 +#: src/api/reference.md:2147 src/api/reference.md:2165 +#: src/api/reference.md:2176 src/api/reference.md:2188 +#: src/api/reference.md:2217 src/api/reference.md:2225 +#: src/api/reference.md:2243 src/api/reference.md:2257 +#: src/api/reference.md:2285 src/api/reference.md:2299 +#: src/api/reference.md:2307 src/api/reference.md:2320 +#: src/api/reference.md:2332 src/api/reference.md:2352 +#: src/api/reference.md:2380 src/api/reference.md:2397 +#: src/api/reference.md:2451 src/api/reference.md:2459 +#: src/api/reference.md:2524 src/api/reference.md:2653 +#: src/api/reference.md:2677 src/api/reference.md:2683 +#: src/api/reference.md:2697 src/api/reference.md:2716 +#: src/api/reference.md:2810 src/api/reference.md:2825 +#: src/api/reference.md:2841 src/api/reference.md:2874 +#: src/api/reference.md:2887 src/api/reference.md:2911 +#: src/api/reference.md:2925 src/api/reference.md:2960 +#: src/api/reference.md:3024 src/api/reference.md:3100 +#: src/api/reference.md:3115 src/api/reference.md:3167 +#: src/api/reference.md:3181 src/api/reference.md:3222 +#: src/api/reference.md:3260 src/api/reference.md:3274 +#: src/api/reference.md:3289 src/api/reference.md:3305 +#: src/api/reference.md:3346 src/api/reference.md:3362 +#: src/api/reference.md:3385 src/api/reference.md:3403 +#: src/api/reference.md:3425 src/api/reference.md:3454 +#: src/api/reference.md:3495 src/api/reference.md:3551 +#: src/api/reference.md:3605 src/api/reference.md:3641 +#: src/api/reference.md:3662 src/api/reference.md:3709 +#: src/api/reference.md:3766 msgid "Methods" msgstr "Métodos" -#: src/api/reference.md:128 src/api/reference.md:399 src/api/reference.md:428 -#: src/api/reference.md:521 src/api/reference.md:2009 src/api/reference.md:2450 +#: src/api/reference.md:129 src/api/reference.md:401 src/api/reference.md:430 +#: src/api/reference.md:523 src/api/reference.md:2011 src/api/reference.md:2455 msgid "`spawn` — module" msgstr "`spawn` — módulo" -#: src/api/reference.md:130 src/api/reference.md:251 src/api/reference.md:285 -#: src/api/reference.md:317 src/api/reference.md:364 src/api/reference.md:411 -#: src/api/reference.md:430 src/api/reference.md:448 src/api/reference.md:484 -#: src/api/reference.md:524 src/api/reference.md:541 src/api/reference.md:588 -#: src/api/reference.md:904 src/api/reference.md:955 src/api/reference.md:975 -#: src/api/reference.md:1029 src/api/reference.md:1138 -#: src/api/reference.md:1188 src/api/reference.md:1245 -#: src/api/reference.md:1278 src/api/reference.md:1393 -#: src/api/reference.md:1438 src/api/reference.md:1643 -#: src/api/reference.md:1668 src/api/reference.md:1724 -#: src/api/reference.md:1753 src/api/reference.md:1775 -#: src/api/reference.md:1846 src/api/reference.md:2011 -#: src/api/reference.md:2048 src/api/reference.md:2073 -#: src/api/reference.md:2099 src/api/reference.md:2125 -#: src/api/reference.md:2155 src/api/reference.md:2168 -#: src/api/reference.md:2365 src/api/reference.md:2685 -#: src/api/reference.md:2769 src/api/reference.md:2812 -#: src/api/reference.md:2830 src/api/reference.md:2893 -#: src/api/reference.md:2910 src/api/reference.md:3000 -#: src/api/reference.md:3084 src/api/reference.md:3100 -#: src/api/reference.md:3135 src/api/reference.md:3206 -#: src/api/reference.md:3243 src/api/reference.md:3259 -#: src/api/reference.md:3274 src/api/reference.md:3286 -#: src/api/reference.md:3325 src/api/reference.md:3370 -#: src/api/reference.md:3421 src/api/reference.md:3468 -#: src/api/reference.md:3574 src/api/reference.md:3610 -#: src/api/reference.md:3629 src/api/reference.md:3669 -#: src/api/reference.md:3714 src/api/reference.md:3783 +#: src/api/reference.md:131 src/api/reference.md:252 src/api/reference.md:286 +#: src/api/reference.md:318 src/api/reference.md:366 src/api/reference.md:413 +#: src/api/reference.md:432 src/api/reference.md:450 src/api/reference.md:486 +#: src/api/reference.md:526 src/api/reference.md:543 src/api/reference.md:590 +#: src/api/reference.md:906 src/api/reference.md:957 src/api/reference.md:977 +#: src/api/reference.md:1031 src/api/reference.md:1140 +#: src/api/reference.md:1190 src/api/reference.md:1247 +#: src/api/reference.md:1280 src/api/reference.md:1395 +#: src/api/reference.md:1440 src/api/reference.md:1645 +#: src/api/reference.md:1670 src/api/reference.md:1726 +#: src/api/reference.md:1755 src/api/reference.md:1777 +#: src/api/reference.md:1848 src/api/reference.md:2013 +#: src/api/reference.md:2050 src/api/reference.md:2075 +#: src/api/reference.md:2101 src/api/reference.md:2127 +#: src/api/reference.md:2157 src/api/reference.md:2170 +#: src/api/reference.md:2370 src/api/reference.md:2690 +#: src/api/reference.md:2774 src/api/reference.md:2817 +#: src/api/reference.md:2835 src/api/reference.md:2863 +#: src/api/reference.md:2902 src/api/reference.md:2919 +#: src/api/reference.md:3009 src/api/reference.md:3093 +#: src/api/reference.md:3109 src/api/reference.md:3144 +#: src/api/reference.md:3215 src/api/reference.md:3252 +#: src/api/reference.md:3268 src/api/reference.md:3283 +#: src/api/reference.md:3295 src/api/reference.md:3334 +#: src/api/reference.md:3351 src/api/reference.md:3390 +#: src/api/reference.md:3441 src/api/reference.md:3488 +#: src/api/reference.md:3594 src/api/reference.md:3630 +#: src/api/reference.md:3649 src/api/reference.md:3689 +#: src/api/reference.md:3734 src/api/reference.md:3803 msgid "Properties" msgstr "Propiedades" -#: src/api/reference.md:132 src/api/reference.md:320 src/api/reference.md:432 -#: src/api/reference.md:526 src/api/reference.md:545 src/api/reference.md:825 -#: src/api/reference.md:957 src/api/reference.md:977 src/api/reference.md:1085 -#: src/api/reference.md:1164 src/api/reference.md:1249 -#: src/api/reference.md:1441 src/api/reference.md:1671 -#: src/api/reference.md:1757 src/api/reference.md:1777 -#: src/api/reference.md:1854 src/api/reference.md:2013 -#: src/api/reference.md:2052 src/api/reference.md:2075 -#: src/api/reference.md:2101 src/api/reference.md:2127 -#: src/api/reference.md:2158 src/api/reference.md:2814 -#: src/api/reference.md:2832 src/api/reference.md:2898 -#: src/api/reference.md:2912 src/api/reference.md:3102 -#: src/api/reference.md:3137 src/api/reference.md:3208 -#: src/api/reference.md:3261 src/api/reference.md:3423 -#: src/api/reference.md:3470 src/api/reference.md:3613 +#: src/api/reference.md:133 src/api/reference.md:321 src/api/reference.md:434 +#: src/api/reference.md:528 src/api/reference.md:547 src/api/reference.md:827 +#: src/api/reference.md:959 src/api/reference.md:979 src/api/reference.md:1087 +#: src/api/reference.md:1166 src/api/reference.md:1251 +#: src/api/reference.md:1443 src/api/reference.md:1673 +#: src/api/reference.md:1759 src/api/reference.md:1779 +#: src/api/reference.md:1856 src/api/reference.md:2015 +#: src/api/reference.md:2054 src/api/reference.md:2077 +#: src/api/reference.md:2103 src/api/reference.md:2129 +#: src/api/reference.md:2160 src/api/reference.md:2819 +#: src/api/reference.md:2837 src/api/reference.md:2907 +#: src/api/reference.md:2921 src/api/reference.md:3111 +#: src/api/reference.md:3146 src/api/reference.md:3217 +#: src/api/reference.md:3270 src/api/reference.md:3353 +#: src/api/reference.md:3443 src/api/reference.md:3490 +#: src/api/reference.md:3633 msgid "`default`" msgstr "`default`" -#: src/api/reference.md:134 +#: src/api/reference.md:135 msgid "`@parcel/watcher`" msgstr "`@parcel/watcher`" -#: src/api/reference.md:138 +#: src/api/reference.md:139 msgid "`__nativeEventCount` — module" msgstr "`__nativeEventCount` — módulo" -#: src/api/reference.md:139 +#: src/api/reference.md:140 msgid "`getEventsSince` — module" msgstr "`getEventsSince` — módulo" -#: src/api/reference.md:140 src/api/reference.md:148 src/api/reference.md:154 -#: src/api/reference.md:160 src/api/reference.md:166 src/api/reference.md:172 -#: src/api/reference.md:178 src/api/reference.md:184 src/api/reference.md:190 -#: src/api/reference.md:971 +#: src/api/reference.md:141 src/api/reference.md:149 src/api/reference.md:155 +#: src/api/reference.md:161 src/api/reference.md:167 src/api/reference.md:173 +#: src/api/reference.md:179 src/api/reference.md:185 src/api/reference.md:191 +#: src/api/reference.md:973 msgid "`subscribe` — module" msgstr "`subscribe` — módulo" -#: src/api/reference.md:141 src/api/reference.md:973 +#: src/api/reference.md:142 src/api/reference.md:975 msgid "`unsubscribe` — module" msgstr "`unsubscribe` — módulo" -#: src/api/reference.md:142 +#: src/api/reference.md:143 msgid "`writeSnapshot` — module" msgstr "`writeSnapshot` — módulo" -#: src/api/reference.md:144 +#: src/api/reference.md:145 msgid "`@parcel/watcher-darwin-arm64`" msgstr "`@parcel/watcher-darwin-arm64`" -#: src/api/reference.md:150 +#: src/api/reference.md:151 msgid "`@parcel/watcher-darwin-x64`" msgstr "`@parcel/watcher-darwin-x64`" -#: src/api/reference.md:156 +#: src/api/reference.md:157 msgid "`@parcel/watcher-linux-arm64-glibc`" msgstr "`@parcel/watcher-linux-arm64-glibc`" -#: src/api/reference.md:162 +#: src/api/reference.md:163 msgid "`@parcel/watcher-linux-arm64-musl`" msgstr "`@parcel/watcher-linux-arm64-musl`" -#: src/api/reference.md:168 +#: src/api/reference.md:169 msgid "`@parcel/watcher-linux-x64-glibc`" msgstr "`@parcel/watcher-linux-x64-glibc`" -#: src/api/reference.md:174 +#: src/api/reference.md:175 msgid "`@parcel/watcher-linux-x64-musl`" msgstr "`@parcel/watcher-linux-x64-musl`" -#: src/api/reference.md:180 +#: src/api/reference.md:181 msgid "`@parcel/watcher-win32-arm64`" msgstr "`@parcel/watcher-win32-arm64`" -#: src/api/reference.md:186 +#: src/api/reference.md:187 msgid "`@parcel/watcher-win32-x64`" msgstr "`@parcel/watcher-win32-x64`" -#: src/api/reference.md:196 +#: src/api/reference.md:197 msgid "`createPdf` — module" msgstr "`createPdf` — módulo" -#: src/api/reference.md:197 +#: src/api/reference.md:198 msgid "`pdfAddLine` — module" msgstr "`pdfAddLine` — módulo" -#: src/api/reference.md:198 +#: src/api/reference.md:199 msgid "`pdfAddText` — module" msgstr "`pdfAddText` — módulo" -#: src/api/reference.md:199 +#: src/api/reference.md:200 msgid "`pdfNewPage` — module" msgstr "`pdfNewPage` — módulo" -#: src/api/reference.md:200 +#: src/api/reference.md:201 msgid "`pdfSave` — module" msgstr "`pdfSave` — módulo" -#: src/api/reference.md:202 +#: src/api/reference.md:203 msgid "`__disposable__`" msgstr "`__disposable__`" -#: src/api/reference.md:206 +#: src/api/reference.md:207 msgid "`adopt` — instance" msgstr "`adopt` — instancia" -#: src/api/reference.md:207 +#: src/api/reference.md:208 msgid "`defer` — instance" msgstr "`defer` — instancia" -#: src/api/reference.md:208 +#: src/api/reference.md:209 msgid "`dispose` — instance" msgstr "`dispose` — instancia" -#: src/api/reference.md:209 +#: src/api/reference.md:210 msgid "`disposeAsync` — instance" msgstr "`disposeAsync` — instancia" -#: src/api/reference.md:210 +#: src/api/reference.md:211 msgid "`disposed` — instance" msgstr "`disposed` — instancia" -#: src/api/reference.md:211 +#: src/api/reference.md:212 msgid "`move` — instance" msgstr "`move` — instancia" -#: src/api/reference.md:212 +#: src/api/reference.md:213 msgid "`use` — instance" msgstr "`use` — instancia" -#: src/api/reference.md:218 src/api/reference.md:327 src/api/reference.md:390 -#: src/api/reference.md:881 +#: src/api/reference.md:219 src/api/reference.md:328 src/api/reference.md:392 +#: src/api/reference.md:883 msgid "`hash` — module" msgstr "`hash` — módulo" -#: src/api/reference.md:219 src/api/reference.md:902 +#: src/api/reference.md:220 src/api/reference.md:904 msgid "`verify` — module" msgstr "`verify` — módulo" -#: src/api/reference.md:221 src/api/reference.md:3245 +#: src/api/reference.md:222 src/api/reference.md:3254 msgid "`assert`" msgstr "`assert`" -#: src/api/reference.md:225 src/api/reference.md:259 +#: src/api/reference.md:226 src/api/reference.md:260 msgid "`Assert`" msgstr "`Assert`" -#: src/api/reference.md:226 src/api/reference.md:260 +#: src/api/reference.md:227 src/api/reference.md:261 msgid "`AssertionError`" msgstr "`AssertionError`" -#: src/api/reference.md:230 src/api/reference.md:264 +#: src/api/reference.md:231 src/api/reference.md:265 msgid "`deepEqual` — module" msgstr "`deepEqual` — módulo" -#: src/api/reference.md:231 src/api/reference.md:265 +#: src/api/reference.md:232 src/api/reference.md:266 msgid "`deepStrictEqual` — module" msgstr "`deepStrictEqual` — módulo" -#: src/api/reference.md:232 src/api/reference.md:266 src/api/reference.md:336 -#: src/api/reference.md:1266 src/api/reference.md:2003 -#: src/api/reference.md:2922 src/api/reference.md:3026 -#: src/api/reference.md:3219 +#: src/api/reference.md:233 src/api/reference.md:267 src/api/reference.md:337 +#: src/api/reference.md:1268 src/api/reference.md:2005 +#: src/api/reference.md:2931 src/api/reference.md:3035 +#: src/api/reference.md:3228 msgid "`default` — module" msgstr "`default` — módulo" -#: src/api/reference.md:233 src/api/reference.md:267 +#: src/api/reference.md:234 src/api/reference.md:268 msgid "`doesNotMatch` — module" msgstr "`doesNotMatch` — módulo" -#: src/api/reference.md:234 src/api/reference.md:268 +#: src/api/reference.md:235 src/api/reference.md:269 msgid "`doesNotReject` — module" msgstr "`doesNotReject` — módulo" -#: src/api/reference.md:235 src/api/reference.md:269 +#: src/api/reference.md:236 src/api/reference.md:270 msgid "`doesNotThrow` — module" msgstr "`doesNotThrow` — módulo" -#: src/api/reference.md:236 src/api/reference.md:270 +#: src/api/reference.md:237 src/api/reference.md:271 msgid "`equal` — module" msgstr "`equal` — módulo" -#: src/api/reference.md:237 src/api/reference.md:271 +#: src/api/reference.md:238 src/api/reference.md:272 msgid "`fail` — module" msgstr "`fail` — módulo" -#: src/api/reference.md:238 src/api/reference.md:272 +#: src/api/reference.md:239 src/api/reference.md:273 msgid "`ifError` — module" msgstr "`ifError` — módulo" -#: src/api/reference.md:239 src/api/reference.md:273 +#: src/api/reference.md:240 src/api/reference.md:274 msgid "`match` — module" msgstr "`match` — módulo" -#: src/api/reference.md:240 src/api/reference.md:274 +#: src/api/reference.md:241 src/api/reference.md:275 msgid "`notDeepEqual` — module" msgstr "`notDeepEqual` — módulo" -#: src/api/reference.md:241 src/api/reference.md:275 +#: src/api/reference.md:242 src/api/reference.md:276 msgid "`notDeepStrictEqual` — module" msgstr "`notDeepStrictEqual` — módulo" -#: src/api/reference.md:242 src/api/reference.md:276 +#: src/api/reference.md:243 src/api/reference.md:277 msgid "`notEqual` — module" msgstr "`notEqual` — módulo" -#: src/api/reference.md:243 src/api/reference.md:277 +#: src/api/reference.md:244 src/api/reference.md:278 msgid "`notStrictEqual` — module" msgstr "`notStrictEqual` — módulo" -#: src/api/reference.md:244 src/api/reference.md:278 +#: src/api/reference.md:245 src/api/reference.md:279 msgid "`ok` — module" msgstr "`ok` — módulo" -#: src/api/reference.md:245 src/api/reference.md:279 +#: src/api/reference.md:246 src/api/reference.md:280 msgid "`partialDeepStrictEqual` — module" msgstr "`partialDeepStrictEqual` — módulo" -#: src/api/reference.md:246 src/api/reference.md:280 +#: src/api/reference.md:247 src/api/reference.md:281 msgid "`rejects` — module" msgstr "`rejects` — módulo" -#: src/api/reference.md:247 src/api/reference.md:281 +#: src/api/reference.md:248 src/api/reference.md:282 msgid "`strict` — module" msgstr "`strict` — módulo" -#: src/api/reference.md:248 src/api/reference.md:282 +#: src/api/reference.md:249 src/api/reference.md:283 msgid "`strictEqual` — module" msgstr "`strictEqual` — módulo" -#: src/api/reference.md:249 src/api/reference.md:283 +#: src/api/reference.md:250 src/api/reference.md:284 msgid "`throws` — module" msgstr "`throws` — módulo" -#: src/api/reference.md:253 src/api/reference.md:287 +#: src/api/reference.md:254 src/api/reference.md:288 msgid "`strict`" msgstr "`strict`" -#: src/api/reference.md:255 +#: src/api/reference.md:256 msgid "`assert/strict`" msgstr "`assert/strict`" -#: src/api/reference.md:289 +#: src/api/reference.md:290 msgid "`async_hooks`" msgstr "`async_hooks`" -#: src/api/reference.md:293 +#: src/api/reference.md:294 msgid "`AsyncLocalStorage`" msgstr "`AsyncLocalStorage`" -#: src/api/reference.md:294 +#: src/api/reference.md:295 msgid "`AsyncResource`" msgstr "`AsyncResource`" -#: src/api/reference.md:298 +#: src/api/reference.md:299 msgid "`asyncId` — instance _(class: `AsyncResource`)_" msgstr "`asyncId` — instancia _(clase: `AsyncResource`)_" -#: src/api/reference.md:299 +#: src/api/reference.md:300 msgid "`bind` — module _(class: `AsyncLocalStorage`)_" msgstr "`bind` — módulo _(clase: `AsyncLocalStorage`)_" -#: src/api/reference.md:300 +#: src/api/reference.md:301 msgid "`bind` — module _(class: `AsyncResource`)_" msgstr "`bind` — módulo _(clase: `AsyncResource`)_" -#: src/api/reference.md:301 +#: src/api/reference.md:302 msgid "`bind` — instance _(class: `AsyncResource`)_" msgstr "`bind` — instancia _(clase: `AsyncResource`)_" -#: src/api/reference.md:302 +#: src/api/reference.md:303 msgid "`createHook` — module" msgstr "`createHook` — módulo" -#: src/api/reference.md:303 +#: src/api/reference.md:304 msgid "`disable` — instance" msgstr "`disable` — instancia" -#: src/api/reference.md:304 +#: src/api/reference.md:305 msgid "`emitDestroy` — instance _(class: `AsyncResource`)_" msgstr "`emitDestroy` — instancia _(clase: `AsyncResource`)_" -#: src/api/reference.md:305 +#: src/api/reference.md:306 msgid "`enable` — instance _(class: `AsyncHook`)_" msgstr "`enable` — instancia _(clase: `AsyncHook`)_" -#: src/api/reference.md:306 +#: src/api/reference.md:307 msgid "`enterWith` — instance" msgstr "`enterWith` — instancia" -#: src/api/reference.md:307 +#: src/api/reference.md:308 msgid "`executionAsyncId` — module" msgstr "`executionAsyncId` — módulo" -#: src/api/reference.md:308 +#: src/api/reference.md:309 msgid "`executionAsyncResource` — module" msgstr "`executionAsyncResource` — módulo" -#: src/api/reference.md:309 src/api/reference.md:1182 +#: src/api/reference.md:310 src/api/reference.md:1184 msgid "`exit` — instance" msgstr "`exit` — instancia" -#: src/api/reference.md:310 +#: src/api/reference.md:311 msgid "`getStore` — instance" msgstr "`getStore` — instancia" -#: src/api/reference.md:311 src/api/reference.md:344 src/api/reference.md:1186 -#: src/api/reference.md:2990 +#: src/api/reference.md:312 src/api/reference.md:345 src/api/reference.md:1188 +#: src/api/reference.md:2999 msgid "`run` — instance" msgstr "`run` — instancia" -#: src/api/reference.md:312 +#: src/api/reference.md:313 msgid "`runInAsyncScope` — instance _(class: `AsyncResource`)_" msgstr "`runInAsyncScope` — instancia _(clase: `AsyncResource`)_" -#: src/api/reference.md:313 +#: src/api/reference.md:314 msgid "`snapshot` — module _(class: `AsyncLocalStorage`)_" msgstr "`snapshot` — módulo _(clase: `AsyncLocalStorage`)_" -#: src/api/reference.md:314 +#: src/api/reference.md:315 msgid "`triggerAsyncId` — module" msgstr "`triggerAsyncId` — módulo" -#: src/api/reference.md:315 +#: src/api/reference.md:316 msgid "`triggerAsyncId` — instance _(class: `AsyncResource`)_" msgstr "`triggerAsyncId` — instancia _(clase: `AsyncResource`)_" -#: src/api/reference.md:319 +#: src/api/reference.md:320 msgid "`asyncWrapProviders`" msgstr "`asyncWrapProviders`" -#: src/api/reference.md:326 +#: src/api/reference.md:327 msgid "`compare` — module" msgstr "`compare` — módulo" -#: src/api/reference.md:333 src/api/reference.md:2960 +#: src/api/reference.md:334 src/api/reference.md:2969 msgid "`all` — instance" msgstr "`all` — instancia" -#: src/api/reference.md:334 src/api/reference.md:2840 src/api/reference.md:2867 -#: src/api/reference.md:2966 src/api/reference.md:3696 +#: src/api/reference.md:335 src/api/reference.md:2845 src/api/reference.md:2876 +#: src/api/reference.md:2975 src/api/reference.md:3716 msgid "`close` — instance" msgstr "`close` — instancia" -#: src/api/reference.md:335 src/api/reference.md:2967 +#: src/api/reference.md:336 src/api/reference.md:2976 msgid "`columns` — instance" msgstr "`columns` — instancia" -#: src/api/reference.md:337 src/api/reference.md:2974 +#: src/api/reference.md:338 src/api/reference.md:2983 msgid "`exec` — instance" msgstr "`exec` — instancia" -#: src/api/reference.md:338 src/api/reference.md:2978 +#: src/api/reference.md:339 src/api/reference.md:2987 msgid "`get` — instance" msgstr "`get` — instancia" -#: src/api/reference.md:339 src/api/reference.md:2982 +#: src/api/reference.md:340 src/api/reference.md:2991 msgid "`iterate` — instance" msgstr "`iterate` — instancia" -#: src/api/reference.md:340 +#: src/api/reference.md:341 msgid "`pluck` — instance" msgstr "`pluck` — instancia" -#: src/api/reference.md:341 +#: src/api/reference.md:342 msgid "`pragma` — instance" msgstr "`pragma` — instancia" -#: src/api/reference.md:342 src/api/reference.md:2988 +#: src/api/reference.md:343 src/api/reference.md:2997 msgid "`prepare` — instance" msgstr "`prepare` — instancia" -#: src/api/reference.md:343 +#: src/api/reference.md:344 msgid "`raw` — instance" msgstr "`raw` — instancia" -#: src/api/reference.md:345 +#: src/api/reference.md:346 +#, fuzzy +msgid "`safeIntegers` — instance" +msgstr "`enter` — instancia" + +#: src/api/reference.md:347 msgid "`transaction` — instance" msgstr "`transaction` — instancia" -#: src/api/reference.md:347 +#: src/api/reference.md:349 msgid "`buffer`" msgstr "`buffer`" -#: src/api/reference.md:351 src/api/reference.md:1258 src/api/reference.md:1995 +#: src/api/reference.md:353 src/api/reference.md:1260 src/api/reference.md:1997 msgid "`Blob`" msgstr "`Blob`" -#: src/api/reference.md:352 +#: src/api/reference.md:354 msgid "`Buffer`" msgstr "`Buffer`" -#: src/api/reference.md:353 +#: src/api/reference.md:355 msgid "`File`" msgstr "`File`" -#: src/api/reference.md:357 +#: src/api/reference.md:359 msgid "`atob` — module" msgstr "`atob` — módulo" -#: src/api/reference.md:358 +#: src/api/reference.md:360 msgid "`btoa` — module" msgstr "`btoa` — módulo" -#: src/api/reference.md:359 +#: src/api/reference.md:361 msgid "`isAscii` — module" msgstr "`isAscii` — módulo" -#: src/api/reference.md:360 +#: src/api/reference.md:362 msgid "`isUtf8` — module" msgstr "`isUtf8` — módulo" -#: src/api/reference.md:361 +#: src/api/reference.md:363 msgid "`resolveObjectURL` — module" msgstr "`resolveObjectURL` — módulo" -#: src/api/reference.md:362 +#: src/api/reference.md:364 msgid "`transcode` — module" msgstr "`transcode` — módulo" -#: src/api/reference.md:366 +#: src/api/reference.md:368 msgid "`INSPECT_MAX_BYTES`" msgstr "`INSPECT_MAX_BYTES`" -#: src/api/reference.md:367 src/api/reference.md:586 src/api/reference.md:907 -#: src/api/reference.md:1395 src/api/reference.md:1440 -#: src/api/reference.md:1670 src/api/reference.md:1853 -#: src/api/reference.md:2051 src/api/reference.md:2157 -#: src/api/reference.md:3002 src/api/reference.md:3612 -#: src/api/reference.md:3786 +#: src/api/reference.md:369 src/api/reference.md:588 src/api/reference.md:909 +#: src/api/reference.md:1397 src/api/reference.md:1442 +#: src/api/reference.md:1672 src/api/reference.md:1855 +#: src/api/reference.md:2053 src/api/reference.md:2159 +#: src/api/reference.md:3011 src/api/reference.md:3632 +#: src/api/reference.md:3806 msgid "`constants`" msgstr "`constants`" -#: src/api/reference.md:368 +#: src/api/reference.md:370 msgid "`kMaxLength`" msgstr "`kMaxLength`" -#: src/api/reference.md:369 +#: src/api/reference.md:371 msgid "`kStringMaxLength`" msgstr "`kStringMaxLength`" -#: src/api/reference.md:371 +#: src/api/reference.md:373 msgid "`bun`" msgstr "`bun`" -#: src/api/reference.md:375 +#: src/api/reference.md:377 #, fuzzy msgid "`Transpiler`" msgstr "Transferencia" -#: src/api/reference.md:379 +#: src/api/reference.md:381 msgid "`Glob` — module" msgstr "`Glob` — módulo" -#: src/api/reference.md:380 +#: src/api/reference.md:382 #, fuzzy msgid "`SQL` — module" msgstr "`t` — módulo" -#: src/api/reference.md:381 +#: src/api/reference.md:383 #, fuzzy msgid "`Terminal` — module" msgstr "`onTerminate` — módulo" -#: src/api/reference.md:382 +#: src/api/reference.md:384 #, fuzzy msgid "`Transpiler` — module" msgstr "`range` — módulo" -#: src/api/reference.md:383 +#: src/api/reference.md:385 #, fuzzy msgid "`build` — module" msgstr "`bind` — módulo" -#: src/api/reference.md:384 src/api/reference.md:1660 src/api/reference.md:1894 -#: src/api/reference.md:3305 +#: src/api/reference.md:386 src/api/reference.md:1662 src/api/reference.md:1896 +#: src/api/reference.md:3314 msgid "`connect` — module" msgstr "`connect` — módulo" -#: src/api/reference.md:385 +#: src/api/reference.md:387 #, fuzzy msgid "`deepEquals` — module" msgstr "`deepEqual` — módulo" -#: src/api/reference.md:386 +#: src/api/reference.md:388 msgid "`file` — module" msgstr "`file` — módulo" -#: src/api/reference.md:387 src/api/reference.md:3411 +#: src/api/reference.md:389 src/api/reference.md:3431 msgid "`fileURLToPath` — module" msgstr "`fileURLToPath` — módulo" -#: src/api/reference.md:388 +#: src/api/reference.md:390 #, fuzzy msgid "`gc` — module" msgstr "`cp` — módulo" -#: src/api/reference.md:389 +#: src/api/reference.md:391 #, fuzzy msgid "`generateHeapSnapshot` — module" msgstr "`getHeapSnapshot` — módulo" -#: src/api/reference.md:391 +#: src/api/reference.md:393 #, fuzzy msgid "`listen` — module" msgstr "`list` — módulo" -#: src/api/reference.md:392 src/api/reference.md:3415 +#: src/api/reference.md:394 src/api/reference.md:3435 msgid "`pathToFileURL` — module" msgstr "`pathToFileURL` — módulo" -#: src/api/reference.md:393 +#: src/api/reference.md:395 msgid "" "`plugin` — module ⚠ **stub** — setup runs synchronously; runtime loader " "hooks and clearAll are inert (#10100)" msgstr "" -#: src/api/reference.md:394 +#: src/api/reference.md:396 #, fuzzy msgid "`scan` — instance _(class: `Transpiler`)_" msgstr "`cancel` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:395 +#: src/api/reference.md:397 #, fuzzy msgid "`scanImports` — instance _(class: `Transpiler`)_" msgstr "`start` — instancia _(clase: `GCProfiler`)_" -#: src/api/reference.md:396 +#: src/api/reference.md:398 #, fuzzy msgid "`serve` — module" msgstr "`Server` — módulo" -#: src/api/reference.md:397 +#: src/api/reference.md:399 #, fuzzy msgid "`sleep` — module" msgstr "`seek` — módulo" -#: src/api/reference.md:398 +#: src/api/reference.md:400 #, fuzzy msgid "`sleepSync` — module" msgstr "`execSync` — módulo" -#: src/api/reference.md:400 +#: src/api/reference.md:402 msgid "`stringWidth` — module" msgstr "`stringWidth` — módulo" -#: src/api/reference.md:401 +#: src/api/reference.md:403 #, fuzzy msgid "`stripANSI` — module" msgstr "`strict` — módulo" -#: src/api/reference.md:402 +#: src/api/reference.md:404 #, fuzzy msgid "`transform` — instance _(class: `Transpiler`)_" msgstr "`start` — instancia _(clase: `GCProfiler`)_" -#: src/api/reference.md:403 +#: src/api/reference.md:405 #, fuzzy msgid "`transformSync` — instance _(class: `Transpiler`)_" msgstr "`transaction` — instancia _(clase: `Database`)_" -#: src/api/reference.md:404 +#: src/api/reference.md:406 msgid "`unsupported` — module" msgstr "`unsupported` — módulo" -#: src/api/reference.md:405 +#: src/api/reference.md:407 #, fuzzy msgid "`which` — module" msgstr "`watch` — módulo" -#: src/api/reference.md:406 +#: src/api/reference.md:408 #, fuzzy msgid "`wrapAnsi` — module" msgstr "`warn` — módulo" -#: src/api/reference.md:407 src/api/reference.md:1386 +#: src/api/reference.md:409 src/api/reference.md:1388 msgid "`write` — module" msgstr "`write` — módulo" -#: src/api/reference.md:408 src/api/reference.md:3780 +#: src/api/reference.md:410 src/api/reference.md:3800 msgid "`zstdDecompress` — module" msgstr "`zstdDecompress` — módulo" -#: src/api/reference.md:409 src/api/reference.md:3781 +#: src/api/reference.md:411 src/api/reference.md:3801 msgid "`zstdDecompressSync` — module" msgstr "`zstdDecompressSync` — módulo" -#: src/api/reference.md:413 +#: src/api/reference.md:415 #, fuzzy msgid "`JSONL`" msgstr "JSON" -#: src/api/reference.md:414 +#: src/api/reference.md:416 msgid "`TOML`" msgstr "" -#: src/api/reference.md:415 +#: src/api/reference.md:417 msgid "`YAML`" msgstr "" -#: src/api/reference.md:416 +#: src/api/reference.md:418 msgid "`ant`" msgstr "" -#: src/api/reference.md:417 src/api/reference.md:2170 +#: src/api/reference.md:419 src/api/reference.md:2172 msgid "`isStandaloneExecutable`" msgstr "`isStandaloneExecutable`" -#: src/api/reference.md:418 +#: src/api/reference.md:420 #, fuzzy msgid "`semver`" msgstr "`server`" -#: src/api/reference.md:419 src/api/reference.md:2796 +#: src/api/reference.md:421 src/api/reference.md:2801 msgid "`stderr`" msgstr "`stderr`" -#: src/api/reference.md:420 src/api/reference.md:2797 +#: src/api/reference.md:422 src/api/reference.md:2802 msgid "`stdin`" msgstr "`stdin`" -#: src/api/reference.md:421 src/api/reference.md:2798 +#: src/api/reference.md:423 src/api/reference.md:2803 msgid "`stdout`" msgstr "`stdout`" -#: src/api/reference.md:422 src/api/reference.md:2800 src/api/reference.md:2816 +#: src/api/reference.md:424 src/api/reference.md:2805 src/api/reference.md:2821 #: src/updater/overview.md:109 src/cli/perry-toml.md:120 msgid "`version`" msgstr "`version`" -#: src/api/reference.md:424 +#: src/api/reference.md:426 #, fuzzy msgid "`bun-pty`" msgstr "`node-pty`" -#: src/api/reference.md:434 +#: src/api/reference.md:436 msgid "`bun:ffi`" msgstr "`bun:ffi`" -#: src/api/reference.md:438 +#: src/api/reference.md:440 msgid "`CFunction` — module" msgstr "`CFunction` — módulo" -#: src/api/reference.md:439 +#: src/api/reference.md:441 msgid "`CString` — module" msgstr "`CString` — módulo" -#: src/api/reference.md:440 +#: src/api/reference.md:442 msgid "`JSCallback` — module" msgstr "`JSCallback` — módulo" -#: src/api/reference.md:441 src/api/reference.md:1272 src/api/reference.md:2732 +#: src/api/reference.md:443 src/api/reference.md:1274 src/api/reference.md:2737 msgid "`dlopen` — module" msgstr "`dlopen` — módulo" -#: src/api/reference.md:442 +#: src/api/reference.md:444 msgid "`linkSymbols` — module" msgstr "`linkSymbols` — módulo" -#: src/api/reference.md:443 +#: src/api/reference.md:445 msgid "`ptr` — module" msgstr "`ptr` — módulo" -#: src/api/reference.md:444 src/api/reference.md:1274 +#: src/api/reference.md:446 src/api/reference.md:1276 msgid "`toArrayBuffer` — module" msgstr "`toArrayBuffer` — módulo" -#: src/api/reference.md:445 src/api/reference.md:1275 +#: src/api/reference.md:447 src/api/reference.md:1277 msgid "`toBuffer` — module" msgstr "`toBuffer` — módulo" -#: src/api/reference.md:446 +#: src/api/reference.md:448 msgid "`viewSource` — module" msgstr "`viewSource` — módulo" -#: src/api/reference.md:450 +#: src/api/reference.md:452 msgid "`FFIType`" msgstr "`FFIType`" -#: src/api/reference.md:451 +#: src/api/reference.md:453 msgid "`read`" msgstr "`read`" -#: src/api/reference.md:452 src/api/reference.md:1280 +#: src/api/reference.md:454 src/api/reference.md:1282 msgid "`suffix`" msgstr "`suffix`" -#: src/api/reference.md:454 +#: src/api/reference.md:456 #, fuzzy msgid "`bun:jsc`" msgstr "`bun`" -#: src/api/reference.md:458 +#: src/api/reference.md:460 #, fuzzy msgid "`heapStats` — module" msgstr "`State` — módulo" -#: src/api/reference.md:460 +#: src/api/reference.md:462 msgid "`bun:sqlite`" msgstr "`bun:sqlite`" -#: src/api/reference.md:464 +#: src/api/reference.md:466 msgid "`Database`" msgstr "`Database`" -#: src/api/reference.md:465 +#: src/api/reference.md:467 msgid "`Statement`" msgstr "`Statement`" -#: src/api/reference.md:469 +#: src/api/reference.md:471 msgid "`Database` — module" msgstr "`Database` — módulo" -#: src/api/reference.md:470 +#: src/api/reference.md:472 msgid "`all` — instance _(class: `Statement`)_" msgstr "`all` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:471 +#: src/api/reference.md:473 msgid "`close` — instance _(class: `Database`)_" msgstr "`close` — instancia _(clase: `Database`)_" -#: src/api/reference.md:472 +#: src/api/reference.md:474 msgid "`finalize` — instance _(class: `Statement`)_" msgstr "`finalize` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:473 +#: src/api/reference.md:475 msgid "`get` — instance _(class: `Statement`)_" msgstr "`get` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:474 +#: src/api/reference.md:476 msgid "`loadExtension` — instance _(class: `Database`)_" msgstr "`loadExtension` — instancia _(clase: `Database`)_" -#: src/api/reference.md:475 +#: src/api/reference.md:477 msgid "`prepare` — instance _(class: `Database`)_" msgstr "`prepare` — instancia _(clase: `Database`)_" -#: src/api/reference.md:476 +#: src/api/reference.md:478 msgid "`query` — instance _(class: `Database`)_" msgstr "`query` — instancia _(clase: `Database`)_" -#: src/api/reference.md:477 +#: src/api/reference.md:479 msgid "`run` — instance _(class: `Database`)_" msgstr "`run` — instancia _(clase: `Database`)_" -#: src/api/reference.md:478 +#: src/api/reference.md:480 msgid "`run` — instance _(class: `Statement`)_" msgstr "`run` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:479 +#: src/api/reference.md:481 msgid "`safeIntegers` — instance _(class: `Statement`)_" msgstr "`safeIntegers` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:480 +#: src/api/reference.md:482 msgid "`serialize` — instance _(class: `Database`)_" msgstr "`serialize` — instancia _(clase: `Database`)_" -#: src/api/reference.md:481 +#: src/api/reference.md:483 msgid "`transaction` — instance _(class: `Database`)_" msgstr "`transaction` — instancia _(clase: `Database`)_" -#: src/api/reference.md:482 +#: src/api/reference.md:484 msgid "`values` — instance _(class: `Statement`)_" msgstr "`values` — instancia _(clase: `Statement`)_" -#: src/api/reference.md:486 +#: src/api/reference.md:488 msgid "`filename`" msgstr "`filename`" -#: src/api/reference.md:487 +#: src/api/reference.md:489 msgid "`inTransaction`" msgstr "`inTransaction`" -#: src/api/reference.md:493 +#: src/api/reference.md:495 msgid "`attr` — instance" msgstr "`attr` — instancia" -#: src/api/reference.md:494 +#: src/api/reference.md:496 msgid "`children` — instance" msgstr "`children` — instancia" -#: src/api/reference.md:495 +#: src/api/reference.md:497 msgid "`eq` — instance" msgstr "`eq` — instancia" -#: src/api/reference.md:496 +#: src/api/reference.md:498 msgid "`find` — instance" msgstr "`find` — instancia" -#: src/api/reference.md:497 +#: src/api/reference.md:499 msgid "`first` — instance" msgstr "`first` — instancia" -#: src/api/reference.md:498 +#: src/api/reference.md:500 msgid "`hasClass` — instance" msgstr "`hasClass` — instancia" -#: src/api/reference.md:499 +#: src/api/reference.md:501 msgid "`html` — instance" msgstr "`html` — instancia" -#: src/api/reference.md:500 +#: src/api/reference.md:502 msgid "`last` — instance" msgstr "`last` — instancia" -#: src/api/reference.md:501 +#: src/api/reference.md:503 msgid "`length` — instance" msgstr "`length` — instancia" -#: src/api/reference.md:502 +#: src/api/reference.md:504 msgid "`load` — module" msgstr "`load` — módulo" -#: src/api/reference.md:503 +#: src/api/reference.md:505 msgid "`parent` — instance" msgstr "`parent` — instancia" -#: src/api/reference.md:504 +#: src/api/reference.md:506 msgid "`select` — instance" msgstr "`select` — instancia" -#: src/api/reference.md:505 +#: src/api/reference.md:507 msgid "`text` — instance" msgstr "`text` — instancia" -#: src/api/reference.md:507 +#: src/api/reference.md:509 msgid "`child_process`" msgstr "`child_process`" -#: src/api/reference.md:511 +#: src/api/reference.md:513 msgid "`ChildProcess`" msgstr "`ChildProcess`" -#: src/api/reference.md:515 +#: src/api/reference.md:517 msgid "`_forkChild` — module" msgstr "`_forkChild` — módulo" -#: src/api/reference.md:516 src/api/reference.md:2242 src/api/reference.md:2259 -#: src/api/reference.md:2284 +#: src/api/reference.md:518 src/api/reference.md:2247 src/api/reference.md:2264 +#: src/api/reference.md:2289 msgid "`exec` — module" msgstr "`exec` — módulo" -#: src/api/reference.md:517 +#: src/api/reference.md:519 msgid "`execFile` — module" msgstr "`execFile` — módulo" -#: src/api/reference.md:518 +#: src/api/reference.md:520 msgid "`execFileSync` — module" msgstr "`execFileSync` — módulo" -#: src/api/reference.md:519 +#: src/api/reference.md:521 msgid "`execSync` — module" msgstr "`execSync` — módulo" -#: src/api/reference.md:520 src/api/reference.md:537 +#: src/api/reference.md:522 src/api/reference.md:539 msgid "`fork` — module" msgstr "`fork` — módulo" -#: src/api/reference.md:522 +#: src/api/reference.md:524 msgid "`spawnSync` — module" msgstr "`spawnSync` — módulo" -#: src/api/reference.md:528 +#: src/api/reference.md:530 msgid "`cluster`" msgstr "`cluster`" -#: src/api/reference.md:536 +#: src/api/reference.md:534 src/api/reference.md:3660 +msgid "`Worker`" +msgstr "`Worker`" + +#: src/api/reference.md:538 msgid "`disconnect` — module" msgstr "`disconnect` — módulo" -#: src/api/reference.md:538 +#: src/api/reference.md:540 msgid "`setupMaster` — module" msgstr "`setupMaster` — módulo" -#: src/api/reference.md:539 +#: src/api/reference.md:541 msgid "`setupPrimary` — module" msgstr "`setupPrimary` — módulo" -#: src/api/reference.md:543 +#: src/api/reference.md:545 msgid "`SCHED_NONE`" msgstr "`SCHED_NONE`" -#: src/api/reference.md:544 +#: src/api/reference.md:546 msgid "`SCHED_RR`" msgstr "`SCHED_RR`" -#: src/api/reference.md:546 +#: src/api/reference.md:548 msgid "`isMaster`" msgstr "`isMaster`" -#: src/api/reference.md:547 +#: src/api/reference.md:549 msgid "`isPrimary`" msgstr "`isPrimary`" -#: src/api/reference.md:548 +#: src/api/reference.md:550 msgid "`isWorker`" msgstr "`isWorker`" -#: src/api/reference.md:549 +#: src/api/reference.md:551 msgid "`schedulingPolicy`" msgstr "`schedulingPolicy`" -#: src/api/reference.md:550 +#: src/api/reference.md:552 msgid "`settings`" msgstr "`settings`" -#: src/api/reference.md:551 +#: src/api/reference.md:553 msgid "`workers`" msgstr "`workers`" -#: src/api/reference.md:553 src/api/reference.md:1756 +#: src/api/reference.md:555 src/api/reference.md:1758 msgid "`console`" msgstr "`console`" -#: src/api/reference.md:557 +#: src/api/reference.md:559 msgid "`Console`" msgstr "`Console`" -#: src/api/reference.md:561 +#: src/api/reference.md:563 msgid "`assert` — module" msgstr "`assert` — módulo" -#: src/api/reference.md:562 +#: src/api/reference.md:564 msgid "`clear` — module" msgstr "`clear` — módulo" -#: src/api/reference.md:563 +#: src/api/reference.md:565 msgid "`context` — module" msgstr "`context` — módulo" -#: src/api/reference.md:564 +#: src/api/reference.md:566 msgid "`count` — module" msgstr "`count` — módulo" -#: src/api/reference.md:565 +#: src/api/reference.md:567 msgid "`countReset` — module" msgstr "`countReset` — módulo" -#: src/api/reference.md:566 +#: src/api/reference.md:568 msgid "`createTask` — module" msgstr "`createTask` — módulo" -#: src/api/reference.md:567 src/api/reference.md:3182 src/api/reference.md:3444 +#: src/api/reference.md:569 src/api/reference.md:3191 src/api/reference.md:3464 msgid "`debug` — module" msgstr "`debug` — módulo" -#: src/api/reference.md:568 +#: src/api/reference.md:570 msgid "`dir` — module" msgstr "`dir` — módulo" -#: src/api/reference.md:569 +#: src/api/reference.md:571 msgid "`dirxml` — module" msgstr "`dirxml` — módulo" -#: src/api/reference.md:570 +#: src/api/reference.md:572 msgid "`error` — module" msgstr "`error` — módulo" -#: src/api/reference.md:571 +#: src/api/reference.md:573 msgid "`group` — module" msgstr "`group` — módulo" -#: src/api/reference.md:572 +#: src/api/reference.md:574 msgid "`groupCollapsed` — module" msgstr "`groupCollapsed` — módulo" -#: src/api/reference.md:573 +#: src/api/reference.md:575 msgid "`groupEnd` — module" msgstr "`groupEnd` — módulo" -#: src/api/reference.md:574 +#: src/api/reference.md:576 msgid "`info` — module" msgstr "`info` — módulo" -#: src/api/reference.md:575 +#: src/api/reference.md:577 msgid "`log` — module" msgstr "`log` — módulo" -#: src/api/reference.md:576 +#: src/api/reference.md:578 msgid "`profile` — module" msgstr "`profile` — módulo" -#: src/api/reference.md:577 +#: src/api/reference.md:579 msgid "`profileEnd` — module" msgstr "`profileEnd` — módulo" -#: src/api/reference.md:578 +#: src/api/reference.md:580 msgid "`table` — module" msgstr "`table` — módulo" -#: src/api/reference.md:579 +#: src/api/reference.md:581 msgid "`time` — module" msgstr "`time` — módulo" -#: src/api/reference.md:580 +#: src/api/reference.md:582 msgid "`timeEnd` — module" msgstr "`timeEnd` — módulo" -#: src/api/reference.md:581 +#: src/api/reference.md:583 msgid "`timeLog` — module" msgstr "`timeLog` — módulo" -#: src/api/reference.md:582 +#: src/api/reference.md:584 msgid "`timeStamp` — module" msgstr "`timeStamp` — módulo" -#: src/api/reference.md:583 +#: src/api/reference.md:585 msgid "`trace` — module" msgstr "`trace` — módulo" -#: src/api/reference.md:584 +#: src/api/reference.md:586 msgid "`warn` — module" msgstr "`warn` — módulo" -#: src/api/reference.md:590 +#: src/api/reference.md:592 msgid "`COPYFILE_EXCL`" msgstr "`COPYFILE_EXCL`" -#: src/api/reference.md:591 +#: src/api/reference.md:593 msgid "`COPYFILE_FICLONE`" msgstr "`COPYFILE_FICLONE`" -#: src/api/reference.md:592 +#: src/api/reference.md:594 msgid "`COPYFILE_FICLONE_FORCE`" msgstr "`COPYFILE_FICLONE_FORCE`" -#: src/api/reference.md:593 +#: src/api/reference.md:595 msgid "`DH_CHECK_P_NOT_PRIME`" msgstr "`DH_CHECK_P_NOT_PRIME`" -#: src/api/reference.md:594 +#: src/api/reference.md:596 msgid "`DH_CHECK_P_NOT_SAFE_PRIME`" msgstr "`DH_CHECK_P_NOT_SAFE_PRIME`" -#: src/api/reference.md:595 +#: src/api/reference.md:597 msgid "`DH_NOT_SUITABLE_GENERATOR`" msgstr "`DH_NOT_SUITABLE_GENERATOR`" -#: src/api/reference.md:596 +#: src/api/reference.md:598 msgid "`DH_UNABLE_TO_CHECK_GENERATOR`" msgstr "`DH_UNABLE_TO_CHECK_GENERATOR`" -#: src/api/reference.md:597 +#: src/api/reference.md:599 msgid "`E2BIG`" msgstr "`E2BIG`" -#: src/api/reference.md:598 +#: src/api/reference.md:600 msgid "`EACCES`" msgstr "`EACCES`" -#: src/api/reference.md:599 +#: src/api/reference.md:601 msgid "`EADDRINUSE`" msgstr "`EADDRINUSE`" -#: src/api/reference.md:600 +#: src/api/reference.md:602 msgid "`EADDRNOTAVAIL`" msgstr "`EADDRNOTAVAIL`" -#: src/api/reference.md:601 +#: src/api/reference.md:603 msgid "`EAFNOSUPPORT`" msgstr "`EAFNOSUPPORT`" -#: src/api/reference.md:602 +#: src/api/reference.md:604 msgid "`EAGAIN`" msgstr "`EAGAIN`" -#: src/api/reference.md:603 +#: src/api/reference.md:605 msgid "`EALREADY`" msgstr "`EALREADY`" -#: src/api/reference.md:604 +#: src/api/reference.md:606 msgid "`EBADF`" msgstr "`EBADF`" -#: src/api/reference.md:605 +#: src/api/reference.md:607 msgid "`EBADMSG`" msgstr "`EBADMSG`" -#: src/api/reference.md:606 +#: src/api/reference.md:608 msgid "`EBUSY`" msgstr "`EBUSY`" -#: src/api/reference.md:607 +#: src/api/reference.md:609 msgid "`ECANCELED`" msgstr "`ECANCELED`" -#: src/api/reference.md:608 +#: src/api/reference.md:610 msgid "`ECHILD`" msgstr "`ECHILD`" -#: src/api/reference.md:609 +#: src/api/reference.md:611 msgid "`ECONNABORTED`" msgstr "`ECONNABORTED`" -#: src/api/reference.md:610 +#: src/api/reference.md:612 msgid "`ECONNREFUSED`" msgstr "`ECONNREFUSED`" -#: src/api/reference.md:611 +#: src/api/reference.md:613 msgid "`ECONNRESET`" msgstr "`ECONNRESET`" -#: src/api/reference.md:612 +#: src/api/reference.md:614 msgid "`EDEADLK`" msgstr "`EDEADLK`" -#: src/api/reference.md:613 +#: src/api/reference.md:615 msgid "`EDESTADDRREQ`" msgstr "`EDESTADDRREQ`" -#: src/api/reference.md:614 +#: src/api/reference.md:616 msgid "`EDOM`" msgstr "`EDOM`" -#: src/api/reference.md:615 +#: src/api/reference.md:617 msgid "`EDQUOT`" msgstr "`EDQUOT`" -#: src/api/reference.md:616 +#: src/api/reference.md:618 msgid "`EEXIST`" msgstr "`EEXIST`" -#: src/api/reference.md:617 +#: src/api/reference.md:619 msgid "`EFAULT`" msgstr "`EFAULT`" -#: src/api/reference.md:618 +#: src/api/reference.md:620 msgid "`EFBIG`" msgstr "`EFBIG`" -#: src/api/reference.md:619 +#: src/api/reference.md:621 msgid "`EHOSTUNREACH`" msgstr "`EHOSTUNREACH`" -#: src/api/reference.md:620 +#: src/api/reference.md:622 msgid "`EIDRM`" msgstr "`EIDRM`" -#: src/api/reference.md:621 +#: src/api/reference.md:623 msgid "`EILSEQ`" msgstr "`EILSEQ`" -#: src/api/reference.md:622 +#: src/api/reference.md:624 msgid "`EINPROGRESS`" msgstr "`EINPROGRESS`" -#: src/api/reference.md:623 +#: src/api/reference.md:625 msgid "`EINTR`" msgstr "`EINTR`" -#: src/api/reference.md:624 +#: src/api/reference.md:626 msgid "`EINVAL`" msgstr "`EINVAL`" -#: src/api/reference.md:625 +#: src/api/reference.md:627 msgid "`EIO`" msgstr "`EIO`" -#: src/api/reference.md:626 +#: src/api/reference.md:628 msgid "`EISCONN`" msgstr "`EISCONN`" -#: src/api/reference.md:627 +#: src/api/reference.md:629 msgid "`EISDIR`" msgstr "`EISDIR`" -#: src/api/reference.md:628 +#: src/api/reference.md:630 msgid "`ELOOP`" msgstr "`ELOOP`" -#: src/api/reference.md:629 +#: src/api/reference.md:631 msgid "`EMFILE`" msgstr "`EMFILE`" -#: src/api/reference.md:630 +#: src/api/reference.md:632 msgid "`EMLINK`" msgstr "`EMLINK`" -#: src/api/reference.md:631 +#: src/api/reference.md:633 msgid "`EMSGSIZE`" msgstr "`EMSGSIZE`" -#: src/api/reference.md:632 +#: src/api/reference.md:634 msgid "`EMULTIHOP`" msgstr "`EMULTIHOP`" -#: src/api/reference.md:633 +#: src/api/reference.md:635 msgid "`ENAMETOOLONG`" msgstr "`ENAMETOOLONG`" -#: src/api/reference.md:634 +#: src/api/reference.md:636 msgid "`ENETDOWN`" msgstr "`ENETDOWN`" -#: src/api/reference.md:635 +#: src/api/reference.md:637 msgid "`ENETRESET`" msgstr "`ENETRESET`" -#: src/api/reference.md:636 +#: src/api/reference.md:638 msgid "`ENETUNREACH`" msgstr "`ENETUNREACH`" -#: src/api/reference.md:637 +#: src/api/reference.md:639 msgid "`ENFILE`" msgstr "`ENFILE`" -#: src/api/reference.md:638 +#: src/api/reference.md:640 msgid "`ENGINE_METHOD_ALL`" msgstr "`ENGINE_METHOD_ALL`" -#: src/api/reference.md:639 +#: src/api/reference.md:641 msgid "`ENGINE_METHOD_CIPHERS`" msgstr "`ENGINE_METHOD_CIPHERS`" -#: src/api/reference.md:640 +#: src/api/reference.md:642 msgid "`ENGINE_METHOD_DH`" msgstr "`ENGINE_METHOD_DH`" -#: src/api/reference.md:641 +#: src/api/reference.md:643 msgid "`ENGINE_METHOD_DIGESTS`" msgstr "`ENGINE_METHOD_DIGESTS`" -#: src/api/reference.md:642 +#: src/api/reference.md:644 msgid "`ENGINE_METHOD_DSA`" msgstr "`ENGINE_METHOD_DSA`" -#: src/api/reference.md:643 +#: src/api/reference.md:645 msgid "`ENGINE_METHOD_EC`" msgstr "`ENGINE_METHOD_EC`" -#: src/api/reference.md:644 +#: src/api/reference.md:646 msgid "`ENGINE_METHOD_NONE`" msgstr "`ENGINE_METHOD_NONE`" -#: src/api/reference.md:645 +#: src/api/reference.md:647 msgid "`ENGINE_METHOD_PKEY_ASN1_METHS`" msgstr "`ENGINE_METHOD_PKEY_ASN1_METHS`" -#: src/api/reference.md:646 +#: src/api/reference.md:648 msgid "`ENGINE_METHOD_PKEY_METHS`" msgstr "`ENGINE_METHOD_PKEY_METHS`" -#: src/api/reference.md:647 +#: src/api/reference.md:649 msgid "`ENGINE_METHOD_RAND`" msgstr "`ENGINE_METHOD_RAND`" -#: src/api/reference.md:648 +#: src/api/reference.md:650 msgid "`ENGINE_METHOD_RSA`" msgstr "`ENGINE_METHOD_RSA`" -#: src/api/reference.md:649 +#: src/api/reference.md:651 msgid "`ENOBUFS`" msgstr "`ENOBUFS`" -#: src/api/reference.md:650 +#: src/api/reference.md:652 msgid "`ENODATA`" msgstr "`ENODATA`" -#: src/api/reference.md:651 +#: src/api/reference.md:653 msgid "`ENODEV`" msgstr "`ENODEV`" -#: src/api/reference.md:652 +#: src/api/reference.md:654 msgid "`ENOENT`" msgstr "`ENOENT`" -#: src/api/reference.md:653 +#: src/api/reference.md:655 msgid "`ENOEXEC`" msgstr "`ENOEXEC`" -#: src/api/reference.md:654 +#: src/api/reference.md:656 msgid "`ENOLCK`" msgstr "`ENOLCK`" -#: src/api/reference.md:655 +#: src/api/reference.md:657 msgid "`ENOLINK`" msgstr "`ENOLINK`" -#: src/api/reference.md:656 +#: src/api/reference.md:658 msgid "`ENOMEM`" msgstr "`ENOMEM`" -#: src/api/reference.md:657 +#: src/api/reference.md:659 msgid "`ENOMSG`" msgstr "`ENOMSG`" -#: src/api/reference.md:658 +#: src/api/reference.md:660 msgid "`ENOPROTOOPT`" msgstr "`ENOPROTOOPT`" -#: src/api/reference.md:659 +#: src/api/reference.md:661 msgid "`ENOSPC`" msgstr "`ENOSPC`" -#: src/api/reference.md:660 +#: src/api/reference.md:662 msgid "`ENOSR`" msgstr "`ENOSR`" -#: src/api/reference.md:661 +#: src/api/reference.md:663 msgid "`ENOSTR`" msgstr "`ENOSTR`" -#: src/api/reference.md:662 +#: src/api/reference.md:664 msgid "`ENOSYS`" msgstr "`ENOSYS`" -#: src/api/reference.md:663 +#: src/api/reference.md:665 msgid "`ENOTCONN`" msgstr "`ENOTCONN`" -#: src/api/reference.md:664 +#: src/api/reference.md:666 msgid "`ENOTDIR`" msgstr "`ENOTDIR`" -#: src/api/reference.md:665 +#: src/api/reference.md:667 msgid "`ENOTEMPTY`" msgstr "`ENOTEMPTY`" -#: src/api/reference.md:666 +#: src/api/reference.md:668 msgid "`ENOTSOCK`" msgstr "`ENOTSOCK`" -#: src/api/reference.md:667 +#: src/api/reference.md:669 msgid "`ENOTSUP`" msgstr "`ENOTSUP`" -#: src/api/reference.md:668 +#: src/api/reference.md:670 msgid "`ENOTTY`" msgstr "`ENOTTY`" -#: src/api/reference.md:669 +#: src/api/reference.md:671 msgid "`ENXIO`" msgstr "`ENXIO`" -#: src/api/reference.md:670 +#: src/api/reference.md:672 msgid "`EOPNOTSUPP`" msgstr "`EOPNOTSUPP`" -#: src/api/reference.md:671 +#: src/api/reference.md:673 msgid "`EOVERFLOW`" msgstr "`EOVERFLOW`" -#: src/api/reference.md:672 +#: src/api/reference.md:674 msgid "`EPERM`" msgstr "`EPERM`" -#: src/api/reference.md:673 +#: src/api/reference.md:675 msgid "`EPIPE`" msgstr "`EPIPE`" -#: src/api/reference.md:674 +#: src/api/reference.md:676 msgid "`EPROTO`" msgstr "`EPROTO`" -#: src/api/reference.md:675 +#: src/api/reference.md:677 msgid "`EPROTONOSUPPORT`" msgstr "`EPROTONOSUPPORT`" -#: src/api/reference.md:676 +#: src/api/reference.md:678 msgid "`EPROTOTYPE`" msgstr "`EPROTOTYPE`" -#: src/api/reference.md:677 +#: src/api/reference.md:679 msgid "`ERANGE`" msgstr "`ERANGE`" -#: src/api/reference.md:678 +#: src/api/reference.md:680 msgid "`EROFS`" msgstr "`EROFS`" -#: src/api/reference.md:679 +#: src/api/reference.md:681 msgid "`ESPIPE`" msgstr "`ESPIPE`" -#: src/api/reference.md:680 +#: src/api/reference.md:682 msgid "`ESRCH`" msgstr "`ESRCH`" -#: src/api/reference.md:681 +#: src/api/reference.md:683 msgid "`ESTALE`" msgstr "`ESTALE`" -#: src/api/reference.md:682 +#: src/api/reference.md:684 msgid "`ETIME`" msgstr "`ETIME`" -#: src/api/reference.md:683 +#: src/api/reference.md:685 msgid "`ETIMEDOUT`" msgstr "`ETIMEDOUT`" -#: src/api/reference.md:684 +#: src/api/reference.md:686 msgid "`ETXTBSY`" msgstr "`ETXTBSY`" -#: src/api/reference.md:685 +#: src/api/reference.md:687 msgid "`EWOULDBLOCK`" msgstr "`EWOULDBLOCK`" -#: src/api/reference.md:686 +#: src/api/reference.md:688 msgid "`EXDEV`" msgstr "`EXDEV`" -#: src/api/reference.md:687 +#: src/api/reference.md:689 msgid "`F_OK`" msgstr "`F_OK`" -#: src/api/reference.md:688 +#: src/api/reference.md:690 msgid "`OPENSSL_VERSION_NUMBER`" msgstr "`OPENSSL_VERSION_NUMBER`" -#: src/api/reference.md:689 +#: src/api/reference.md:691 msgid "`O_APPEND`" msgstr "`O_APPEND`" -#: src/api/reference.md:690 +#: src/api/reference.md:692 msgid "`O_CREAT`" msgstr "`O_CREAT`" -#: src/api/reference.md:691 +#: src/api/reference.md:693 msgid "`O_DIRECT`" msgstr "`O_DIRECT`" -#: src/api/reference.md:692 +#: src/api/reference.md:694 msgid "`O_DIRECTORY`" msgstr "`O_DIRECTORY`" -#: src/api/reference.md:693 +#: src/api/reference.md:695 msgid "`O_DSYNC`" msgstr "`O_DSYNC`" -#: src/api/reference.md:694 +#: src/api/reference.md:696 msgid "`O_EXCL`" msgstr "`O_EXCL`" -#: src/api/reference.md:695 +#: src/api/reference.md:697 msgid "`O_NOATIME`" msgstr "`O_NOATIME`" -#: src/api/reference.md:696 +#: src/api/reference.md:698 msgid "`O_NOCTTY`" msgstr "`O_NOCTTY`" -#: src/api/reference.md:697 +#: src/api/reference.md:699 msgid "`O_NOFOLLOW`" msgstr "`O_NOFOLLOW`" -#: src/api/reference.md:698 +#: src/api/reference.md:700 msgid "`O_NONBLOCK`" msgstr "`O_NONBLOCK`" -#: src/api/reference.md:699 +#: src/api/reference.md:701 msgid "`O_RDONLY`" msgstr "`O_RDONLY`" -#: src/api/reference.md:700 +#: src/api/reference.md:702 msgid "`O_RDWR`" msgstr "`O_RDWR`" -#: src/api/reference.md:701 +#: src/api/reference.md:703 msgid "`O_SYMLINK`" msgstr "`O_SYMLINK`" -#: src/api/reference.md:702 +#: src/api/reference.md:704 msgid "`O_SYNC`" msgstr "`O_SYNC`" -#: src/api/reference.md:703 +#: src/api/reference.md:705 msgid "`O_TRUNC`" msgstr "`O_TRUNC`" -#: src/api/reference.md:704 +#: src/api/reference.md:706 msgid "`O_WRONLY`" msgstr "`O_WRONLY`" -#: src/api/reference.md:705 +#: src/api/reference.md:707 msgid "`POINT_CONVERSION_COMPRESSED`" msgstr "`POINT_CONVERSION_COMPRESSED`" -#: src/api/reference.md:706 +#: src/api/reference.md:708 msgid "`POINT_CONVERSION_HYBRID`" msgstr "`POINT_CONVERSION_HYBRID`" -#: src/api/reference.md:707 +#: src/api/reference.md:709 msgid "`POINT_CONVERSION_UNCOMPRESSED`" msgstr "`POINT_CONVERSION_UNCOMPRESSED`" -#: src/api/reference.md:708 +#: src/api/reference.md:710 msgid "`PRIORITY_ABOVE_NORMAL`" msgstr "`PRIORITY_ABOVE_NORMAL`" -#: src/api/reference.md:709 +#: src/api/reference.md:711 msgid "`PRIORITY_BELOW_NORMAL`" msgstr "`PRIORITY_BELOW_NORMAL`" -#: src/api/reference.md:710 +#: src/api/reference.md:712 msgid "`PRIORITY_HIGH`" msgstr "`PRIORITY_HIGH`" -#: src/api/reference.md:711 +#: src/api/reference.md:713 msgid "`PRIORITY_HIGHEST`" msgstr "`PRIORITY_HIGHEST`" -#: src/api/reference.md:712 +#: src/api/reference.md:714 msgid "`PRIORITY_LOW`" msgstr "`PRIORITY_LOW`" -#: src/api/reference.md:713 +#: src/api/reference.md:715 msgid "`PRIORITY_NORMAL`" msgstr "`PRIORITY_NORMAL`" -#: src/api/reference.md:714 +#: src/api/reference.md:716 msgid "`RSA_NO_PADDING`" msgstr "`RSA_NO_PADDING`" -#: src/api/reference.md:715 +#: src/api/reference.md:717 msgid "`RSA_PKCS1_OAEP_PADDING`" msgstr "`RSA_PKCS1_OAEP_PADDING`" -#: src/api/reference.md:716 +#: src/api/reference.md:718 msgid "`RSA_PKCS1_PADDING`" msgstr "`RSA_PKCS1_PADDING`" -#: src/api/reference.md:717 +#: src/api/reference.md:719 msgid "`RSA_PKCS1_PSS_PADDING`" msgstr "`RSA_PKCS1_PSS_PADDING`" -#: src/api/reference.md:718 +#: src/api/reference.md:720 msgid "`RSA_PSS_SALTLEN_AUTO`" msgstr "`RSA_PSS_SALTLEN_AUTO`" -#: src/api/reference.md:719 +#: src/api/reference.md:721 msgid "`RSA_PSS_SALTLEN_DIGEST`" msgstr "`RSA_PSS_SALTLEN_DIGEST`" -#: src/api/reference.md:720 +#: src/api/reference.md:722 msgid "`RSA_PSS_SALTLEN_MAX_SIGN`" msgstr "`RSA_PSS_SALTLEN_MAX_SIGN`" -#: src/api/reference.md:721 +#: src/api/reference.md:723 msgid "`RSA_X931_PADDING`" msgstr "`RSA_X931_PADDING`" -#: src/api/reference.md:722 +#: src/api/reference.md:724 msgid "`RTLD_DEEPBIND`" msgstr "`RTLD_DEEPBIND`" -#: src/api/reference.md:723 +#: src/api/reference.md:725 msgid "`RTLD_GLOBAL`" msgstr "`RTLD_GLOBAL`" -#: src/api/reference.md:724 +#: src/api/reference.md:726 msgid "`RTLD_LAZY`" msgstr "`RTLD_LAZY`" -#: src/api/reference.md:725 +#: src/api/reference.md:727 msgid "`RTLD_LOCAL`" msgstr "`RTLD_LOCAL`" -#: src/api/reference.md:726 +#: src/api/reference.md:728 msgid "`RTLD_NOW`" msgstr "`RTLD_NOW`" -#: src/api/reference.md:727 +#: src/api/reference.md:729 msgid "`R_OK`" msgstr "`R_OK`" -#: src/api/reference.md:728 +#: src/api/reference.md:730 msgid "`SIGABRT`" msgstr "`SIGABRT`" -#: src/api/reference.md:729 +#: src/api/reference.md:731 msgid "`SIGALRM`" msgstr "`SIGALRM`" -#: src/api/reference.md:730 +#: src/api/reference.md:732 msgid "`SIGBUS`" msgstr "`SIGBUS`" -#: src/api/reference.md:731 +#: src/api/reference.md:733 msgid "`SIGCHLD`" msgstr "`SIGCHLD`" -#: src/api/reference.md:732 +#: src/api/reference.md:734 msgid "`SIGCONT`" msgstr "`SIGCONT`" -#: src/api/reference.md:733 +#: src/api/reference.md:735 msgid "`SIGFPE`" msgstr "`SIGFPE`" -#: src/api/reference.md:734 +#: src/api/reference.md:736 msgid "`SIGHUP`" msgstr "`SIGHUP`" -#: src/api/reference.md:735 +#: src/api/reference.md:737 msgid "`SIGILL`" msgstr "`SIGILL`" -#: src/api/reference.md:736 +#: src/api/reference.md:738 msgid "`SIGINFO`" msgstr "`SIGINFO`" -#: src/api/reference.md:737 +#: src/api/reference.md:739 msgid "`SIGINT`" msgstr "`SIGINT`" -#: src/api/reference.md:738 +#: src/api/reference.md:740 msgid "`SIGIO`" msgstr "`SIGIO`" -#: src/api/reference.md:739 +#: src/api/reference.md:741 msgid "`SIGIOT`" msgstr "`SIGIOT`" -#: src/api/reference.md:740 +#: src/api/reference.md:742 msgid "`SIGKILL`" msgstr "`SIGKILL`" -#: src/api/reference.md:741 +#: src/api/reference.md:743 msgid "`SIGPIPE`" msgstr "`SIGPIPE`" -#: src/api/reference.md:742 +#: src/api/reference.md:744 msgid "`SIGPOLL`" msgstr "`SIGPOLL`" -#: src/api/reference.md:743 +#: src/api/reference.md:745 msgid "`SIGPROF`" msgstr "`SIGPROF`" -#: src/api/reference.md:744 +#: src/api/reference.md:746 msgid "`SIGPWR`" msgstr "`SIGPWR`" -#: src/api/reference.md:745 +#: src/api/reference.md:747 msgid "`SIGQUIT`" msgstr "`SIGQUIT`" -#: src/api/reference.md:746 +#: src/api/reference.md:748 msgid "`SIGSEGV`" msgstr "`SIGSEGV`" -#: src/api/reference.md:747 +#: src/api/reference.md:749 msgid "`SIGSTKFLT`" msgstr "`SIGSTKFLT`" -#: src/api/reference.md:748 +#: src/api/reference.md:750 msgid "`SIGSTOP`" msgstr "`SIGSTOP`" -#: src/api/reference.md:749 +#: src/api/reference.md:751 msgid "`SIGSYS`" msgstr "`SIGSYS`" -#: src/api/reference.md:750 +#: src/api/reference.md:752 msgid "`SIGTERM`" msgstr "`SIGTERM`" -#: src/api/reference.md:751 +#: src/api/reference.md:753 msgid "`SIGTRAP`" msgstr "`SIGTRAP`" -#: src/api/reference.md:752 +#: src/api/reference.md:754 msgid "`SIGTSTP`" msgstr "`SIGTSTP`" -#: src/api/reference.md:753 +#: src/api/reference.md:755 msgid "`SIGTTIN`" msgstr "`SIGTTIN`" -#: src/api/reference.md:754 +#: src/api/reference.md:756 msgid "`SIGTTOU`" msgstr "`SIGTTOU`" -#: src/api/reference.md:755 +#: src/api/reference.md:757 msgid "`SIGURG`" msgstr "`SIGURG`" -#: src/api/reference.md:756 +#: src/api/reference.md:758 msgid "`SIGUSR1`" msgstr "`SIGUSR1`" -#: src/api/reference.md:757 +#: src/api/reference.md:759 msgid "`SIGUSR2`" msgstr "`SIGUSR2`" -#: src/api/reference.md:758 +#: src/api/reference.md:760 msgid "`SIGVTALRM`" msgstr "`SIGVTALRM`" -#: src/api/reference.md:759 +#: src/api/reference.md:761 msgid "`SIGWINCH`" msgstr "`SIGWINCH`" -#: src/api/reference.md:760 +#: src/api/reference.md:762 msgid "`SIGXCPU`" msgstr "`SIGXCPU`" -#: src/api/reference.md:761 +#: src/api/reference.md:763 msgid "`SIGXFSZ`" msgstr "`SIGXFSZ`" -#: src/api/reference.md:762 +#: src/api/reference.md:764 msgid "`SSL_OP_ALL`" msgstr "`SSL_OP_ALL`" -#: src/api/reference.md:763 +#: src/api/reference.md:765 msgid "`SSL_OP_ALLOW_NO_DHE_KEX`" msgstr "`SSL_OP_ALLOW_NO_DHE_KEX`" -#: src/api/reference.md:764 +#: src/api/reference.md:766 msgid "`SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION`" msgstr "`SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION`" -#: src/api/reference.md:765 +#: src/api/reference.md:767 msgid "`SSL_OP_CIPHER_SERVER_PREFERENCE`" msgstr "`SSL_OP_CIPHER_SERVER_PREFERENCE`" -#: src/api/reference.md:766 +#: src/api/reference.md:768 msgid "`SSL_OP_CISCO_ANYCONNECT`" msgstr "`SSL_OP_CISCO_ANYCONNECT`" -#: src/api/reference.md:767 +#: src/api/reference.md:769 msgid "`SSL_OP_COOKIE_EXCHANGE`" msgstr "`SSL_OP_COOKIE_EXCHANGE`" -#: src/api/reference.md:768 +#: src/api/reference.md:770 msgid "`SSL_OP_CRYPTOPRO_TLSEXT_BUG`" msgstr "`SSL_OP_CRYPTOPRO_TLSEXT_BUG`" -#: src/api/reference.md:769 +#: src/api/reference.md:771 msgid "`SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS`" msgstr "`SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS`" -#: src/api/reference.md:770 +#: src/api/reference.md:772 msgid "`SSL_OP_LEGACY_SERVER_CONNECT`" msgstr "`SSL_OP_LEGACY_SERVER_CONNECT`" -#: src/api/reference.md:771 +#: src/api/reference.md:773 msgid "`SSL_OP_NO_COMPRESSION`" msgstr "`SSL_OP_NO_COMPRESSION`" -#: src/api/reference.md:772 +#: src/api/reference.md:774 msgid "`SSL_OP_NO_ENCRYPT_THEN_MAC`" msgstr "`SSL_OP_NO_ENCRYPT_THEN_MAC`" -#: src/api/reference.md:773 +#: src/api/reference.md:775 msgid "`SSL_OP_NO_QUERY_MTU`" msgstr "`SSL_OP_NO_QUERY_MTU`" -#: src/api/reference.md:774 +#: src/api/reference.md:776 msgid "`SSL_OP_NO_RENEGOTIATION`" msgstr "`SSL_OP_NO_RENEGOTIATION`" -#: src/api/reference.md:775 +#: src/api/reference.md:777 msgid "`SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION`" msgstr "`SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION`" -#: src/api/reference.md:776 +#: src/api/reference.md:778 msgid "`SSL_OP_NO_SSLv2`" msgstr "`SSL_OP_NO_SSLv2`" -#: src/api/reference.md:777 +#: src/api/reference.md:779 msgid "`SSL_OP_NO_SSLv3`" msgstr "`SSL_OP_NO_SSLv3`" -#: src/api/reference.md:778 +#: src/api/reference.md:780 msgid "`SSL_OP_NO_TICKET`" msgstr "`SSL_OP_NO_TICKET`" -#: src/api/reference.md:779 +#: src/api/reference.md:781 msgid "`SSL_OP_NO_TLSv1`" msgstr "`SSL_OP_NO_TLSv1`" -#: src/api/reference.md:780 +#: src/api/reference.md:782 msgid "`SSL_OP_NO_TLSv1_1`" msgstr "`SSL_OP_NO_TLSv1_1`" -#: src/api/reference.md:781 +#: src/api/reference.md:783 msgid "`SSL_OP_NO_TLSv1_2`" msgstr "`SSL_OP_NO_TLSv1_2`" -#: src/api/reference.md:782 +#: src/api/reference.md:784 msgid "`SSL_OP_NO_TLSv1_3`" msgstr "`SSL_OP_NO_TLSv1_3`" -#: src/api/reference.md:783 +#: src/api/reference.md:785 msgid "`SSL_OP_PRIORITIZE_CHACHA`" msgstr "`SSL_OP_PRIORITIZE_CHACHA`" -#: src/api/reference.md:784 +#: src/api/reference.md:786 msgid "`SSL_OP_TLS_ROLLBACK_BUG`" msgstr "`SSL_OP_TLS_ROLLBACK_BUG`" -#: src/api/reference.md:785 +#: src/api/reference.md:787 msgid "`S_IFBLK`" msgstr "`S_IFBLK`" -#: src/api/reference.md:786 +#: src/api/reference.md:788 msgid "`S_IFCHR`" msgstr "`S_IFCHR`" -#: src/api/reference.md:787 +#: src/api/reference.md:789 msgid "`S_IFDIR`" msgstr "`S_IFDIR`" -#: src/api/reference.md:788 +#: src/api/reference.md:790 msgid "`S_IFIFO`" msgstr "`S_IFIFO`" -#: src/api/reference.md:789 +#: src/api/reference.md:791 msgid "`S_IFLNK`" msgstr "`S_IFLNK`" -#: src/api/reference.md:790 +#: src/api/reference.md:792 msgid "`S_IFMT`" msgstr "`S_IFMT`" -#: src/api/reference.md:791 +#: src/api/reference.md:793 msgid "`S_IFREG`" msgstr "`S_IFREG`" -#: src/api/reference.md:792 +#: src/api/reference.md:794 msgid "`S_IFSOCK`" msgstr "`S_IFSOCK`" -#: src/api/reference.md:793 +#: src/api/reference.md:795 msgid "`S_IRGRP`" msgstr "`S_IRGRP`" -#: src/api/reference.md:794 +#: src/api/reference.md:796 msgid "`S_IROTH`" msgstr "`S_IROTH`" -#: src/api/reference.md:795 +#: src/api/reference.md:797 msgid "`S_IRUSR`" msgstr "`S_IRUSR`" -#: src/api/reference.md:796 +#: src/api/reference.md:798 msgid "`S_IRWXG`" msgstr "`S_IRWXG`" -#: src/api/reference.md:797 +#: src/api/reference.md:799 msgid "`S_IRWXO`" msgstr "`S_IRWXO`" -#: src/api/reference.md:798 +#: src/api/reference.md:800 msgid "`S_IRWXU`" msgstr "`S_IRWXU`" -#: src/api/reference.md:799 +#: src/api/reference.md:801 msgid "`S_IWGRP`" msgstr "`S_IWGRP`" -#: src/api/reference.md:800 +#: src/api/reference.md:802 msgid "`S_IWOTH`" msgstr "`S_IWOTH`" -#: src/api/reference.md:801 +#: src/api/reference.md:803 msgid "`S_IWUSR`" msgstr "`S_IWUSR`" -#: src/api/reference.md:802 +#: src/api/reference.md:804 msgid "`S_IXGRP`" msgstr "`S_IXGRP`" -#: src/api/reference.md:803 +#: src/api/reference.md:805 msgid "`S_IXOTH`" msgstr "`S_IXOTH`" -#: src/api/reference.md:804 +#: src/api/reference.md:806 msgid "`S_IXUSR`" msgstr "`S_IXUSR`" -#: src/api/reference.md:805 +#: src/api/reference.md:807 msgid "`TLS1_1_VERSION`" msgstr "`TLS1_1_VERSION`" -#: src/api/reference.md:806 +#: src/api/reference.md:808 msgid "`TLS1_2_VERSION`" msgstr "`TLS1_2_VERSION`" -#: src/api/reference.md:807 +#: src/api/reference.md:809 msgid "`TLS1_3_VERSION`" msgstr "`TLS1_3_VERSION`" -#: src/api/reference.md:808 +#: src/api/reference.md:810 msgid "`TLS1_VERSION`" msgstr "`TLS1_VERSION`" -#: src/api/reference.md:809 +#: src/api/reference.md:811 msgid "`UV_DIRENT_BLOCK`" msgstr "`UV_DIRENT_BLOCK`" -#: src/api/reference.md:810 +#: src/api/reference.md:812 msgid "`UV_DIRENT_CHAR`" msgstr "`UV_DIRENT_CHAR`" -#: src/api/reference.md:811 +#: src/api/reference.md:813 msgid "`UV_DIRENT_DIR`" msgstr "`UV_DIRENT_DIR`" -#: src/api/reference.md:812 +#: src/api/reference.md:814 msgid "`UV_DIRENT_FIFO`" msgstr "`UV_DIRENT_FIFO`" -#: src/api/reference.md:813 +#: src/api/reference.md:815 msgid "`UV_DIRENT_FILE`" msgstr "`UV_DIRENT_FILE`" -#: src/api/reference.md:814 +#: src/api/reference.md:816 msgid "`UV_DIRENT_LINK`" msgstr "`UV_DIRENT_LINK`" -#: src/api/reference.md:815 +#: src/api/reference.md:817 msgid "`UV_DIRENT_SOCKET`" msgstr "`UV_DIRENT_SOCKET`" -#: src/api/reference.md:816 +#: src/api/reference.md:818 msgid "`UV_DIRENT_UNKNOWN`" msgstr "`UV_DIRENT_UNKNOWN`" -#: src/api/reference.md:817 +#: src/api/reference.md:819 msgid "`UV_FS_COPYFILE_EXCL`" msgstr "`UV_FS_COPYFILE_EXCL`" -#: src/api/reference.md:818 +#: src/api/reference.md:820 msgid "`UV_FS_COPYFILE_FICLONE`" msgstr "`UV_FS_COPYFILE_FICLONE`" -#: src/api/reference.md:819 +#: src/api/reference.md:821 msgid "`UV_FS_COPYFILE_FICLONE_FORCE`" msgstr "`UV_FS_COPYFILE_FICLONE_FORCE`" -#: src/api/reference.md:820 +#: src/api/reference.md:822 msgid "`UV_FS_O_FILEMAP`" msgstr "`UV_FS_O_FILEMAP`" -#: src/api/reference.md:821 +#: src/api/reference.md:823 msgid "`UV_FS_SYMLINK_DIR`" msgstr "`UV_FS_SYMLINK_DIR`" -#: src/api/reference.md:822 +#: src/api/reference.md:824 msgid "`UV_FS_SYMLINK_JUNCTION`" msgstr "`UV_FS_SYMLINK_JUNCTION`" -#: src/api/reference.md:823 +#: src/api/reference.md:825 msgid "`W_OK`" msgstr "`W_OK`" -#: src/api/reference.md:824 +#: src/api/reference.md:826 msgid "`X_OK`" msgstr "`X_OK`" -#: src/api/reference.md:826 +#: src/api/reference.md:828 msgid "`defaultCoreCipherList`" msgstr "`defaultCoreCipherList`" -#: src/api/reference.md:828 src/cli/capabilities.md:52 +#: src/api/reference.md:830 src/cli/capabilities.md:52 msgid "`crypto`" msgstr "`crypto`" -#: src/api/reference.md:832 +#: src/api/reference.md:834 msgid "`Cipheriv`" msgstr "`Cipheriv`" -#: src/api/reference.md:833 +#: src/api/reference.md:835 msgid "`Decipheriv`" msgstr "`Decipheriv`" -#: src/api/reference.md:834 +#: src/api/reference.md:836 msgid "`DiffieHellman`" msgstr "`DiffieHellman`" -#: src/api/reference.md:835 +#: src/api/reference.md:837 msgid "`DiffieHellmanGroup`" msgstr "`DiffieHellmanGroup`" -#: src/api/reference.md:836 +#: src/api/reference.md:838 msgid "`ECDH`" msgstr "`ECDH`" -#: src/api/reference.md:837 +#: src/api/reference.md:839 msgid "`KeyObject`" msgstr "`KeyObject`" -#: src/api/reference.md:838 +#: src/api/reference.md:840 msgid "`X509Certificate`" msgstr "`X509Certificate`" -#: src/api/reference.md:842 +#: src/api/reference.md:844 msgid "`Hash` — module" msgstr "`Hash` — módulo" -#: src/api/reference.md:843 +#: src/api/reference.md:845 msgid "`Hmac` — module" msgstr "`Hmac` — módulo" -#: src/api/reference.md:844 +#: src/api/reference.md:846 msgid "`Sign` — module" msgstr "`Sign` — módulo" -#: src/api/reference.md:845 +#: src/api/reference.md:847 msgid "`Verify` — module" msgstr "`Verify` — módulo" -#: src/api/reference.md:846 +#: src/api/reference.md:848 msgid "`argon2` — module" msgstr "`argon2` — módulo" -#: src/api/reference.md:847 +#: src/api/reference.md:849 msgid "`argon2Sync` — module" msgstr "`argon2Sync` — módulo" -#: src/api/reference.md:848 +#: src/api/reference.md:850 msgid "`checkPrime` — module" msgstr "`checkPrime` — módulo" -#: src/api/reference.md:849 +#: src/api/reference.md:851 msgid "`checkPrimeSync` — module" msgstr "`checkPrimeSync` — módulo" -#: src/api/reference.md:850 +#: src/api/reference.md:852 msgid "`createCipheriv` — module" msgstr "`createCipheriv` — módulo" -#: src/api/reference.md:851 +#: src/api/reference.md:853 msgid "`createDecipheriv` — module" msgstr "`createDecipheriv` — módulo" -#: src/api/reference.md:852 +#: src/api/reference.md:854 msgid "`createDiffieHellman` — module" msgstr "`createDiffieHellman` — módulo" -#: src/api/reference.md:853 +#: src/api/reference.md:855 msgid "`createDiffieHellmanGroup` — module" msgstr "`createDiffieHellmanGroup` — módulo" -#: src/api/reference.md:854 +#: src/api/reference.md:856 msgid "`createECDH` — module" msgstr "`createECDH` — módulo" -#: src/api/reference.md:855 +#: src/api/reference.md:857 msgid "`createHash` — module" msgstr "`createHash` — módulo" -#: src/api/reference.md:856 +#: src/api/reference.md:858 msgid "`createHmac` — module" msgstr "`createHmac` — módulo" -#: src/api/reference.md:857 +#: src/api/reference.md:859 msgid "`createPrivateKey` — module" msgstr "`createPrivateKey` — módulo" -#: src/api/reference.md:858 +#: src/api/reference.md:860 msgid "`createPublicKey` — module" msgstr "`createPublicKey` — módulo" -#: src/api/reference.md:859 +#: src/api/reference.md:861 msgid "`createSecretKey` — module" msgstr "`createSecretKey` — módulo" -#: src/api/reference.md:860 src/api/reference.md:861 +#: src/api/reference.md:862 src/api/reference.md:863 msgid "`createSign` — module" msgstr "`createSign` — módulo" -#: src/api/reference.md:862 src/api/reference.md:863 +#: src/api/reference.md:864 src/api/reference.md:865 msgid "`createVerify` — module" msgstr "`createVerify` — módulo" -#: src/api/reference.md:864 +#: src/api/reference.md:866 msgid "`decapsulate` — module" msgstr "`decapsulate` — módulo" -#: src/api/reference.md:865 +#: src/api/reference.md:867 msgid "`diffieHellman` — module" msgstr "`diffieHellman` — módulo" -#: src/api/reference.md:866 +#: src/api/reference.md:868 msgid "`encapsulate` — module" msgstr "`encapsulate` — módulo" -#: src/api/reference.md:867 +#: src/api/reference.md:869 msgid "`generateKey` — module" msgstr "`generateKey` — módulo" -#: src/api/reference.md:868 +#: src/api/reference.md:870 msgid "`generateKeyPair` — module" msgstr "`generateKeyPair` — módulo" -#: src/api/reference.md:869 src/api/reference.md:870 +#: src/api/reference.md:871 src/api/reference.md:872 msgid "`generateKeyPairSync` — module" msgstr "`generateKeyPairSync` — módulo" -#: src/api/reference.md:871 +#: src/api/reference.md:873 msgid "`generateKeySync` — module" msgstr "`generateKeySync` — módulo" -#: src/api/reference.md:872 +#: src/api/reference.md:874 msgid "`generatePrime` — module" msgstr "`generatePrime` — módulo" -#: src/api/reference.md:873 +#: src/api/reference.md:875 msgid "`generatePrimeSync` — module" msgstr "`generatePrimeSync` — módulo" -#: src/api/reference.md:874 +#: src/api/reference.md:876 msgid "`getCipherInfo` — module" msgstr "`getCipherInfo` — módulo" -#: src/api/reference.md:875 src/api/reference.md:3312 +#: src/api/reference.md:877 src/api/reference.md:3321 msgid "`getCiphers` — module" msgstr "`getCiphers` — módulo" -#: src/api/reference.md:876 +#: src/api/reference.md:878 msgid "`getCurves` — module" msgstr "`getCurves` — módulo" -#: src/api/reference.md:877 +#: src/api/reference.md:879 msgid "`getDiffieHellman` — module" msgstr "`getDiffieHellman` — módulo" -#: src/api/reference.md:878 +#: src/api/reference.md:880 msgid "`getFips` — module" msgstr "`getFips` — módulo" -#: src/api/reference.md:879 +#: src/api/reference.md:881 msgid "`getHashes` — module" msgstr "`getHashes` — módulo" -#: src/api/reference.md:880 +#: src/api/reference.md:882 msgid "`getRandomValues` — module" msgstr "`getRandomValues` — módulo" -#: src/api/reference.md:882 +#: src/api/reference.md:884 msgid "`hkdf` — module" msgstr "`hkdf` — módulo" -#: src/api/reference.md:883 +#: src/api/reference.md:885 msgid "`hkdfSync` — module" msgstr "`hkdfSync` — módulo" -#: src/api/reference.md:884 +#: src/api/reference.md:886 msgid "`pbkdf2` — module" msgstr "`pbkdf2` — módulo" -#: src/api/reference.md:885 +#: src/api/reference.md:887 msgid "`pbkdf2Sync` — module" msgstr "`pbkdf2Sync` — módulo" -#: src/api/reference.md:886 +#: src/api/reference.md:888 msgid "`privateDecrypt` — module" msgstr "`privateDecrypt` — módulo" -#: src/api/reference.md:887 +#: src/api/reference.md:889 msgid "`privateEncrypt` — module" msgstr "`privateEncrypt` — módulo" -#: src/api/reference.md:888 +#: src/api/reference.md:890 msgid "`publicDecrypt` — module" msgstr "`publicDecrypt` — módulo" -#: src/api/reference.md:889 +#: src/api/reference.md:891 msgid "`publicEncrypt` — module" msgstr "`publicEncrypt` — módulo" -#: src/api/reference.md:890 +#: src/api/reference.md:892 msgid "`randomBytes` — module" msgstr "`randomBytes` — módulo" -#: src/api/reference.md:891 +#: src/api/reference.md:893 msgid "`randomFill` — module" msgstr "`randomFill` — módulo" -#: src/api/reference.md:892 +#: src/api/reference.md:894 msgid "`randomFillSync` — module" msgstr "`randomFillSync` — módulo" -#: src/api/reference.md:893 src/api/reference.md:894 +#: src/api/reference.md:895 src/api/reference.md:896 msgid "`randomInt` — module" msgstr "`randomInt` — módulo" -#: src/api/reference.md:895 +#: src/api/reference.md:897 msgid "`randomUUID` — module" msgstr "`randomUUID` — módulo" -#: src/api/reference.md:896 +#: src/api/reference.md:898 msgid "`scrypt` — module" msgstr "`scrypt` — módulo" -#: src/api/reference.md:897 +#: src/api/reference.md:899 msgid "`scryptSync` — module" msgstr "`scryptSync` — módulo" -#: src/api/reference.md:898 +#: src/api/reference.md:900 msgid "`secureHeapUsed` — module" msgstr "`secureHeapUsed` — módulo" -#: src/api/reference.md:899 +#: src/api/reference.md:901 msgid "`setFips` — module" msgstr "`setFips` — módulo" -#: src/api/reference.md:900 +#: src/api/reference.md:902 msgid "`sign` — module" msgstr "`sign` — módulo" -#: src/api/reference.md:901 +#: src/api/reference.md:903 msgid "`timingSafeEqual` — module" msgstr "`timingSafeEqual` — módulo" -#: src/api/reference.md:906 +#: src/api/reference.md:908 msgid "`Certificate`" msgstr "`Certificate`" -#: src/api/reference.md:908 +#: src/api/reference.md:910 msgid "`subtle`" msgstr "`subtle`" -#: src/api/reference.md:909 +#: src/api/reference.md:911 msgid "`webcrypto`" msgstr "`webcrypto`" -#: src/api/reference.md:911 +#: src/api/reference.md:913 msgid "`dgram`" msgstr "`dgram`" -#: src/api/reference.md:915 src/api/reference.md:1863 +#: src/api/reference.md:917 src/api/reference.md:1865 msgid "`Socket`" msgstr "`Socket`" -#: src/api/reference.md:919 src/api/reference.md:1871 +#: src/api/reference.md:921 src/api/reference.md:1873 msgid "`Socket` — module" msgstr "`Socket` — módulo" -#: src/api/reference.md:920 src/api/reference.md:1881 +#: src/api/reference.md:922 src/api/reference.md:1883 msgid "`addListener` — instance _(class: `Socket`)_" msgstr "`addListener` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:921 +#: src/api/reference.md:923 msgid "`addMembership` — instance _(class: `Socket`)_" msgstr "`addMembership` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:922 +#: src/api/reference.md:924 msgid "`addSourceSpecificMembership` — instance _(class: `Socket`)_" msgstr "`addSourceSpecificMembership` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:923 src/api/reference.md:1885 +#: src/api/reference.md:925 src/api/reference.md:1887 msgid "`address` — instance _(class: `Socket`)_" msgstr "`address` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:924 +#: src/api/reference.md:926 msgid "`bind` — instance _(class: `Socket`)_" msgstr "`bind` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:925 +#: src/api/reference.md:927 msgid "`close` — instance _(class: `Socket`)_" msgstr "`close` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:926 src/api/reference.md:1895 +#: src/api/reference.md:928 src/api/reference.md:1897 msgid "`connect` — instance _(class: `Socket`)_" msgstr "`connect` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:927 +#: src/api/reference.md:929 msgid "`createSocket` — module" msgstr "`createSocket` — módulo" -#: src/api/reference.md:928 +#: src/api/reference.md:930 msgid "`disconnect` — instance _(class: `Socket`)_" msgstr "`disconnect` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:929 +#: src/api/reference.md:931 msgid "`dropMembership` — instance _(class: `Socket`)_" msgstr "`dropMembership` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:930 +#: src/api/reference.md:932 msgid "`dropSourceSpecificMembership` — instance _(class: `Socket`)_" msgstr "`dropSourceSpecificMembership` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:931 +#: src/api/reference.md:933 msgid "`emit` — instance _(class: `Socket`)_" msgstr "`emit` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:932 src/api/reference.md:1904 +#: src/api/reference.md:934 src/api/reference.md:1906 msgid "`eventNames` — instance _(class: `Socket`)_" msgstr "`eventNames` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:933 +#: src/api/reference.md:935 msgid "`getRecvBufferSize` — instance _(class: `Socket`)_" msgstr "`getRecvBufferSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:934 +#: src/api/reference.md:936 msgid "`getSendBufferSize` — instance _(class: `Socket`)_" msgstr "`getSendBufferSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:935 +#: src/api/reference.md:937 msgid "`getSendQueueCount` — instance _(class: `Socket`)_" msgstr "`getSendQueueCount` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:936 +#: src/api/reference.md:938 msgid "`getSendQueueSize` — instance _(class: `Socket`)_" msgstr "`getSendQueueSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:937 src/api/reference.md:1932 +#: src/api/reference.md:939 src/api/reference.md:1934 msgid "`listenerCount` — instance _(class: `Socket`)_" msgstr "`listenerCount` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:938 src/api/reference.md:1941 +#: src/api/reference.md:940 src/api/reference.md:1943 msgid "`off` — instance _(class: `Socket`)_" msgstr "`off` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:939 src/api/reference.md:1943 +#: src/api/reference.md:941 src/api/reference.md:1945 msgid "`on` — instance _(class: `Socket`)_" msgstr "`on` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:940 src/api/reference.md:1944 +#: src/api/reference.md:942 src/api/reference.md:1946 msgid "`once` — instance _(class: `Socket`)_" msgstr "`once` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:941 src/api/reference.md:1959 +#: src/api/reference.md:943 src/api/reference.md:1961 msgid "`ref` — instance _(class: `Socket`)_" msgstr "`ref` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:942 src/api/reference.md:1960 +#: src/api/reference.md:944 src/api/reference.md:1962 msgid "`remoteAddress` — instance _(class: `Socket`)_" msgstr "`remoteAddress` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:943 src/api/reference.md:1965 +#: src/api/reference.md:945 src/api/reference.md:1967 msgid "`removeListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:944 +#: src/api/reference.md:946 msgid "`send` — instance _(class: `Socket`)_" msgstr "`send` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:945 +#: src/api/reference.md:947 msgid "`sendto` — instance _(class: `Socket`)_" msgstr "`sendto` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:946 +#: src/api/reference.md:948 msgid "`setBroadcast` — instance _(class: `Socket`)_" msgstr "`setBroadcast` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:947 +#: src/api/reference.md:949 msgid "`setMulticastInterface` — instance _(class: `Socket`)_" msgstr "`setMulticastInterface` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:948 +#: src/api/reference.md:950 msgid "`setMulticastLoopback` — instance _(class: `Socket`)_" msgstr "`setMulticastLoopback` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:949 +#: src/api/reference.md:951 msgid "`setMulticastTTL` — instance _(class: `Socket`)_" msgstr "`setMulticastTTL` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:950 +#: src/api/reference.md:952 msgid "`setRecvBufferSize` — instance _(class: `Socket`)_" msgstr "`setRecvBufferSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:951 +#: src/api/reference.md:953 msgid "`setSendBufferSize` — instance _(class: `Socket`)_" msgstr "`setSendBufferSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:952 +#: src/api/reference.md:954 msgid "`setTTL` — instance _(class: `Socket`)_" msgstr "`setTTL` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:953 src/api/reference.md:1984 +#: src/api/reference.md:955 src/api/reference.md:1986 msgid "`unref` — instance _(class: `Socket`)_" msgstr "`unref` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:959 +#: src/api/reference.md:961 msgid "`diagnostics_channel`" msgstr "`diagnostics_channel`" -#: src/api/reference.md:963 +#: src/api/reference.md:965 msgid "`BoundedChannel`" msgstr "`BoundedChannel`" -#: src/api/reference.md:964 +#: src/api/reference.md:966 msgid "`Channel`" msgstr "`Channel`" -#: src/api/reference.md:968 +#: src/api/reference.md:970 msgid "`boundedChannel` — module" msgstr "`boundedChannel` — módulo" -#: src/api/reference.md:969 +#: src/api/reference.md:971 msgid "`channel` — module" msgstr "`channel` — módulo" -#: src/api/reference.md:970 +#: src/api/reference.md:972 msgid "`hasSubscribers` — module" msgstr "`hasSubscribers` — módulo" -#: src/api/reference.md:972 +#: src/api/reference.md:974 msgid "`tracingChannel` — module" msgstr "`tracingChannel` — módulo" -#: src/api/reference.md:979 +#: src/api/reference.md:981 msgid "`dns`" msgstr "`dns`" -#: src/api/reference.md:983 src/api/reference.md:1092 +#: src/api/reference.md:985 src/api/reference.md:1094 msgid "`Resolver`" msgstr "`Resolver`" -#: src/api/reference.md:987 src/api/reference.md:1096 +#: src/api/reference.md:989 src/api/reference.md:1098 msgid "`Resolver` — module" msgstr "`Resolver` — módulo" -#: src/api/reference.md:988 src/api/reference.md:1097 +#: src/api/reference.md:990 src/api/reference.md:1099 msgid "`cancel` — instance _(class: `Resolver`)_" msgstr "`cancel` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:989 src/api/reference.md:1098 +#: src/api/reference.md:991 src/api/reference.md:1100 msgid "`getDefaultResultOrder` — module" msgstr "`getDefaultResultOrder` — módulo" -#: src/api/reference.md:990 src/api/reference.md:1099 +#: src/api/reference.md:992 src/api/reference.md:1101 msgid "`getServers` — module" msgstr "`getServers` — módulo" -#: src/api/reference.md:991 src/api/reference.md:1100 +#: src/api/reference.md:993 src/api/reference.md:1102 msgid "`getServers` — instance _(class: `Resolver`)_" msgstr "`getServers` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:992 src/api/reference.md:1101 +#: src/api/reference.md:994 src/api/reference.md:1103 msgid "`lookup` — module" msgstr "`lookup` — módulo" -#: src/api/reference.md:993 src/api/reference.md:1102 +#: src/api/reference.md:995 src/api/reference.md:1104 msgid "`lookupService` — module" msgstr "`lookupService` — módulo" -#: src/api/reference.md:994 src/api/reference.md:1103 src/api/reference.md:2070 -#: src/api/reference.md:2096 src/api/reference.md:2122 -#: src/api/reference.md:3416 +#: src/api/reference.md:996 src/api/reference.md:1105 src/api/reference.md:2072 +#: src/api/reference.md:2098 src/api/reference.md:2124 +#: src/api/reference.md:3436 msgid "`resolve` — module" msgstr "`resolve` — módulo" -#: src/api/reference.md:995 src/api/reference.md:1104 +#: src/api/reference.md:997 src/api/reference.md:1106 msgid "`resolve` — instance _(class: `Resolver`)_" msgstr "`resolve` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:996 src/api/reference.md:1105 +#: src/api/reference.md:998 src/api/reference.md:1107 msgid "`resolve4` — module" msgstr "`resolve4` — módulo" -#: src/api/reference.md:997 src/api/reference.md:1106 +#: src/api/reference.md:999 src/api/reference.md:1108 msgid "`resolve4` — instance _(class: `Resolver`)_" msgstr "`resolve4` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:998 src/api/reference.md:1107 +#: src/api/reference.md:1000 src/api/reference.md:1109 msgid "`resolve6` — module" msgstr "`resolve6` — módulo" -#: src/api/reference.md:999 src/api/reference.md:1108 +#: src/api/reference.md:1001 src/api/reference.md:1110 msgid "`resolve6` — instance _(class: `Resolver`)_" msgstr "`resolve6` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1000 src/api/reference.md:1109 +#: src/api/reference.md:1002 src/api/reference.md:1111 msgid "`resolveAny` — module" msgstr "`resolveAny` — módulo" -#: src/api/reference.md:1001 src/api/reference.md:1110 +#: src/api/reference.md:1003 src/api/reference.md:1112 msgid "`resolveAny` — instance _(class: `Resolver`)_" msgstr "`resolveAny` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1002 src/api/reference.md:1111 +#: src/api/reference.md:1004 src/api/reference.md:1113 msgid "`resolveCaa` — module" msgstr "`resolveCaa` — módulo" -#: src/api/reference.md:1003 src/api/reference.md:1112 +#: src/api/reference.md:1005 src/api/reference.md:1114 msgid "`resolveCaa` — instance _(class: `Resolver`)_" msgstr "`resolveCaa` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1004 src/api/reference.md:1113 +#: src/api/reference.md:1006 src/api/reference.md:1115 msgid "`resolveCname` — module" msgstr "`resolveCname` — módulo" -#: src/api/reference.md:1005 src/api/reference.md:1114 +#: src/api/reference.md:1007 src/api/reference.md:1116 msgid "`resolveCname` — instance _(class: `Resolver`)_" msgstr "`resolveCname` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1006 src/api/reference.md:1115 +#: src/api/reference.md:1008 src/api/reference.md:1117 msgid "`resolveMx` — module" msgstr "`resolveMx` — módulo" -#: src/api/reference.md:1007 src/api/reference.md:1116 +#: src/api/reference.md:1009 src/api/reference.md:1118 msgid "`resolveMx` — instance _(class: `Resolver`)_" msgstr "`resolveMx` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1008 src/api/reference.md:1117 +#: src/api/reference.md:1010 src/api/reference.md:1119 msgid "`resolveNaptr` — module" msgstr "`resolveNaptr` — módulo" -#: src/api/reference.md:1009 src/api/reference.md:1118 +#: src/api/reference.md:1011 src/api/reference.md:1120 msgid "`resolveNaptr` — instance _(class: `Resolver`)_" msgstr "`resolveNaptr` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1010 src/api/reference.md:1119 +#: src/api/reference.md:1012 src/api/reference.md:1121 msgid "`resolveNs` — module" msgstr "`resolveNs` — módulo" -#: src/api/reference.md:1011 src/api/reference.md:1120 +#: src/api/reference.md:1013 src/api/reference.md:1122 msgid "`resolveNs` — instance _(class: `Resolver`)_" msgstr "`resolveNs` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1012 src/api/reference.md:1121 +#: src/api/reference.md:1014 src/api/reference.md:1123 msgid "`resolvePtr` — module" msgstr "`resolvePtr` — módulo" -#: src/api/reference.md:1013 src/api/reference.md:1122 +#: src/api/reference.md:1015 src/api/reference.md:1124 msgid "`resolvePtr` — instance _(class: `Resolver`)_" msgstr "`resolvePtr` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1014 src/api/reference.md:1123 +#: src/api/reference.md:1016 src/api/reference.md:1125 msgid "`resolveSoa` — module" msgstr "`resolveSoa` — módulo" -#: src/api/reference.md:1015 src/api/reference.md:1124 +#: src/api/reference.md:1017 src/api/reference.md:1126 msgid "`resolveSoa` — instance _(class: `Resolver`)_" msgstr "`resolveSoa` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1016 src/api/reference.md:1125 +#: src/api/reference.md:1018 src/api/reference.md:1127 msgid "`resolveSrv` — module" msgstr "`resolveSrv` — módulo" -#: src/api/reference.md:1017 src/api/reference.md:1126 +#: src/api/reference.md:1019 src/api/reference.md:1128 msgid "`resolveSrv` — instance _(class: `Resolver`)_" msgstr "`resolveSrv` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1018 src/api/reference.md:1127 +#: src/api/reference.md:1020 src/api/reference.md:1129 msgid "`resolveTlsa` — module" msgstr "`resolveTlsa` — módulo" -#: src/api/reference.md:1019 src/api/reference.md:1128 +#: src/api/reference.md:1021 src/api/reference.md:1130 msgid "`resolveTlsa` — instance _(class: `Resolver`)_" msgstr "`resolveTlsa` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1020 src/api/reference.md:1129 +#: src/api/reference.md:1022 src/api/reference.md:1131 msgid "`resolveTxt` — module" msgstr "`resolveTxt` — módulo" -#: src/api/reference.md:1021 src/api/reference.md:1130 +#: src/api/reference.md:1023 src/api/reference.md:1132 msgid "`resolveTxt` — instance _(class: `Resolver`)_" msgstr "`resolveTxt` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1022 src/api/reference.md:1131 -#: src/api/reference.md:1803 +#: src/api/reference.md:1024 src/api/reference.md:1133 +#: src/api/reference.md:1805 msgid "`reverse` — module" msgstr "`reverse` — módulo" -#: src/api/reference.md:1023 src/api/reference.md:1132 +#: src/api/reference.md:1025 src/api/reference.md:1134 msgid "`reverse` — instance _(class: `Resolver`)_" msgstr "`reverse` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1024 src/api/reference.md:1133 +#: src/api/reference.md:1026 src/api/reference.md:1135 msgid "`setDefaultResultOrder` — module" msgstr "`setDefaultResultOrder` — módulo" -#: src/api/reference.md:1025 src/api/reference.md:1134 +#: src/api/reference.md:1027 src/api/reference.md:1136 msgid "`setLocalAddress` — instance _(class: `Resolver`)_" msgstr "`setLocalAddress` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1026 src/api/reference.md:1135 +#: src/api/reference.md:1028 src/api/reference.md:1137 msgid "`setServers` — module" msgstr "`setServers` — módulo" -#: src/api/reference.md:1027 src/api/reference.md:1136 +#: src/api/reference.md:1029 src/api/reference.md:1138 msgid "`setServers` — instance _(class: `Resolver`)_" msgstr "`setServers` — instancia _(clase: `Resolver`)_" -#: src/api/reference.md:1031 src/api/reference.md:1032 +#: src/api/reference.md:1033 src/api/reference.md:1034 msgid "`ADDRCONFIG`" msgstr "`ADDRCONFIG`" -#: src/api/reference.md:1033 src/api/reference.md:1034 -#: src/api/reference.md:1140 +#: src/api/reference.md:1035 src/api/reference.md:1036 +#: src/api/reference.md:1142 msgid "`ADDRGETNETWORKPARAMS`" msgstr "`ADDRGETNETWORKPARAMS`" -#: src/api/reference.md:1035 src/api/reference.md:1036 +#: src/api/reference.md:1037 src/api/reference.md:1038 msgid "`ALL`" msgstr "`ALL`" -#: src/api/reference.md:1037 src/api/reference.md:1038 -#: src/api/reference.md:1141 +#: src/api/reference.md:1039 src/api/reference.md:1040 +#: src/api/reference.md:1143 msgid "`BADFAMILY`" msgstr "`BADFAMILY`" -#: src/api/reference.md:1039 src/api/reference.md:1040 -#: src/api/reference.md:1142 +#: src/api/reference.md:1041 src/api/reference.md:1042 +#: src/api/reference.md:1144 msgid "`BADFLAGS`" msgstr "`BADFLAGS`" -#: src/api/reference.md:1041 src/api/reference.md:1042 -#: src/api/reference.md:1143 +#: src/api/reference.md:1043 src/api/reference.md:1044 +#: src/api/reference.md:1145 msgid "`BADHINTS`" msgstr "`BADHINTS`" -#: src/api/reference.md:1043 src/api/reference.md:1044 -#: src/api/reference.md:1144 +#: src/api/reference.md:1045 src/api/reference.md:1046 +#: src/api/reference.md:1146 msgid "`BADNAME`" msgstr "`BADNAME`" -#: src/api/reference.md:1045 src/api/reference.md:1046 -#: src/api/reference.md:1145 +#: src/api/reference.md:1047 src/api/reference.md:1048 +#: src/api/reference.md:1147 msgid "`BADQUERY`" msgstr "`BADQUERY`" -#: src/api/reference.md:1047 src/api/reference.md:1048 -#: src/api/reference.md:1146 +#: src/api/reference.md:1049 src/api/reference.md:1050 +#: src/api/reference.md:1148 msgid "`BADRESP`" msgstr "`BADRESP`" -#: src/api/reference.md:1049 src/api/reference.md:1050 -#: src/api/reference.md:1147 +#: src/api/reference.md:1051 src/api/reference.md:1052 +#: src/api/reference.md:1149 msgid "`BADSTR`" msgstr "`BADSTR`" -#: src/api/reference.md:1051 src/api/reference.md:1052 -#: src/api/reference.md:1148 +#: src/api/reference.md:1053 src/api/reference.md:1054 +#: src/api/reference.md:1150 msgid "`CANCELLED`" msgstr "`CANCELLED`" -#: src/api/reference.md:1053 src/api/reference.md:1054 -#: src/api/reference.md:1149 +#: src/api/reference.md:1055 src/api/reference.md:1056 +#: src/api/reference.md:1151 msgid "`CONNREFUSED`" msgstr "`CONNREFUSED`" -#: src/api/reference.md:1055 src/api/reference.md:1056 -#: src/api/reference.md:1150 +#: src/api/reference.md:1057 src/api/reference.md:1058 +#: src/api/reference.md:1152 msgid "`DESTRUCTION`" msgstr "`DESTRUCTION`" -#: src/api/reference.md:1057 src/api/reference.md:1058 -#: src/api/reference.md:1151 +#: src/api/reference.md:1059 src/api/reference.md:1060 +#: src/api/reference.md:1153 msgid "`EOF`" msgstr "`EOF`" -#: src/api/reference.md:1059 src/api/reference.md:1060 -#: src/api/reference.md:1152 +#: src/api/reference.md:1061 src/api/reference.md:1062 +#: src/api/reference.md:1154 msgid "`FILE`" msgstr "`FILE`" -#: src/api/reference.md:1061 src/api/reference.md:1062 -#: src/api/reference.md:1153 +#: src/api/reference.md:1063 src/api/reference.md:1064 +#: src/api/reference.md:1155 msgid "`FORMERR`" msgstr "`FORMERR`" -#: src/api/reference.md:1063 src/api/reference.md:1064 -#: src/api/reference.md:1154 +#: src/api/reference.md:1065 src/api/reference.md:1066 +#: src/api/reference.md:1156 msgid "`LOADIPHLPAPI`" msgstr "`LOADIPHLPAPI`" -#: src/api/reference.md:1065 src/api/reference.md:1066 -#: src/api/reference.md:1155 +#: src/api/reference.md:1067 src/api/reference.md:1068 +#: src/api/reference.md:1157 msgid "`NODATA`" msgstr "`NODATA`" -#: src/api/reference.md:1067 src/api/reference.md:1068 -#: src/api/reference.md:1156 +#: src/api/reference.md:1069 src/api/reference.md:1070 +#: src/api/reference.md:1158 msgid "`NOMEM`" msgstr "`NOMEM`" -#: src/api/reference.md:1069 src/api/reference.md:1070 -#: src/api/reference.md:1157 +#: src/api/reference.md:1071 src/api/reference.md:1072 +#: src/api/reference.md:1159 msgid "`NONAME`" msgstr "`NONAME`" -#: src/api/reference.md:1071 src/api/reference.md:1072 -#: src/api/reference.md:1158 +#: src/api/reference.md:1073 src/api/reference.md:1074 +#: src/api/reference.md:1160 msgid "`NOTFOUND`" msgstr "`NOTFOUND`" -#: src/api/reference.md:1073 src/api/reference.md:1074 -#: src/api/reference.md:1159 +#: src/api/reference.md:1075 src/api/reference.md:1076 +#: src/api/reference.md:1161 msgid "`NOTIMP`" msgstr "`NOTIMP`" -#: src/api/reference.md:1075 src/api/reference.md:1076 -#: src/api/reference.md:1160 +#: src/api/reference.md:1077 src/api/reference.md:1078 +#: src/api/reference.md:1162 msgid "`NOTINITIALIZED`" msgstr "`NOTINITIALIZED`" -#: src/api/reference.md:1077 src/api/reference.md:1078 -#: src/api/reference.md:1161 +#: src/api/reference.md:1079 src/api/reference.md:1080 +#: src/api/reference.md:1163 msgid "`REFUSED`" msgstr "`REFUSED`" -#: src/api/reference.md:1079 src/api/reference.md:1080 -#: src/api/reference.md:1162 +#: src/api/reference.md:1081 src/api/reference.md:1082 +#: src/api/reference.md:1164 msgid "`SERVFAIL`" msgstr "`SERVFAIL`" -#: src/api/reference.md:1081 src/api/reference.md:1082 -#: src/api/reference.md:1163 +#: src/api/reference.md:1083 src/api/reference.md:1084 +#: src/api/reference.md:1165 msgid "`TIMEOUT`" msgstr "`TIMEOUT`" -#: src/api/reference.md:1083 src/api/reference.md:1084 +#: src/api/reference.md:1085 src/api/reference.md:1086 msgid "`V4MAPPED`" msgstr "`V4MAPPED`" -#: src/api/reference.md:1086 src/api/reference.md:1396 -#: src/api/reference.md:3086 src/api/reference.md:3087 -#: src/api/reference.md:3276 +#: src/api/reference.md:1088 src/api/reference.md:1398 +#: src/api/reference.md:2865 src/api/reference.md:3095 +#: src/api/reference.md:3096 src/api/reference.md:3285 msgid "`promises`" msgstr "`promises`" -#: src/api/reference.md:1088 +#: src/api/reference.md:1090 msgid "`dns/promises`" msgstr "`dns/promises`" -#: src/api/reference.md:1166 src/api/reference.md:2783 +#: src/api/reference.md:1168 src/api/reference.md:2788 msgid "`domain`" msgstr "`domain`" -#: src/api/reference.md:1170 +#: src/api/reference.md:1172 msgid "`Domain`" msgstr "`Domain`" -#: src/api/reference.md:1174 +#: src/api/reference.md:1176 msgid "`Domain` — module" msgstr "`Domain` — módulo" -#: src/api/reference.md:1175 +#: src/api/reference.md:1177 msgid "`add` — instance" msgstr "`add` — instancia" -#: src/api/reference.md:1176 src/api/reference.md:1217 -#: src/api/reference.md:3021 +#: src/api/reference.md:1178 src/api/reference.md:1219 +#: src/api/reference.md:3030 msgid "`addListener` — instance" msgstr "`addListener` — instancia" -#: src/api/reference.md:1177 +#: src/api/reference.md:1179 msgid "`bind` — instance" msgstr "`bind` — instancia" -#: src/api/reference.md:1178 src/api/reference.md:2255 +#: src/api/reference.md:1180 src/api/reference.md:2260 msgid "`create` — module" msgstr "`create` — módulo" -#: src/api/reference.md:1179 +#: src/api/reference.md:1181 msgid "`createDomain` — module" msgstr "`createDomain` — módulo" -#: src/api/reference.md:1180 src/api/reference.md:1221 -#: src/api/reference.md:3030 src/api/reference.md:3699 +#: src/api/reference.md:1182 src/api/reference.md:1223 +#: src/api/reference.md:3039 src/api/reference.md:3719 msgid "`emit` — instance" msgstr "`emit` — instancia" -#: src/api/reference.md:1181 +#: src/api/reference.md:1183 msgid "`enter` — instance" msgstr "`enter` — instancia" -#: src/api/reference.md:1183 +#: src/api/reference.md:1185 msgid "`intercept` — instance" msgstr "`intercept` — instancia" -#: src/api/reference.md:1184 src/api/reference.md:1232 -#: src/api/reference.md:2849 src/api/reference.md:3046 -#: src/api/reference.md:3701 +#: src/api/reference.md:1186 src/api/reference.md:1234 +#: src/api/reference.md:2854 src/api/reference.md:3055 +#: src/api/reference.md:3721 msgid "`on` — instance" msgstr "`on` — instancia" -#: src/api/reference.md:1185 +#: src/api/reference.md:1187 msgid "`remove` — instance" msgstr "`remove` — instancia" -#: src/api/reference.md:1190 +#: src/api/reference.md:1192 msgid "`_stack`" msgstr "`_stack`" -#: src/api/reference.md:1191 +#: src/api/reference.md:1193 msgid "`active`" msgstr "`active`" -#: src/api/reference.md:1192 +#: src/api/reference.md:1194 msgid "`members`" msgstr "`members`" -#: src/api/reference.md:1198 +#: src/api/reference.md:1200 msgid "`createRandom` — module _(class: `Wallet`)_" msgstr "`createRandom` — módulo _(clase: `Wallet`)_" -#: src/api/reference.md:1199 +#: src/api/reference.md:1201 msgid "`formatEther` — module" msgstr "`formatEther` — módulo" -#: src/api/reference.md:1200 +#: src/api/reference.md:1202 msgid "`formatUnits` — module" msgstr "`formatUnits` — módulo" -#: src/api/reference.md:1201 +#: src/api/reference.md:1203 msgid "`getAddress` — module" msgstr "`getAddress` — módulo" -#: src/api/reference.md:1202 +#: src/api/reference.md:1204 msgid "`parseEther` — module" msgstr "`parseEther` — módulo" -#: src/api/reference.md:1203 +#: src/api/reference.md:1205 msgid "`parseUnits` — module" msgstr "`parseUnits` — módulo" -#: src/api/reference.md:1205 +#: src/api/reference.md:1207 msgid "`events`" msgstr "`events`" -#: src/api/reference.md:1209 +#: src/api/reference.md:1211 msgid "`EventEmitter`" msgstr "`EventEmitter`" -#: src/api/reference.md:1210 +#: src/api/reference.md:1212 msgid "`EventEmitterAsyncResource`" msgstr "`EventEmitterAsyncResource`" -#: src/api/reference.md:1214 +#: src/api/reference.md:1216 msgid "`EventEmitter` — module" msgstr "`EventEmitter` — módulo" -#: src/api/reference.md:1215 +#: src/api/reference.md:1217 msgid "`EventEmitterAsyncResource` — module" msgstr "`EventEmitterAsyncResource` — módulo" -#: src/api/reference.md:1216 +#: src/api/reference.md:1218 msgid "`addAbortListener` — module" msgstr "`addAbortListener` — módulo" -#: src/api/reference.md:1218 +#: src/api/reference.md:1220 msgid "`asyncId` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`asyncId` — instancia _(clase: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1219 +#: src/api/reference.md:1221 msgid "`asyncResource` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`asyncResource` — instancia _(clase: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1220 +#: src/api/reference.md:1222 msgid "`domain` — instance" msgstr "`domain` — instancia" -#: src/api/reference.md:1222 +#: src/api/reference.md:1224 msgid "`emitDestroy` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`emitDestroy` — instancia _(clase: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1223 src/api/reference.md:3033 +#: src/api/reference.md:1225 src/api/reference.md:3042 msgid "`eventNames` — instance" msgstr "`eventNames` — instancia" -#: src/api/reference.md:1224 +#: src/api/reference.md:1226 msgid "`getEventListeners` — module" msgstr "`getEventListeners` — módulo" -#: src/api/reference.md:1225 src/api/reference.md:3036 +#: src/api/reference.md:1227 src/api/reference.md:3045 msgid "`getMaxListeners` — instance" msgstr "`getMaxListeners` — instancia" -#: src/api/reference.md:1226 +#: src/api/reference.md:1228 msgid "`getMaxListeners` — module" msgstr "`getMaxListeners` — módulo" -#: src/api/reference.md:1227 +#: src/api/reference.md:1229 msgid "`init` — module" msgstr "`init` — módulo" -#: src/api/reference.md:1228 src/api/reference.md:3043 +#: src/api/reference.md:1230 src/api/reference.md:3052 msgid "`listenerCount` — instance" msgstr "`listenerCount` — instancia" -#: src/api/reference.md:1229 +#: src/api/reference.md:1231 msgid "`listenerCount` — module" msgstr "`listenerCount` — módulo" -#: src/api/reference.md:1230 src/api/reference.md:3044 +#: src/api/reference.md:1232 src/api/reference.md:3053 msgid "`listeners` — instance" msgstr "`listeners` — instancia" -#: src/api/reference.md:1231 src/api/reference.md:3045 +#: src/api/reference.md:1233 src/api/reference.md:3054 msgid "`off` — instance" msgstr "`off` — instancia" -#: src/api/reference.md:1233 +#: src/api/reference.md:1235 msgid "`on` — module" msgstr "`on` — módulo" -#: src/api/reference.md:1234 src/api/reference.md:3047 +#: src/api/reference.md:1236 src/api/reference.md:3056 msgid "`once` — instance" msgstr "`once` — instancia" -#: src/api/reference.md:1235 +#: src/api/reference.md:1237 msgid "`once` — module" msgstr "`once` — módulo" -#: src/api/reference.md:1236 src/api/reference.md:3051 +#: src/api/reference.md:1238 src/api/reference.md:3060 msgid "`prependListener` — instance" msgstr "`prependListener` — instancia" -#: src/api/reference.md:1237 src/api/reference.md:3052 +#: src/api/reference.md:1239 src/api/reference.md:3061 msgid "`prependOnceListener` — instance" msgstr "`prependOnceListener` — instancia" -#: src/api/reference.md:1238 src/api/reference.md:3054 +#: src/api/reference.md:1240 src/api/reference.md:3063 msgid "`rawListeners` — instance" msgstr "`rawListeners` — instancia" -#: src/api/reference.md:1239 src/api/reference.md:3065 +#: src/api/reference.md:1241 src/api/reference.md:3074 msgid "`removeAllListeners` — instance" msgstr "`removeAllListeners` — instancia" -#: src/api/reference.md:1240 src/api/reference.md:3066 +#: src/api/reference.md:1242 src/api/reference.md:3075 msgid "`removeListener` — instance" msgstr "`removeListener` — instancia" -#: src/api/reference.md:1241 src/api/reference.md:3070 +#: src/api/reference.md:1243 src/api/reference.md:3079 msgid "`setMaxListeners` — instance" msgstr "`setMaxListeners` — instancia" -#: src/api/reference.md:1242 +#: src/api/reference.md:1244 msgid "`setMaxListeners` — module" msgstr "`setMaxListeners` — módulo" -#: src/api/reference.md:1243 +#: src/api/reference.md:1245 msgid "`triggerAsyncId` — instance _(class: `EventEmitterAsyncResource`)_" msgstr "`triggerAsyncId` — instancia _(clase: `EventEmitterAsyncResource`)_" -#: src/api/reference.md:1247 +#: src/api/reference.md:1249 msgid "`captureRejectionSymbol`" msgstr "`captureRejectionSymbol`" -#: src/api/reference.md:1248 +#: src/api/reference.md:1250 msgid "`captureRejections`" msgstr "`captureRejections`" -#: src/api/reference.md:1250 +#: src/api/reference.md:1252 msgid "`defaultMaxListeners`" msgstr "`defaultMaxListeners`" -#: src/api/reference.md:1251 +#: src/api/reference.md:1253 msgid "`errorMonitor`" msgstr "`errorMonitor`" -#: src/api/reference.md:1252 +#: src/api/reference.md:1254 msgid "`usingDomains`" msgstr "`usingDomains`" -#: src/api/reference.md:1254 +#: src/api/reference.md:1256 msgid "`fetch`" msgstr "`fetch`" -#: src/api/reference.md:1259 src/api/reference.md:1996 +#: src/api/reference.md:1261 src/api/reference.md:1998 msgid "`FormData`" msgstr "`FormData`" -#: src/api/reference.md:1260 src/api/reference.md:1997 +#: src/api/reference.md:1262 src/api/reference.md:1999 msgid "`Headers`" msgstr "`Headers`" -#: src/api/reference.md:1261 src/api/reference.md:1998 +#: src/api/reference.md:1263 src/api/reference.md:2000 msgid "`Request`" msgstr "`Request`" -#: src/api/reference.md:1262 src/api/reference.md:1999 +#: src/api/reference.md:1264 src/api/reference.md:2001 msgid "`Response`" msgstr "`Response`" -#: src/api/reference.md:1268 +#: src/api/reference.md:1270 msgid "`ffi`" msgstr "`ffi`" -#: src/api/reference.md:1273 +#: src/api/reference.md:1275 msgid "`getRawPointer` — module" msgstr "`getRawPointer` — módulo" -#: src/api/reference.md:1276 +#: src/api/reference.md:1278 msgid "`toString` — module" msgstr "`toString` — módulo" -#: src/api/reference.md:1282 +#: src/api/reference.md:1284 msgid "`fs`" msgstr "`fs`" -#: src/api/reference.md:1286 +#: src/api/reference.md:1288 msgid "`Dir`" msgstr "`Dir`" -#: src/api/reference.md:1287 +#: src/api/reference.md:1289 msgid "`Dirent`" msgstr "`Dirent`" -#: src/api/reference.md:1288 +#: src/api/reference.md:1290 msgid "`FileReadStream`" msgstr "`FileReadStream`" -#: src/api/reference.md:1289 +#: src/api/reference.md:1291 msgid "`FileWriteStream`" msgstr "`FileWriteStream`" -#: src/api/reference.md:1290 src/api/reference.md:3339 +#: src/api/reference.md:1292 src/api/reference.md:3359 msgid "`ReadStream`" msgstr "`ReadStream`" -#: src/api/reference.md:1291 +#: src/api/reference.md:1293 msgid "`Stats`" msgstr "`Stats`" -#: src/api/reference.md:1292 +#: src/api/reference.md:1294 msgid "`Utf8Stream`" msgstr "`Utf8Stream`" -#: src/api/reference.md:1293 src/api/reference.md:3340 +#: src/api/reference.md:1295 src/api/reference.md:3360 msgid "`WriteStream`" msgstr "`WriteStream`" -#: src/api/reference.md:1297 src/api/reference.md:1298 +#: src/api/reference.md:1299 src/api/reference.md:1300 msgid "`_toUnixTimestamp` — module" msgstr "`_toUnixTimestamp` — módulo" -#: src/api/reference.md:1299 src/api/reference.md:1402 +#: src/api/reference.md:1301 src/api/reference.md:1404 msgid "`access` — module" msgstr "`access` — módulo" -#: src/api/reference.md:1300 +#: src/api/reference.md:1302 msgid "`accessSync` — module" msgstr "`accessSync` — módulo" -#: src/api/reference.md:1301 src/api/reference.md:1403 +#: src/api/reference.md:1303 src/api/reference.md:1405 msgid "`appendFile` — module" msgstr "`appendFile` — módulo" -#: src/api/reference.md:1302 +#: src/api/reference.md:1304 msgid "`appendFileSync` — module" msgstr "`appendFileSync` — módulo" -#: src/api/reference.md:1303 src/api/reference.md:1404 +#: src/api/reference.md:1305 src/api/reference.md:1406 msgid "`chmod` — module" msgstr "`chmod` — módulo" -#: src/api/reference.md:1304 +#: src/api/reference.md:1306 msgid "`chmodSync` — module" msgstr "`chmodSync` — módulo" -#: src/api/reference.md:1305 src/api/reference.md:1405 +#: src/api/reference.md:1307 src/api/reference.md:1407 msgid "`chown` — module" msgstr "`chown` — módulo" -#: src/api/reference.md:1306 +#: src/api/reference.md:1308 msgid "`chownSync` — module" msgstr "`chownSync` — módulo" -#: src/api/reference.md:1307 src/api/reference.md:1737 +#: src/api/reference.md:1309 src/api/reference.md:1739 msgid "`close` — module" msgstr "`close` — módulo" -#: src/api/reference.md:1308 +#: src/api/reference.md:1310 msgid "`closeSync` — module" msgstr "`closeSync` — módulo" -#: src/api/reference.md:1309 src/api/reference.md:1406 +#: src/api/reference.md:1311 src/api/reference.md:1408 msgid "`copyFile` — module" msgstr "`copyFile` — módulo" -#: src/api/reference.md:1310 +#: src/api/reference.md:1312 msgid "`copyFileSync` — module" msgstr "`copyFileSync` — módulo" -#: src/api/reference.md:1311 src/api/reference.md:1407 +#: src/api/reference.md:1313 src/api/reference.md:1409 msgid "`cp` — module" msgstr "`cp` — módulo" -#: src/api/reference.md:1312 +#: src/api/reference.md:1314 msgid "`cpSync` — module" msgstr "`cpSync` — módulo" -#: src/api/reference.md:1313 +#: src/api/reference.md:1315 msgid "`createReadStream` — module" msgstr "`createReadStream` — módulo" -#: src/api/reference.md:1314 +#: src/api/reference.md:1316 msgid "`createWriteStream` — module" msgstr "`createWriteStream` — módulo" -#: src/api/reference.md:1315 +#: src/api/reference.md:1317 msgid "`exists` — module" msgstr "`exists` — módulo" -#: src/api/reference.md:1316 +#: src/api/reference.md:1318 msgid "`existsSync` — module" msgstr "`existsSync` — módulo" -#: src/api/reference.md:1317 +#: src/api/reference.md:1319 msgid "`fchmod` — module" msgstr "`fchmod` — módulo" -#: src/api/reference.md:1318 +#: src/api/reference.md:1320 msgid "`fchmodSync` — module" msgstr "`fchmodSync` — módulo" -#: src/api/reference.md:1319 +#: src/api/reference.md:1321 msgid "`fchown` — module" msgstr "`fchown` — módulo" -#: src/api/reference.md:1320 +#: src/api/reference.md:1322 msgid "`fchownSync` — module" msgstr "`fchownSync` — módulo" -#: src/api/reference.md:1321 +#: src/api/reference.md:1323 msgid "`fdatasync` — module" msgstr "`fdatasync` — módulo" -#: src/api/reference.md:1322 +#: src/api/reference.md:1324 msgid "`fdatasyncSync` — module" msgstr "`fdatasyncSync` — módulo" -#: src/api/reference.md:1323 +#: src/api/reference.md:1325 msgid "`fstat` — module" msgstr "`fstat` — módulo" -#: src/api/reference.md:1324 +#: src/api/reference.md:1326 msgid "`fstatSync` — module" msgstr "`fstatSync` — módulo" -#: src/api/reference.md:1325 +#: src/api/reference.md:1327 msgid "`fsync` — module" msgstr "`fsync` — módulo" -#: src/api/reference.md:1326 +#: src/api/reference.md:1328 msgid "`fsyncSync` — module" msgstr "`fsyncSync` — módulo" -#: src/api/reference.md:1327 +#: src/api/reference.md:1329 msgid "`ftruncate` — module" msgstr "`ftruncate` — módulo" -#: src/api/reference.md:1328 +#: src/api/reference.md:1330 msgid "`ftruncateSync` — module" msgstr "`ftruncateSync` — módulo" -#: src/api/reference.md:1329 +#: src/api/reference.md:1331 msgid "`futimes` — module" msgstr "`futimes` — módulo" -#: src/api/reference.md:1330 +#: src/api/reference.md:1332 msgid "`futimesSync` — module" msgstr "`futimesSync` — módulo" -#: src/api/reference.md:1331 src/api/reference.md:1408 +#: src/api/reference.md:1333 src/api/reference.md:1410 msgid "`glob` — module" msgstr "`glob` — módulo" -#: src/api/reference.md:1332 +#: src/api/reference.md:1334 msgid "`globSync` — module" msgstr "`globSync` — módulo" -#: src/api/reference.md:1333 src/api/reference.md:1409 +#: src/api/reference.md:1335 src/api/reference.md:1411 msgid "`lchmod` — module" msgstr "`lchmod` — módulo" -#: src/api/reference.md:1334 +#: src/api/reference.md:1336 msgid "`lchmodSync` — module" msgstr "`lchmodSync` — módulo" -#: src/api/reference.md:1335 src/api/reference.md:1410 +#: src/api/reference.md:1337 src/api/reference.md:1412 msgid "`lchown` — module" msgstr "`lchown` — módulo" -#: src/api/reference.md:1336 +#: src/api/reference.md:1338 msgid "`lchownSync` — module" msgstr "`lchownSync` — módulo" -#: src/api/reference.md:1337 src/api/reference.md:1411 +#: src/api/reference.md:1339 src/api/reference.md:1413 msgid "`link` — module" msgstr "`link` — módulo" -#: src/api/reference.md:1338 +#: src/api/reference.md:1340 msgid "`linkSync` — module" msgstr "`linkSync` — módulo" -#: src/api/reference.md:1339 src/api/reference.md:1412 +#: src/api/reference.md:1341 src/api/reference.md:1414 msgid "`lstat` — module" msgstr "`lstat` — módulo" -#: src/api/reference.md:1340 +#: src/api/reference.md:1342 msgid "`lstatSync` — module" msgstr "`lstatSync` — módulo" -#: src/api/reference.md:1341 src/api/reference.md:1413 +#: src/api/reference.md:1343 src/api/reference.md:1415 msgid "`lutimes` — module" msgstr "`lutimes` — módulo" -#: src/api/reference.md:1342 +#: src/api/reference.md:1344 msgid "`lutimesSync` — module" msgstr "`lutimesSync` — módulo" -#: src/api/reference.md:1343 src/api/reference.md:1414 +#: src/api/reference.md:1345 src/api/reference.md:1416 msgid "`mkdir` — module" msgstr "`mkdir` — módulo" -#: src/api/reference.md:1344 +#: src/api/reference.md:1346 msgid "`mkdirSync` — module" msgstr "`mkdirSync` — módulo" -#: src/api/reference.md:1345 src/api/reference.md:1415 +#: src/api/reference.md:1347 src/api/reference.md:1417 msgid "`mkdtemp` — module" msgstr "`mkdtemp` — módulo" -#: src/api/reference.md:1346 +#: src/api/reference.md:1348 msgid "`mkdtempDisposableSync` — module" msgstr "`mkdtempDisposableSync` — módulo" -#: src/api/reference.md:1347 +#: src/api/reference.md:1349 msgid "`mkdtempSync` — module" msgstr "`mkdtempSync` — módulo" -#: src/api/reference.md:1348 src/api/reference.md:1417 +#: src/api/reference.md:1350 src/api/reference.md:1419 msgid "`open` — module" msgstr "`open` — módulo" -#: src/api/reference.md:1349 +#: src/api/reference.md:1351 msgid "`openAsBlob` — module" msgstr "`openAsBlob` — módulo" -#: src/api/reference.md:1350 +#: src/api/reference.md:1352 msgid "`openSync` — module" msgstr "`openSync` — módulo" -#: src/api/reference.md:1351 src/api/reference.md:1418 +#: src/api/reference.md:1353 src/api/reference.md:1420 msgid "`opendir` — module" msgstr "`opendir` — módulo" -#: src/api/reference.md:1352 +#: src/api/reference.md:1354 msgid "`opendirSync` — module" msgstr "`opendirSync` — módulo" -#: src/api/reference.md:1353 +#: src/api/reference.md:1355 msgid "`read` — module" msgstr "`read` — módulo" -#: src/api/reference.md:1354 src/api/reference.md:1421 +#: src/api/reference.md:1356 src/api/reference.md:1423 msgid "`readFile` — module" msgstr "`readFile` — módulo" -#: src/api/reference.md:1355 +#: src/api/reference.md:1357 msgid "`readFileSync` — module" msgstr "`readFileSync` — módulo" -#: src/api/reference.md:1356 +#: src/api/reference.md:1358 msgid "`readSync` — module" msgstr "`readSync` — módulo" -#: src/api/reference.md:1357 src/api/reference.md:1422 +#: src/api/reference.md:1359 src/api/reference.md:1424 msgid "`readdir` — module" msgstr "`readdir` — módulo" -#: src/api/reference.md:1358 +#: src/api/reference.md:1360 msgid "`readdirSync` — module" msgstr "`readdirSync` — módulo" -#: src/api/reference.md:1359 src/api/reference.md:1423 +#: src/api/reference.md:1361 src/api/reference.md:1425 msgid "`readlink` — module" msgstr "`readlink` — módulo" -#: src/api/reference.md:1360 +#: src/api/reference.md:1362 msgid "`readlinkSync` — module" msgstr "`readlinkSync` — módulo" -#: src/api/reference.md:1361 +#: src/api/reference.md:1363 msgid "`readv` — module" msgstr "`readv` — módulo" -#: src/api/reference.md:1362 +#: src/api/reference.md:1364 msgid "`readvSync` — module" msgstr "`readvSync` — módulo" -#: src/api/reference.md:1363 src/api/reference.md:1424 +#: src/api/reference.md:1365 src/api/reference.md:1426 msgid "`realpath` — module" msgstr "`realpath` — módulo" -#: src/api/reference.md:1364 +#: src/api/reference.md:1366 msgid "`realpathSync` — module" msgstr "`realpathSync` — módulo" -#: src/api/reference.md:1365 src/api/reference.md:1425 +#: src/api/reference.md:1367 src/api/reference.md:1427 msgid "`rename` — module" msgstr "`rename` — módulo" -#: src/api/reference.md:1366 +#: src/api/reference.md:1368 msgid "`renameSync` — module" msgstr "`renameSync` — módulo" -#: src/api/reference.md:1367 src/api/reference.md:1426 +#: src/api/reference.md:1369 src/api/reference.md:1428 msgid "`rm` — module" msgstr "`rm` — módulo" -#: src/api/reference.md:1368 +#: src/api/reference.md:1370 msgid "`rmSync` — module" msgstr "`rmSync` — módulo" -#: src/api/reference.md:1369 src/api/reference.md:1427 +#: src/api/reference.md:1371 src/api/reference.md:1429 msgid "`rmdir` — module" msgstr "`rmdir` — módulo" -#: src/api/reference.md:1370 +#: src/api/reference.md:1372 msgid "`rmdirSync` — module" msgstr "`rmdirSync` — módulo" -#: src/api/reference.md:1371 src/api/reference.md:1428 +#: src/api/reference.md:1373 src/api/reference.md:1430 msgid "`stat` — module" msgstr "`stat` — módulo" -#: src/api/reference.md:1372 +#: src/api/reference.md:1374 msgid "`statSync` — module" msgstr "`statSync` — módulo" -#: src/api/reference.md:1373 src/api/reference.md:1429 +#: src/api/reference.md:1375 src/api/reference.md:1431 msgid "`statfs` — module" msgstr "`statfs` — módulo" -#: src/api/reference.md:1374 +#: src/api/reference.md:1376 msgid "`statfsSync` — module" msgstr "`statfsSync` — módulo" -#: src/api/reference.md:1375 src/api/reference.md:1430 +#: src/api/reference.md:1377 src/api/reference.md:1432 msgid "`symlink` — module" msgstr "`symlink` — módulo" -#: src/api/reference.md:1376 +#: src/api/reference.md:1378 msgid "`symlinkSync` — module" msgstr "`symlinkSync` — módulo" -#: src/api/reference.md:1377 src/api/reference.md:1431 +#: src/api/reference.md:1379 src/api/reference.md:1433 msgid "`truncate` — module" msgstr "`truncate` — módulo" -#: src/api/reference.md:1378 +#: src/api/reference.md:1380 msgid "`truncateSync` — module" msgstr "`truncateSync` — módulo" -#: src/api/reference.md:1379 src/api/reference.md:1432 +#: src/api/reference.md:1381 src/api/reference.md:1434 msgid "`unlink` — module" msgstr "`unlink` — módulo" -#: src/api/reference.md:1380 +#: src/api/reference.md:1382 msgid "`unlinkSync` — module" msgstr "`unlinkSync` — módulo" -#: src/api/reference.md:1381 +#: src/api/reference.md:1383 msgid "`unwatchFile` — module" msgstr "`unwatchFile` — módulo" -#: src/api/reference.md:1382 src/api/reference.md:1433 +#: src/api/reference.md:1384 src/api/reference.md:1435 msgid "`utimes` — module" msgstr "`utimes` — módulo" -#: src/api/reference.md:1383 +#: src/api/reference.md:1385 msgid "`utimesSync` — module" msgstr "`utimesSync` — módulo" -#: src/api/reference.md:1384 src/api/reference.md:1434 +#: src/api/reference.md:1386 src/api/reference.md:1436 msgid "`watch` — module" msgstr "`watch` — módulo" -#: src/api/reference.md:1385 +#: src/api/reference.md:1387 msgid "`watchFile` — module" msgstr "`watchFile` — módulo" -#: src/api/reference.md:1387 src/api/reference.md:1435 +#: src/api/reference.md:1389 src/api/reference.md:1437 msgid "`writeFile` — module" msgstr "`writeFile` — módulo" -#: src/api/reference.md:1388 +#: src/api/reference.md:1390 msgid "`writeFileSync` — module" msgstr "`writeFileSync` — módulo" -#: src/api/reference.md:1389 +#: src/api/reference.md:1391 msgid "`writeSync` — module" msgstr "`writeSync` — módulo" -#: src/api/reference.md:1390 +#: src/api/reference.md:1392 msgid "`writev` — module" msgstr "`writev` — módulo" -#: src/api/reference.md:1391 +#: src/api/reference.md:1393 msgid "`writevSync` — module" msgstr "`writevSync` — módulo" -#: src/api/reference.md:1398 +#: src/api/reference.md:1400 msgid "`fs/promises`" msgstr "`fs/promises`" -#: src/api/reference.md:1416 +#: src/api/reference.md:1418 msgid "`mkdtempDisposable` — module" msgstr "`mkdtempDisposable` — módulo" -#: src/api/reference.md:1419 +#: src/api/reference.md:1421 msgid "`pull` — instance _(class: `FileHandle`)_" msgstr "`pull` — instancia _(clase: `FileHandle`)_" -#: src/api/reference.md:1420 +#: src/api/reference.md:1422 msgid "`pullSync` — instance _(class: `FileHandle`)_" msgstr "`pullSync` — instancia _(clase: `FileHandle`)_" -#: src/api/reference.md:1436 +#: src/api/reference.md:1438 msgid "`writer` — instance _(class: `FileHandle`)_" msgstr "`writer` — instancia _(clase: `FileHandle`)_" -#: src/api/reference.md:1443 +#: src/api/reference.md:1445 msgid "`http`" msgstr "`http`" -#: src/api/reference.md:1447 src/api/reference.md:1678 -#: src/api/reference.md:3380 +#: src/api/reference.md:1449 src/api/reference.md:1680 +#: src/api/reference.md:3400 msgid "`Agent`" msgstr "`Agent`" -#: src/api/reference.md:1448 +#: src/api/reference.md:1450 msgid "`ClientRequest`" msgstr "`ClientRequest`" -#: src/api/reference.md:1449 src/api/reference.md:1450 +#: src/api/reference.md:1451 src/api/reference.md:1452 msgid "`IncomingMessage`" msgstr "`IncomingMessage`" -#: src/api/reference.md:1451 src/api/reference.md:1452 +#: src/api/reference.md:1453 src/api/reference.md:1454 msgid "`OutgoingMessage`" msgstr "`OutgoingMessage`" -#: src/api/reference.md:1453 src/api/reference.md:1454 -#: src/api/reference.md:1679 src/api/reference.md:1680 -#: src/api/reference.md:1862 +#: src/api/reference.md:1455 src/api/reference.md:1456 +#: src/api/reference.md:1681 src/api/reference.md:1682 +#: src/api/reference.md:1864 msgid "`Server`" msgstr "`Server`" -#: src/api/reference.md:1455 src/api/reference.md:1456 +#: src/api/reference.md:1457 src/api/reference.md:1458 msgid "`ServerResponse`" msgstr "`ServerResponse`" -#: src/api/reference.md:1457 src/api/reference.md:3686 +#: src/api/reference.md:1459 src/api/reference.md:3706 msgid "`WebSocket`" msgstr "`WebSocket`" -#: src/api/reference.md:1461 src/api/reference.md:1684 -#: src/api/reference.md:3385 +#: src/api/reference.md:1463 src/api/reference.md:1686 +#: src/api/reference.md:3405 msgid "`Agent` — module" msgstr "`Agent` — módulo" -#: src/api/reference.md:1462 src/api/reference.md:1685 -#: src/api/reference.md:1870 src/api/reference.md:3299 -#: src/api/reference.md:3691 +#: src/api/reference.md:1464 src/api/reference.md:1687 +#: src/api/reference.md:1872 src/api/reference.md:3308 +#: src/api/reference.md:3711 msgid "`Server` — module" msgstr "`Server` — módulo" -#: src/api/reference.md:1463 +#: src/api/reference.md:1465 msgid "`__get_aborted` — instance _(class: `ClientRequest`)_" msgstr "`__get_aborted` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1464 +#: src/api/reference.md:1466 msgid "`__get_aborted` — instance _(class: `IncomingMessage`)_" msgstr "`__get_aborted` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1465 +#: src/api/reference.md:1467 msgid "`__get_complete` — instance _(class: `IncomingMessage`)_" msgstr "`__get_complete` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1466 +#: src/api/reference.md:1468 msgid "`__get_connection` — instance _(class: `IncomingMessage`)_" msgstr "`__get_connection` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1467 +#: src/api/reference.md:1469 msgid "`__get_connection` — instance _(class: `ClientRequest`)_" msgstr "`__get_connection` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1468 +#: src/api/reference.md:1470 msgid "`__get_createConnection` — instance _(class: `Agent`)_" msgstr "`__get_createConnection` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1469 +#: src/api/reference.md:1471 msgid "`__get_createSocket` — instance _(class: `Agent`)_" msgstr "`__get_createSocket` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1470 +#: src/api/reference.md:1472 msgid "`__get_defaultPort` — instance _(class: `Agent`)_" msgstr "`__get_defaultPort` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1471 +#: src/api/reference.md:1473 msgid "`__get_destroyed` — instance _(class: `Agent`)_" msgstr "`__get_destroyed` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1472 +#: src/api/reference.md:1474 msgid "`__get_destroyed` — instance _(class: `ClientRequest`)_" msgstr "`__get_destroyed` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1473 +#: src/api/reference.md:1475 msgid "`__get_destroyed` — instance _(class: `IncomingMessage`)_" msgstr "`__get_destroyed` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1474 +#: src/api/reference.md:1476 msgid "`__get_finished` — instance _(class: `ClientRequest`)_" msgstr "`__get_finished` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1475 +#: src/api/reference.md:1477 msgid "`__get_freeSockets` — instance _(class: `Agent`)_" msgstr "`__get_freeSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1476 +#: src/api/reference.md:1478 msgid "`__get_headers` — instance _(class: `IncomingMessage`)_" msgstr "`__get_headers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1477 +#: src/api/reference.md:1479 msgid "`__get_headersSent` — instance _(class: `ServerResponse`)_" msgstr "`__get_headersSent` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1478 +#: src/api/reference.md:1480 msgid "`__get_headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_headersTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1479 +#: src/api/reference.md:1481 msgid "`__get_host` — instance _(class: `ClientRequest`)_" msgstr "`__get_host` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1480 +#: src/api/reference.md:1482 msgid "`__get_httpVersion` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersion` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1481 +#: src/api/reference.md:1483 msgid "`__get_httpVersionMajor` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersionMajor` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1482 +#: src/api/reference.md:1484 msgid "`__get_httpVersionMinor` — instance _(class: `IncomingMessage`)_" msgstr "`__get_httpVersionMinor` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1483 +#: src/api/reference.md:1485 msgid "`__get_keepAlive` — instance _(class: `Agent`)_" msgstr "`__get_keepAlive` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1484 +#: src/api/reference.md:1486 msgid "`__get_keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`__get_keepAliveMsecs` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1485 +#: src/api/reference.md:1487 msgid "`__get_keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_keepAliveTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1486 +#: src/api/reference.md:1488 msgid "`__get_keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`__get_keepAliveTimeoutBuffer` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1487 +#: src/api/reference.md:1489 msgid "`__get_listening` — instance _(class: `HttpServer`)_" msgstr "`__get_listening` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1488 +#: src/api/reference.md:1490 msgid "`__get_maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxFreeSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1489 +#: src/api/reference.md:1491 msgid "`__get_maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`__get_maxHeadersCount` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1490 +#: src/api/reference.md:1492 msgid "`__get_maxHeadersCount` — instance _(class: `ClientRequest`)_" msgstr "`__get_maxHeadersCount` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1491 +#: src/api/reference.md:1493 msgid "`__get_maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`__get_maxRequestsPerSocket` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1492 +#: src/api/reference.md:1494 msgid "`__get_maxSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1493 +#: src/api/reference.md:1495 msgid "`__get_maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`__get_maxTotalSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1494 +#: src/api/reference.md:1496 msgid "`__get_method` — instance _(class: `ClientRequest`)_" msgstr "`__get_method` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1495 +#: src/api/reference.md:1497 msgid "`__get_method` — instance _(class: `IncomingMessage`)_" msgstr "`__get_method` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1496 +#: src/api/reference.md:1498 msgid "`__get_path` — instance _(class: `ClientRequest`)_" msgstr "`__get_path` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1497 +#: src/api/reference.md:1499 msgid "`__get_protocol` — instance _(class: `Agent`)_" msgstr "`__get_protocol` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1498 +#: src/api/reference.md:1500 msgid "`__get_protocol` — instance _(class: `ClientRequest`)_" msgstr "`__get_protocol` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1499 +#: src/api/reference.md:1501 #, fuzzy msgid "`__get_rawHeaders` — instance _(class: `IncomingMessage`)_" msgstr "`__get_headers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1500 +#: src/api/reference.md:1502 msgid "`__get_req` — instance _(class: `IncomingMessage`)_" msgstr "`__get_req` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1501 +#: src/api/reference.md:1503 msgid "`__get_requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`__get_requestTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1502 +#: src/api/reference.md:1504 msgid "`__get_requests` — instance _(class: `Agent`)_" msgstr "`__get_requests` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1503 +#: src/api/reference.md:1505 msgid "`__get_reusedSocket` — instance _(class: `ClientRequest`)_" msgstr "`__get_reusedSocket` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1504 +#: src/api/reference.md:1506 msgid "`__get_socket` — instance _(class: `IncomingMessage`)_" msgstr "`__get_socket` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1505 +#: src/api/reference.md:1507 msgid "`__get_socket` — instance _(class: `ClientRequest`)_" msgstr "`__get_socket` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1506 +#: src/api/reference.md:1508 msgid "`__get_sockets` — instance _(class: `Agent`)_" msgstr "`__get_sockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1507 +#: src/api/reference.md:1509 msgid "`__get_statusCode` — instance _(class: `IncomingMessage`)_" msgstr "`__get_statusCode` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1508 +#: src/api/reference.md:1510 msgid "`__get_statusCode` — instance _(class: `ServerResponse`)_" msgstr "`__get_statusCode` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1509 +#: src/api/reference.md:1511 msgid "`__get_statusMessage` — instance _(class: `IncomingMessage`)_" msgstr "`__get_statusMessage` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1510 +#: src/api/reference.md:1512 msgid "`__get_timeout` — instance _(class: `HttpServer`)_" msgstr "`__get_timeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1511 +#: src/api/reference.md:1513 msgid "`__get_trailers` — instance _(class: `IncomingMessage`)_" msgstr "`__get_trailers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1512 +#: src/api/reference.md:1514 msgid "`__get_url` — instance _(class: `IncomingMessage`)_" msgstr "`__get_url` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1513 +#: src/api/reference.md:1515 msgid "`__get_writableEnded` — instance _(class: `ClientRequest`)_" msgstr "`__get_writableEnded` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1514 +#: src/api/reference.md:1516 msgid "`__get_writableEnded` — instance _(class: `ServerResponse`)_" msgstr "`__get_writableEnded` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1515 +#: src/api/reference.md:1517 msgid "`__get_writableFinished` — instance _(class: `ClientRequest`)_" msgstr "`__get_writableFinished` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1516 +#: src/api/reference.md:1518 msgid "`__get_writableFinished` — instance _(class: `ServerResponse`)_" msgstr "`__get_writableFinished` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1517 +#: src/api/reference.md:1519 msgid "`__set_createConnection` — instance _(class: `Agent`)_" msgstr "`__set_createConnection` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1518 +#: src/api/reference.md:1520 msgid "`__set_createSocket` — instance _(class: `Agent`)_" msgstr "`__set_createSocket` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1519 +#: src/api/reference.md:1521 msgid "`__set_headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_headersTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1520 +#: src/api/reference.md:1522 msgid "`__set_keepAlive` — instance _(class: `Agent`)_" msgstr "`__set_keepAlive` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1521 +#: src/api/reference.md:1523 msgid "`__set_keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`__set_keepAliveMsecs` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1522 +#: src/api/reference.md:1524 msgid "`__set_keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_keepAliveTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1523 +#: src/api/reference.md:1525 msgid "`__set_keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`__set_keepAliveTimeoutBuffer` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1524 +#: src/api/reference.md:1526 msgid "`__set_maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxFreeSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1525 +#: src/api/reference.md:1527 msgid "`__set_maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`__set_maxHeadersCount` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1526 +#: src/api/reference.md:1528 msgid "`__set_maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`__set_maxRequestsPerSocket` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1527 +#: src/api/reference.md:1529 msgid "`__set_maxSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1528 +#: src/api/reference.md:1530 msgid "`__set_maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`__set_maxTotalSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1529 +#: src/api/reference.md:1531 msgid "`__set_protocol` — instance _(class: `Agent`)_" msgstr "`__set_protocol` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1530 +#: src/api/reference.md:1532 msgid "`__set_requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`__set_requestTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1531 +#: src/api/reference.md:1533 msgid "`__set_sendDate` — instance _(class: `ServerResponse`)_" msgstr "`__set_sendDate` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1532 +#: src/api/reference.md:1534 msgid "`__set_statusCode` — instance _(class: `ServerResponse`)_" msgstr "`__set_statusCode` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1533 +#: src/api/reference.md:1535 msgid "`__set_statusMessage` — instance _(class: `ServerResponse`)_" msgstr "`__set_statusMessage` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1534 +#: src/api/reference.md:1536 msgid "`__set_strictContentLength` — instance _(class: `ServerResponse`)_" msgstr "`__set_strictContentLength` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1535 +#: src/api/reference.md:1537 msgid "`__set_timeout` — instance _(class: `HttpServer`)_" msgstr "`__set_timeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1536 +#: src/api/reference.md:1538 msgid "`_connectionListener` — module" msgstr "`_connectionListener` — módulo" -#: src/api/reference.md:1537 +#: src/api/reference.md:1539 msgid "`abort` — instance _(class: `ClientRequest`)_" msgstr "`abort` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1538 +#: src/api/reference.md:1540 msgid "`addListener` — instance _(class: `HttpServer`)_" msgstr "`addListener` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1539 +#: src/api/reference.md:1541 msgid "`addListener` — instance _(class: `IncomingMessage`)_" msgstr "`addListener` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1540 +#: src/api/reference.md:1542 msgid "`addListener` — instance _(class: `ServerResponse`)_" msgstr "`addListener` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1541 +#: src/api/reference.md:1543 msgid "`addTrailers` — instance _(class: `ServerResponse`)_" msgstr "`addTrailers` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1542 +#: src/api/reference.md:1544 msgid "`address` — instance _(class: `HttpServer`)_" msgstr "`address` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1543 +#: src/api/reference.md:1545 msgid "`appendHeader` — instance _(class: `ServerResponse`)_" msgstr "`appendHeader` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1544 src/api/reference.md:3388 +#: src/api/reference.md:1546 src/api/reference.md:3408 msgid "`close` — instance _(class: `Agent`)_" msgstr "`close` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1545 +#: src/api/reference.md:1547 msgid "`close` — instance _(class: `HttpServer`)_" msgstr "`close` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1546 +#: src/api/reference.md:1548 msgid "`closeAllConnections` — instance _(class: `HttpServer`)_" msgstr "`closeAllConnections` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1547 +#: src/api/reference.md:1549 msgid "`closeIdleConnections` — instance _(class: `HttpServer`)_" msgstr "`closeIdleConnections` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1548 +#: src/api/reference.md:1550 #, fuzzy msgid "`complete` — instance _(class: `IncomingMessage`)_" msgstr "`__get_complete` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1549 +#: src/api/reference.md:1551 msgid "`connection` — instance _(class: `IncomingMessage`)_" msgstr "`connection` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1550 +#: src/api/reference.md:1552 msgid "`cork` — instance _(class: `ClientRequest`)_" msgstr "`cork` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1551 +#: src/api/reference.md:1553 msgid "`cork` — instance _(class: `ServerResponse`)_" msgstr "`cork` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1552 src/api/reference.md:1553 -#: src/api/reference.md:1662 src/api/reference.md:1706 -#: src/api/reference.md:1707 src/api/reference.md:1899 -#: src/api/reference.md:3308 +#: src/api/reference.md:1554 src/api/reference.md:1555 +#: src/api/reference.md:1664 src/api/reference.md:1708 +#: src/api/reference.md:1709 src/api/reference.md:1901 +#: src/api/reference.md:3317 msgid "`createServer` — module" msgstr "`createServer` — módulo" -#: src/api/reference.md:1554 +#: src/api/reference.md:1556 msgid "`defaultPort` — instance _(class: `Agent`)_" msgstr "`defaultPort` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1555 src/api/reference.md:3390 +#: src/api/reference.md:1557 src/api/reference.md:3410 msgid "`destroy` — instance _(class: `Agent`)_" msgstr "`destroy` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1556 +#: src/api/reference.md:1558 msgid "`destroy` — instance _(class: `IncomingMessage`)_" msgstr "`destroy` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1557 +#: src/api/reference.md:1559 msgid "`destroy` — instance _(class: `ClientRequest`)_" msgstr "`destroy` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1558 +#: src/api/reference.md:1560 msgid "`destroyed` — instance _(class: `Agent`)_" msgstr "`destroyed` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1559 +#: src/api/reference.md:1561 msgid "`end` — instance _(class: `ServerResponse`)_" msgstr "`end` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1560 +#: src/api/reference.md:1562 msgid "`flushHeaders` — instance _(class: `ClientRequest`)_" msgstr "`flushHeaders` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1561 +#: src/api/reference.md:1563 msgid "`flushHeaders` — instance _(class: `ServerResponse`)_" msgstr "`flushHeaders` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1562 +#: src/api/reference.md:1564 msgid "`freeSockets` — instance _(class: `Agent`)_" msgstr "`freeSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1563 src/api/reference.md:1708 +#: src/api/reference.md:1565 src/api/reference.md:1710 msgid "`get` — module" msgstr "`get` — módulo" -#: src/api/reference.md:1564 +#: src/api/reference.md:1566 msgid "`getHeader` — instance _(class: `ClientRequest`)_" msgstr "`getHeader` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1565 +#: src/api/reference.md:1567 msgid "`getHeader` — instance _(class: `ServerResponse`)_" msgstr "`getHeader` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1566 +#: src/api/reference.md:1568 msgid "`getHeaderNames` — instance _(class: `ClientRequest`)_" msgstr "`getHeaderNames` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1567 +#: src/api/reference.md:1569 msgid "`getHeaderNames` — instance _(class: `ServerResponse`)_" msgstr "`getHeaderNames` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1568 +#: src/api/reference.md:1570 msgid "`getHeaders` — instance _(class: `ClientRequest`)_" msgstr "`getHeaders` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1569 +#: src/api/reference.md:1571 msgid "`getHeaders` — instance _(class: `ServerResponse`)_" msgstr "`getHeaders` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1570 +#: src/api/reference.md:1572 msgid "`getName` — instance _(class: `Agent`)_" msgstr "`getName` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1571 +#: src/api/reference.md:1573 msgid "`getRawHeaderNames` — instance _(class: `ClientRequest`)_" msgstr "`getRawHeaderNames` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1572 +#: src/api/reference.md:1574 msgid "`getStatus` — instance _(class: `ServerResponse`)_" msgstr "`getStatus` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1573 +#: src/api/reference.md:1575 msgid "`hasHeader` — instance _(class: `ClientRequest`)_" msgstr "`hasHeader` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1574 +#: src/api/reference.md:1576 msgid "`hasHeader` — instance _(class: `ServerResponse`)_" msgstr "`hasHeader` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1575 +#: src/api/reference.md:1577 msgid "`headers` — instance _(class: `IncomingMessage`)_" msgstr "`headers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1576 +#: src/api/reference.md:1578 msgid "`headersTimeout` — instance _(class: `HttpServer`)_" msgstr "`headersTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1577 +#: src/api/reference.md:1579 msgid "`httpVersion` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1578 +#: src/api/reference.md:1580 #, fuzzy msgid "`httpVersionMajor` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1579 +#: src/api/reference.md:1581 #, fuzzy msgid "`httpVersionMinor` — instance _(class: `IncomingMessage`)_" msgstr "`httpVersion` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1580 +#: src/api/reference.md:1582 msgid "`keepAlive` — instance _(class: `Agent`)_" msgstr "`keepAlive` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1581 +#: src/api/reference.md:1583 msgid "`keepAliveMsecs` — instance _(class: `Agent`)_" msgstr "`keepAliveMsecs` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1582 +#: src/api/reference.md:1584 msgid "`keepAliveTimeout` — instance _(class: `HttpServer`)_" msgstr "`keepAliveTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1583 +#: src/api/reference.md:1585 msgid "`keepAliveTimeoutBuffer` — instance _(class: `HttpServer`)_" msgstr "`keepAliveTimeoutBuffer` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1584 +#: src/api/reference.md:1586 msgid "" "`keepSocketAlive` — instance _(class: `Agent`)_ ⚠ **stub** — reqwest owns " "the keep-alive pool; per-socket hooks are no-ops, warns once (#4917)" @@ -40722,113 +40451,113 @@ msgstr "" "la piscina de mantenimiento; los ganchos de per-socket son no-ops, advierte " "una vez (#4917)" -#: src/api/reference.md:1585 +#: src/api/reference.md:1587 msgid "`listen` — instance _(class: `HttpServer`)_" msgstr "`listen` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1586 +#: src/api/reference.md:1588 msgid "`listenerCount` — instance _(class: `ClientRequest`)_" msgstr "`listenerCount` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1587 +#: src/api/reference.md:1589 msgid "`listening` — instance _(class: `HttpServer`)_" msgstr "`listening` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1588 +#: src/api/reference.md:1590 msgid "`maxFreeSockets` — instance _(class: `Agent`)_" msgstr "`maxFreeSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1589 +#: src/api/reference.md:1591 msgid "`maxHeadersCount` — instance _(class: `HttpServer`)_" msgstr "`maxHeadersCount` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1590 +#: src/api/reference.md:1592 msgid "`maxRequestsPerSocket` — instance _(class: `HttpServer`)_" msgstr "`maxRequestsPerSocket` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1591 +#: src/api/reference.md:1593 msgid "`maxSockets` — instance _(class: `Agent`)_" msgstr "`maxSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1592 +#: src/api/reference.md:1594 msgid "`maxTotalSockets` — instance _(class: `Agent`)_" msgstr "`maxTotalSockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1593 +#: src/api/reference.md:1595 msgid "`method` — instance _(class: `IncomingMessage`)_" msgstr "`method` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1594 +#: src/api/reference.md:1596 msgid "`on` — instance _(class: `HttpServer`)_" msgstr "`on` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1595 +#: src/api/reference.md:1597 msgid "`on` — instance _(class: `IncomingMessage`)_" msgstr "`on` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1596 +#: src/api/reference.md:1598 msgid "`on` — instance _(class: `ServerResponse`)_" msgstr "`on` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1597 +#: src/api/reference.md:1599 msgid "`once` — instance _(class: `IncomingMessage`)_" msgstr "`once` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1598 +#: src/api/reference.md:1600 msgid "`once` — instance _(class: `ClientRequest`)_" msgstr "`once` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1599 +#: src/api/reference.md:1601 msgid "`pause` — instance _(class: `IncomingMessage`)_" msgstr "`pause` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1600 +#: src/api/reference.md:1602 msgid "`protocol` — instance _(class: `Agent`)_" msgstr "`protocol` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1601 +#: src/api/reference.md:1603 #, fuzzy msgid "`rawHeaders` — instance _(class: `IncomingMessage`)_" msgstr "`headers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1602 +#: src/api/reference.md:1604 msgid "`read` — instance _(class: `IncomingMessage`)_" msgstr "`read` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1603 +#: src/api/reference.md:1605 msgid "`ref` — instance _(class: `HttpServer`)_" msgstr "`ref` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1604 +#: src/api/reference.md:1606 msgid "`removeHeader` — instance _(class: `ClientRequest`)_" msgstr "`removeHeader` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1605 +#: src/api/reference.md:1607 msgid "`removeHeader` — instance _(class: `ServerResponse`)_" msgstr "`removeHeader` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1606 +#: src/api/reference.md:1608 msgid "`req` — instance _(class: `IncomingMessage`)_" msgstr "`req` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1607 src/api/reference.md:1718 -#: src/api/reference.md:3393 +#: src/api/reference.md:1609 src/api/reference.md:1720 +#: src/api/reference.md:3413 msgid "`request` — module" msgstr "`request` — módulo" -#: src/api/reference.md:1608 +#: src/api/reference.md:1610 msgid "`requestTimeout` — instance _(class: `HttpServer`)_" msgstr "`requestTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1609 +#: src/api/reference.md:1611 msgid "`requests` — instance _(class: `Agent`)_" msgstr "`requests` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1610 +#: src/api/reference.md:1612 msgid "`resume` — instance _(class: `IncomingMessage`)_" msgstr "`resume` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1611 +#: src/api/reference.md:1613 msgid "" "`reuseSocket` — instance _(class: `Agent`)_ ⚠ **stub** — reqwest owns the " "keep-alive pool; per-socket hooks are no-ops, warns once (#4917)" @@ -40837,369 +40566,369 @@ msgstr "" "piscina de mantenimiento; los ganchos de per-socket son no-ops, advierte una " "vez (#4917)" -#: src/api/reference.md:1612 +#: src/api/reference.md:1614 msgid "`setEncoding` — instance _(class: `IncomingMessage`)_" msgstr "`setEncoding` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1613 +#: src/api/reference.md:1615 msgid "`setGlobalProxyFromEnv` — module" msgstr "`setGlobalProxyFromEnv` — módulo" -#: src/api/reference.md:1614 +#: src/api/reference.md:1616 msgid "`setHeader` — instance _(class: `ClientRequest`)_" msgstr "`setHeader` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1615 +#: src/api/reference.md:1617 msgid "`setHeader` — instance _(class: `ServerResponse`)_" msgstr "`setHeader` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1616 +#: src/api/reference.md:1618 msgid "`setHeaders` — instance _(class: `ServerResponse`)_" msgstr "`setHeaders` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1617 +#: src/api/reference.md:1619 msgid "`setMaxIdleHTTPParsers` — module" msgstr "`setMaxIdleHTTPParsers` — módulo" -#: src/api/reference.md:1618 +#: src/api/reference.md:1620 msgid "`setNoDelay` — instance _(class: `ClientRequest`)_" msgstr "`setNoDelay` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1619 +#: src/api/reference.md:1621 msgid "`setSocketKeepAlive` — instance _(class: `ClientRequest`)_" msgstr "`setSocketKeepAlive` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1620 +#: src/api/reference.md:1622 msgid "`setStatus` — instance _(class: `ServerResponse`)_" msgstr "`setStatus` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1621 +#: src/api/reference.md:1623 msgid "`setTimeout` — instance _(class: `HttpServer`)_" msgstr "`setTimeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1622 +#: src/api/reference.md:1624 msgid "`setTimeout` — instance _(class: `IncomingMessage`)_" msgstr "`setTimeout` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1623 +#: src/api/reference.md:1625 msgid "`setTimeout` — instance _(class: `ClientRequest`)_" msgstr "`setTimeout` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1624 +#: src/api/reference.md:1626 msgid "`setTimeout` — instance _(class: `ServerResponse`)_" msgstr "`setTimeout` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1625 +#: src/api/reference.md:1627 msgid "`socket` — instance _(class: `IncomingMessage`)_" msgstr "`socket` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1626 +#: src/api/reference.md:1628 msgid "`sockets` — instance _(class: `Agent`)_" msgstr "`sockets` — instancia _(clase: `Agent`)_" -#: src/api/reference.md:1627 +#: src/api/reference.md:1629 msgid "`statusCode` — instance _(class: `IncomingMessage`)_" msgstr "`statusCode` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1628 +#: src/api/reference.md:1630 msgid "`statusMessage` — instance _(class: `IncomingMessage`)_" msgstr "`statusMessage` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1629 +#: src/api/reference.md:1631 msgid "`timeout` — instance _(class: `HttpServer`)_" msgstr "`timeout` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1630 +#: src/api/reference.md:1632 msgid "`trailers` — instance _(class: `IncomingMessage`)_" msgstr "`trailers` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1631 +#: src/api/reference.md:1633 msgid "`uncork` — instance _(class: `ClientRequest`)_" msgstr "`uncork` — instancia _(clase: `ClientRequest`)_" -#: src/api/reference.md:1632 +#: src/api/reference.md:1634 msgid "`uncork` — instance _(class: `ServerResponse`)_" msgstr "`uncork` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1633 +#: src/api/reference.md:1635 msgid "`unref` — instance _(class: `HttpServer`)_" msgstr "`unref` — instancia _(clase: `HttpServer`)_" -#: src/api/reference.md:1634 +#: src/api/reference.md:1636 msgid "`url` — instance _(class: `IncomingMessage`)_" msgstr "`url` — instancia _(clase: `IncomingMessage`)_" -#: src/api/reference.md:1635 +#: src/api/reference.md:1637 msgid "`validateHeaderName` — module" msgstr "`validateHeaderName` — módulo" -#: src/api/reference.md:1636 +#: src/api/reference.md:1638 msgid "`validateHeaderValue` — module" msgstr "`validateHeaderValue` — módulo" -#: src/api/reference.md:1637 +#: src/api/reference.md:1639 msgid "`write` — instance _(class: `ServerResponse`)_" msgstr "`write` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1638 +#: src/api/reference.md:1640 msgid "`writeContinue` — instance _(class: `ServerResponse`)_" msgstr "`writeContinue` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1639 +#: src/api/reference.md:1641 msgid "`writeEarlyHints` — instance _(class: `ServerResponse`)_" msgstr "`writeEarlyHints` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1640 +#: src/api/reference.md:1642 msgid "`writeHead` — instance _(class: `ServerResponse`)_" msgstr "`writeHead` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1641 +#: src/api/reference.md:1643 msgid "`writeProcessing` — instance _(class: `ServerResponse`)_" msgstr "`writeProcessing` — instancia _(clase: `ServerResponse`)_" -#: src/api/reference.md:1645 +#: src/api/reference.md:1647 msgid "`METHODS`" msgstr "`METHODS`" -#: src/api/reference.md:1646 +#: src/api/reference.md:1648 msgid "`STATUS_CODES`" msgstr "`STATUS_CODES`" -#: src/api/reference.md:1647 src/api/reference.md:1726 +#: src/api/reference.md:1649 src/api/reference.md:1728 msgid "`globalAgent`" msgstr "`globalAgent`" -#: src/api/reference.md:1648 +#: src/api/reference.md:1650 msgid "`kConnectionsCheckingInterval`" msgstr "`kConnectionsCheckingInterval`" -#: src/api/reference.md:1649 +#: src/api/reference.md:1651 msgid "`maxHeaderSize`" msgstr "`maxHeaderSize`" -#: src/api/reference.md:1651 +#: src/api/reference.md:1653 msgid "`http2`" msgstr "`http2`" -#: src/api/reference.md:1655 +#: src/api/reference.md:1657 msgid "`Http2ServerRequest`" msgstr "`Http2ServerRequest`" -#: src/api/reference.md:1656 +#: src/api/reference.md:1658 msgid "`Http2ServerResponse`" msgstr "`Http2ServerResponse`" -#: src/api/reference.md:1661 +#: src/api/reference.md:1663 msgid "`createSecureServer` — module" msgstr "`createSecureServer` — módulo" -#: src/api/reference.md:1663 +#: src/api/reference.md:1665 msgid "`getDefaultSettings` — module" msgstr "`getDefaultSettings` — módulo" -#: src/api/reference.md:1664 +#: src/api/reference.md:1666 msgid "`getPackedSettings` — module" msgstr "`getPackedSettings` — módulo" -#: src/api/reference.md:1665 +#: src/api/reference.md:1667 msgid "`getUnpackedSettings` — module" msgstr "`getUnpackedSettings` — módulo" -#: src/api/reference.md:1666 +#: src/api/reference.md:1668 msgid "`performServerHandshake` — module" msgstr "`performServerHandshake` — módulo" -#: src/api/reference.md:1672 +#: src/api/reference.md:1674 msgid "`sensitiveHeaders`" msgstr "`sensitiveHeaders`" -#: src/api/reference.md:1674 +#: src/api/reference.md:1676 msgid "`https`" msgstr "`https`" -#: src/api/reference.md:1686 +#: src/api/reference.md:1688 msgid "`__get_headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_headersTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1687 +#: src/api/reference.md:1689 msgid "`__get_keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_keepAliveTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1688 +#: src/api/reference.md:1690 msgid "`__get_keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`__get_keepAliveTimeoutBuffer` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1689 +#: src/api/reference.md:1691 msgid "`__get_listening` — instance _(class: `HttpsServer`)_" msgstr "`__get_listening` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1690 +#: src/api/reference.md:1692 msgid "`__get_maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`__get_maxHeadersCount` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1691 +#: src/api/reference.md:1693 msgid "`__get_maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`__get_maxRequestsPerSocket` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1692 +#: src/api/reference.md:1694 msgid "`__get_requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_requestTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1693 +#: src/api/reference.md:1695 msgid "`__get_timeout` — instance _(class: `HttpsServer`)_" msgstr "`__get_timeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1694 +#: src/api/reference.md:1696 msgid "`__set_headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_headersTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1695 +#: src/api/reference.md:1697 msgid "`__set_keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_keepAliveTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1696 +#: src/api/reference.md:1698 msgid "`__set_keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`__set_keepAliveTimeoutBuffer` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1697 +#: src/api/reference.md:1699 msgid "`__set_maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`__set_maxHeadersCount` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1698 +#: src/api/reference.md:1700 msgid "`__set_maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`__set_maxRequestsPerSocket` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1699 +#: src/api/reference.md:1701 msgid "`__set_requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_requestTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1700 +#: src/api/reference.md:1702 msgid "`__set_timeout` — instance _(class: `HttpsServer`)_" msgstr "`__set_timeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1701 +#: src/api/reference.md:1703 msgid "`addListener` — instance _(class: `HttpsServer`)_" msgstr "`addListener` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1702 +#: src/api/reference.md:1704 msgid "`address` — instance _(class: `HttpsServer`)_" msgstr "`address` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1703 +#: src/api/reference.md:1705 msgid "`close` — instance _(class: `HttpsServer`)_" msgstr "`close` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1704 +#: src/api/reference.md:1706 msgid "`closeAllConnections` — instance _(class: `HttpsServer`)_" msgstr "`closeAllConnections` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1705 +#: src/api/reference.md:1707 msgid "`closeIdleConnections` — instance _(class: `HttpsServer`)_" msgstr "`closeIdleConnections` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1709 +#: src/api/reference.md:1711 msgid "`headersTimeout` — instance _(class: `HttpsServer`)_" msgstr "`headersTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1710 +#: src/api/reference.md:1712 msgid "`keepAliveTimeout` — instance _(class: `HttpsServer`)_" msgstr "`keepAliveTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1711 +#: src/api/reference.md:1713 msgid "`keepAliveTimeoutBuffer` — instance _(class: `HttpsServer`)_" msgstr "`keepAliveTimeoutBuffer` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1712 +#: src/api/reference.md:1714 msgid "`listen` — instance _(class: `HttpsServer`)_" msgstr "`listen` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1713 +#: src/api/reference.md:1715 msgid "`listening` — instance _(class: `HttpsServer`)_" msgstr "`listening` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1714 +#: src/api/reference.md:1716 msgid "`maxHeadersCount` — instance _(class: `HttpsServer`)_" msgstr "`maxHeadersCount` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1715 +#: src/api/reference.md:1717 msgid "`maxRequestsPerSocket` — instance _(class: `HttpsServer`)_" msgstr "`maxRequestsPerSocket` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1716 +#: src/api/reference.md:1718 msgid "`on` — instance _(class: `HttpsServer`)_" msgstr "`on` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1717 +#: src/api/reference.md:1719 msgid "`ref` — instance _(class: `HttpsServer`)_" msgstr "`ref` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1719 +#: src/api/reference.md:1721 msgid "`requestTimeout` — instance _(class: `HttpsServer`)_" msgstr "`requestTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1720 +#: src/api/reference.md:1722 msgid "`setTimeout` — instance _(class: `HttpsServer`)_" msgstr "`setTimeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1721 +#: src/api/reference.md:1723 msgid "`timeout` — instance _(class: `HttpsServer`)_" msgstr "`timeout` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1722 +#: src/api/reference.md:1724 msgid "`unref` — instance _(class: `HttpsServer`)_" msgstr "`unref` — instancia _(clase: `HttpsServer`)_" -#: src/api/reference.md:1728 +#: src/api/reference.md:1730 msgid "`inspector`" msgstr "`inspector`" -#: src/api/reference.md:1732 src/api/reference.md:1763 -#: src/api/reference.md:2948 +#: src/api/reference.md:1734 src/api/reference.md:1765 +#: src/api/reference.md:2957 msgid "`Session`" msgstr "`Session`" -#: src/api/reference.md:1736 src/api/reference.md:1767 -#: src/api/reference.md:2955 +#: src/api/reference.md:1738 src/api/reference.md:1769 +#: src/api/reference.md:2964 msgid "`Session` — module" msgstr "`Session` — módulo" -#: src/api/reference.md:1738 src/api/reference.md:1768 +#: src/api/reference.md:1740 src/api/reference.md:1770 msgid "`connect` — instance _(class: `Session`)_" msgstr "`connect` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1739 src/api/reference.md:1769 +#: src/api/reference.md:1741 src/api/reference.md:1771 msgid "`connectToMainThread` — instance _(class: `Session`)_" msgstr "`connectToMainThread` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1740 +#: src/api/reference.md:1742 msgid "`debug` — module _(class: `console`)_" msgstr "`debug` — módulo _(clase: `console`)_" -#: src/api/reference.md:1741 src/api/reference.md:1770 +#: src/api/reference.md:1743 src/api/reference.md:1772 msgid "`disconnect` — instance _(class: `Session`)_" msgstr "`disconnect` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1742 +#: src/api/reference.md:1744 msgid "`error` — module _(class: `console`)_" msgstr "`error` — módulo _(clase: `console`)_" -#: src/api/reference.md:1743 +#: src/api/reference.md:1745 msgid "`info` — module _(class: `console`)_" msgstr "`info` — módulo _(clase: `console`)_" -#: src/api/reference.md:1744 +#: src/api/reference.md:1746 msgid "`log` — module _(class: `console`)_" msgstr "`log` — módulo _(clase: `console`)_" -#: src/api/reference.md:1745 src/api/reference.md:1771 +#: src/api/reference.md:1747 src/api/reference.md:1773 msgid "`on` — instance _(class: `Session`)_" msgstr "`on` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1746 src/api/reference.md:1772 +#: src/api/reference.md:1748 src/api/reference.md:1774 msgid "`once` — instance _(class: `Session`)_" msgstr "`once` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1747 +#: src/api/reference.md:1749 msgid "" "`open` — module ⚠ **stub** — accepts port/host but binds no real WebSocket " "inspector endpoint; sessions are in-process fakes (#4916)" @@ -41208,7 +40937,7 @@ msgstr "" "final de inspector real WebSocket; las sesiones son falsas en el proceso " "(#4916)" -#: src/api/reference.md:1748 +#: src/api/reference.md:1750 msgid "" "`post` — instance _(class: `Session`)_ ⚠ **stub** — only Runtime.enable and " "a canned Runtime.evaluate subset respond; every other protocol method throws " @@ -41218,7 +40947,7 @@ msgstr "" "un subconjunto enlatado Runtime.evaluate responden; cualquier otro método de " "protocolo lanza error Inspector -32601 (#4916)" -#: src/api/reference.md:1749 +#: src/api/reference.md:1751 msgid "" "`url` — module ⚠ **stub** — always undefined: Perry never exposes a real " "inspector endpoint (#4916)" @@ -41226,7 +40955,7 @@ msgstr "" "`url` — módulo ­ **talón** — siempre indefinido: Perry nunca expone un punto " "final de inspector real (#4916)" -#: src/api/reference.md:1750 +#: src/api/reference.md:1752 msgid "" "`waitForDebugger` — module ⚠ **stub** — returns immediately after open(); " "there is no debugger to wait for (#4916)" @@ -41234,3091 +40963,3106 @@ msgstr "" "`waitForDebugger` — módulo ­ **talón** — vuelve inmediatamente después de " "open(); no hay depurador que esperar (#4916)" -#: src/api/reference.md:1751 +#: src/api/reference.md:1753 msgid "`warn` — module _(class: `console`)_" msgstr "`warn` — módulo _(clase: `console`)_" -#: src/api/reference.md:1755 +#: src/api/reference.md:1757 msgid "`Network`" msgstr "`Network`" -#: src/api/reference.md:1759 +#: src/api/reference.md:1761 msgid "`inspector/promises`" msgstr "`inspector/promises`" -#: src/api/reference.md:1773 +#: src/api/reference.md:1775 msgid "`post` — instance _(class: `Session`)_" msgstr "`post` — instancia _(clase: `Session`)_" -#: src/api/reference.md:1779 +#: src/api/reference.md:1781 msgid "`lodash`" msgstr "`lodash`" -#: src/api/reference.md:1783 +#: src/api/reference.md:1785 msgid "`camelCase` — module" msgstr "`camelCase` — módulo" -#: src/api/reference.md:1784 +#: src/api/reference.md:1786 msgid "`chunk` — module" msgstr "`chunk` — módulo" -#: src/api/reference.md:1785 src/api/reference.md:1786 +#: src/api/reference.md:1787 src/api/reference.md:1788 msgid "`clamp` — module" msgstr "`clamp` — módulo" -#: src/api/reference.md:1787 +#: src/api/reference.md:1789 msgid "`compact` — module" msgstr "`compact` — módulo" -#: src/api/reference.md:1788 +#: src/api/reference.md:1790 msgid "`drop` — module" msgstr "`drop` — módulo" -#: src/api/reference.md:1789 +#: src/api/reference.md:1791 msgid "`first` — module" msgstr "`first` — módulo" -#: src/api/reference.md:1790 +#: src/api/reference.md:1792 msgid "`flatten` — module" msgstr "`flatten` — módulo" -#: src/api/reference.md:1791 +#: src/api/reference.md:1793 msgid "`head` — module" msgstr "`head` — módulo" -#: src/api/reference.md:1792 +#: src/api/reference.md:1794 msgid "`inRange` — module" msgstr "`inRange` — módulo" -#: src/api/reference.md:1793 +#: src/api/reference.md:1795 msgid "`kebabCase` — module" msgstr "`kebabCase` — módulo" -#: src/api/reference.md:1794 +#: src/api/reference.md:1796 msgid "`last` — module" msgstr "`last` — módulo" -#: src/api/reference.md:1795 +#: src/api/reference.md:1797 msgid "`max` — module" msgstr "`max` — módulo" -#: src/api/reference.md:1796 +#: src/api/reference.md:1798 msgid "`maxBy` — module" msgstr "`maxBy` — módulo" -#: src/api/reference.md:1797 +#: src/api/reference.md:1799 msgid "`mean` — module" msgstr "`mean` — módulo" -#: src/api/reference.md:1798 +#: src/api/reference.md:1800 msgid "`meanBy` — module" msgstr "`meanBy` — módulo" -#: src/api/reference.md:1799 +#: src/api/reference.md:1801 msgid "`min` — module" msgstr "`min` — módulo" -#: src/api/reference.md:1800 +#: src/api/reference.md:1802 msgid "`minBy` — module" msgstr "`minBy` — módulo" -#: src/api/reference.md:1801 +#: src/api/reference.md:1803 msgid "`random` — module" msgstr "`random` — módulo" -#: src/api/reference.md:1802 +#: src/api/reference.md:1804 msgid "`range` — module" msgstr "`range` — módulo" -#: src/api/reference.md:1804 +#: src/api/reference.md:1806 msgid "`size` — module" msgstr "`size` — módulo" -#: src/api/reference.md:1805 +#: src/api/reference.md:1807 msgid "`snakeCase` — module" msgstr "`snakeCase` — módulo" -#: src/api/reference.md:1806 +#: src/api/reference.md:1808 msgid "`sum` — module" msgstr "`sum` — módulo" -#: src/api/reference.md:1807 +#: src/api/reference.md:1809 msgid "`sumBy` — module" msgstr "`sumBy` — módulo" -#: src/api/reference.md:1808 +#: src/api/reference.md:1810 msgid "`tail` — module" msgstr "`tail` — módulo" -#: src/api/reference.md:1809 +#: src/api/reference.md:1811 msgid "`take` — module" msgstr "`take` — módulo" -#: src/api/reference.md:1810 +#: src/api/reference.md:1812 msgid "`times` — module" msgstr "`times` — módulo" -#: src/api/reference.md:1811 +#: src/api/reference.md:1813 msgid "`uniq` — module" msgstr "`uniq` — módulo" -#: src/api/reference.md:1813 +#: src/api/reference.md:1815 msgid "`module`" msgstr "`module`" -#: src/api/reference.md:1817 src/api/reference.md:1848 +#: src/api/reference.md:1819 src/api/reference.md:1850 msgid "`Module`" msgstr "`Module`" -#: src/api/reference.md:1818 +#: src/api/reference.md:1820 msgid "`SourceMap`" msgstr "`SourceMap`" -#: src/api/reference.md:1822 +#: src/api/reference.md:1824 msgid "`Module` — module" msgstr "`Module` — módulo" -#: src/api/reference.md:1823 +#: src/api/reference.md:1825 msgid "`SourceMap` — module" msgstr "`SourceMap` — módulo" -#: src/api/reference.md:1824 +#: src/api/reference.md:1826 msgid "`_findPath` — module" msgstr "`_findPath` — módulo" -#: src/api/reference.md:1825 +#: src/api/reference.md:1827 msgid "`_initPaths` — module" msgstr "`_initPaths` — módulo" -#: src/api/reference.md:1826 +#: src/api/reference.md:1828 msgid "`_load` — module" msgstr "`_load` — módulo" -#: src/api/reference.md:1827 +#: src/api/reference.md:1829 msgid "`_nodeModulePaths` — module" msgstr "`_nodeModulePaths` — módulo" -#: src/api/reference.md:1828 +#: src/api/reference.md:1830 msgid "`_preloadModules` — module" msgstr "`_preloadModules` — módulo" -#: src/api/reference.md:1829 +#: src/api/reference.md:1831 msgid "`_resolveFilename` — module" msgstr "`_resolveFilename` — módulo" -#: src/api/reference.md:1830 +#: src/api/reference.md:1832 msgid "`_resolveLookupPaths` — module" msgstr "`_resolveLookupPaths` — módulo" -#: src/api/reference.md:1831 +#: src/api/reference.md:1833 msgid "`createRequire` — module" msgstr "`createRequire` — módulo" -#: src/api/reference.md:1832 +#: src/api/reference.md:1834 msgid "`enableCompileCache` — module" msgstr "`enableCompileCache` — módulo" -#: src/api/reference.md:1833 +#: src/api/reference.md:1835 msgid "`findPackageJSON` — module" msgstr "`findPackageJSON` — módulo" -#: src/api/reference.md:1834 +#: src/api/reference.md:1836 msgid "`findSourceMap` — module" msgstr "`findSourceMap` — módulo" -#: src/api/reference.md:1835 +#: src/api/reference.md:1837 msgid "`flushCompileCache` — module" msgstr "`flushCompileCache` — módulo" -#: src/api/reference.md:1836 +#: src/api/reference.md:1838 msgid "`getCompileCacheDir` — module" msgstr "`getCompileCacheDir` — módulo" -#: src/api/reference.md:1837 +#: src/api/reference.md:1839 msgid "`getSourceMapsSupport` — module" msgstr "`getSourceMapsSupport` — módulo" -#: src/api/reference.md:1838 +#: src/api/reference.md:1840 msgid "`isBuiltin` — module" msgstr "`isBuiltin` — módulo" -#: src/api/reference.md:1839 +#: src/api/reference.md:1841 msgid "`register` — module" msgstr "`register` — módulo" -#: src/api/reference.md:1840 +#: src/api/reference.md:1842 msgid "`registerHooks` — module" msgstr "`registerHooks` — módulo" -#: src/api/reference.md:1841 +#: src/api/reference.md:1843 msgid "`runMain` — module" msgstr "`runMain` — módulo" -#: src/api/reference.md:1842 +#: src/api/reference.md:1844 msgid "`setSourceMapsSupport` — module" msgstr "`setSourceMapsSupport` — módulo" -#: src/api/reference.md:1843 +#: src/api/reference.md:1845 msgid "`stripTypeScriptTypes` — module" msgstr "`stripTypeScriptTypes` — módulo" -#: src/api/reference.md:1844 +#: src/api/reference.md:1846 msgid "`syncBuiltinESMExports` — module" msgstr "`syncBuiltinESMExports` — módulo" -#: src/api/reference.md:1849 +#: src/api/reference.md:1851 msgid "`_cache`" msgstr "`_cache`" -#: src/api/reference.md:1850 +#: src/api/reference.md:1852 msgid "`_extensions`" msgstr "`_extensions`" -#: src/api/reference.md:1851 +#: src/api/reference.md:1853 msgid "`_pathCache`" msgstr "`_pathCache`" -#: src/api/reference.md:1852 src/api/reference.md:2897 +#: src/api/reference.md:1854 src/api/reference.md:2906 msgid "`builtinModules`" msgstr "`builtinModules`" -#: src/api/reference.md:1855 +#: src/api/reference.md:1857 msgid "`globalPaths`" msgstr "`globalPaths`" -#: src/api/reference.md:1861 +#: src/api/reference.md:1863 msgid "`BlockList`" msgstr "`BlockList`" -#: src/api/reference.md:1864 +#: src/api/reference.md:1866 msgid "`SocketAddress`" msgstr "`SocketAddress`" -#: src/api/reference.md:1865 src/api/reference.md:3011 +#: src/api/reference.md:1867 src/api/reference.md:3020 msgid "`Stream`" msgstr "`Stream`" -#: src/api/reference.md:1869 +#: src/api/reference.md:1871 msgid "`BlockList` — module" msgstr "`BlockList` — módulo" -#: src/api/reference.md:1872 +#: src/api/reference.md:1874 msgid "`SocketAddress` — module" msgstr "`SocketAddress` — módulo" -#: src/api/reference.md:1873 +#: src/api/reference.md:1875 msgid "`Stream` — module" msgstr "`Stream` — módulo" -#: src/api/reference.md:1874 +#: src/api/reference.md:1876 msgid "`__set_dropMaxConnection` — instance _(class: `Server`)_" msgstr "`__set_dropMaxConnection` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1875 +#: src/api/reference.md:1877 msgid "`__set_maxConnections` — instance _(class: `Server`)_" msgstr "`__set_maxConnections` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1876 +#: src/api/reference.md:1878 msgid "`_createServerHandle` — module" msgstr "`_createServerHandle` — módulo" -#: src/api/reference.md:1877 +#: src/api/reference.md:1879 msgid "`_normalizeArgs` — module" msgstr "`_normalizeArgs` — módulo" -#: src/api/reference.md:1878 +#: src/api/reference.md:1880 #, fuzzy msgid "`_readableState` — instance" msgstr "`readyState` — instancia" -#: src/api/reference.md:1879 +#: src/api/reference.md:1881 #, fuzzy msgid "`_writableState` — instance" msgstr "`writable` — instancia" -#: src/api/reference.md:1880 +#: src/api/reference.md:1882 msgid "`addAddress` — instance _(class: `BlockList`)_" msgstr "`addAddress` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1882 src/api/reference.md:3301 +#: src/api/reference.md:1884 src/api/reference.md:3310 msgid "`addListener` — instance _(class: `Server`)_" msgstr "`addListener` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1883 +#: src/api/reference.md:1885 msgid "`addRange` — instance _(class: `BlockList`)_" msgstr "`addRange` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1884 +#: src/api/reference.md:1886 msgid "`addSubnet` — instance _(class: `BlockList`)_" msgstr "`addSubnet` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1886 +#: src/api/reference.md:1888 msgid "`address` — instance _(class: `SocketAddress`)_" msgstr "`address` — instancia _(clase: `SocketAddress`)_" -#: src/api/reference.md:1887 src/api/reference.md:3302 +#: src/api/reference.md:1889 src/api/reference.md:3311 msgid "`address` — instance _(class: `Server`)_" msgstr "`address` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1888 +#: src/api/reference.md:1890 msgid "`autoSelectFamilyAttemptedAddresses` — instance _(class: `Socket`)_" msgstr "`autoSelectFamilyAttemptedAddresses` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1889 +#: src/api/reference.md:1891 msgid "`bufferSize` — instance _(class: `Socket`)_" msgstr "`bufferSize` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1890 +#: src/api/reference.md:1892 msgid "`bytesRead` — instance _(class: `Socket`)_" msgstr "`bytesRead` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1891 +#: src/api/reference.md:1893 msgid "`bytesWritten` — instance _(class: `Socket`)_" msgstr "`bytesWritten` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1892 +#: src/api/reference.md:1894 msgid "`check` — instance _(class: `BlockList`)_" msgstr "`check` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1893 src/api/reference.md:3304 +#: src/api/reference.md:1895 src/api/reference.md:3313 msgid "`close` — instance _(class: `Server`)_" msgstr "`close` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1896 +#: src/api/reference.md:1898 msgid "`connecting` — instance _(class: `Socket`)_" msgstr "`connecting` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1897 +#: src/api/reference.md:1899 msgid "`cork` — instance _(class: `Socket`)_" msgstr "`cork` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1898 +#: src/api/reference.md:1900 msgid "`createConnection` — module" msgstr "`createConnection` — módulo" -#: src/api/reference.md:1900 +#: src/api/reference.md:1902 msgid "`destroy` — instance _(class: `Socket`)_" msgstr "`destroy` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1901 +#: src/api/reference.md:1903 msgid "`destroyed` — instance _(class: `Socket`)_" msgstr "`destroyed` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1902 +#: src/api/reference.md:1904 msgid "`dropMaxConnection` — instance _(class: `Server`)_" msgstr "`dropMaxConnection` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1903 +#: src/api/reference.md:1905 msgid "`end` — instance _(class: `Socket`)_" msgstr "`end` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1905 src/api/reference.md:3309 +#: src/api/reference.md:1907 src/api/reference.md:3318 msgid "`eventNames` — instance _(class: `Server`)_" msgstr "`eventNames` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1906 +#: src/api/reference.md:1908 msgid "`exportKeyingMaterial` — instance _(class: `Socket`)_" msgstr "`exportKeyingMaterial` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1907 +#: src/api/reference.md:1909 msgid "`family` — instance _(class: `SocketAddress`)_" msgstr "`family` — instancia _(clase: `SocketAddress`)_" -#: src/api/reference.md:1908 +#: src/api/reference.md:1910 msgid "`flowlabel` — instance _(class: `SocketAddress`)_" msgstr "`flowlabel` — instancia _(clase: `SocketAddress`)_" -#: src/api/reference.md:1909 +#: src/api/reference.md:1911 msgid "`fromJSON` — instance _(class: `BlockList`)_" msgstr "`fromJSON` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1910 +#: src/api/reference.md:1912 msgid "`getCertificate` — instance _(class: `Socket`)_" msgstr "`getCertificate` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1911 +#: src/api/reference.md:1913 msgid "`getCipher` — instance _(class: `Socket`)_" msgstr "`getCipher` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1912 +#: src/api/reference.md:1914 msgid "`getConnections` — instance _(class: `Server`)_" msgstr "`getConnections` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1913 +#: src/api/reference.md:1915 msgid "`getDefaultAutoSelectFamily` — module" msgstr "`getDefaultAutoSelectFamily` — módulo" -#: src/api/reference.md:1914 +#: src/api/reference.md:1916 msgid "`getDefaultAutoSelectFamilyAttemptTimeout` — module" msgstr "`getDefaultAutoSelectFamilyAttemptTimeout` — módulo" -#: src/api/reference.md:1915 +#: src/api/reference.md:1917 msgid "`getEphemeralKeyInfo` — instance _(class: `Socket`)_" msgstr "`getEphemeralKeyInfo` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1916 +#: src/api/reference.md:1918 msgid "`getFinished` — instance _(class: `Socket`)_" msgstr "`getFinished` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1917 +#: src/api/reference.md:1919 msgid "`getPeerCertificate` — instance _(class: `Socket`)_" msgstr "`getPeerCertificate` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1918 +#: src/api/reference.md:1920 msgid "`getPeerFinished` — instance _(class: `Socket`)_" msgstr "`getPeerFinished` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1919 +#: src/api/reference.md:1921 msgid "`getPeerX509Certificate` — instance _(class: `Socket`)_" msgstr "`getPeerX509Certificate` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1920 +#: src/api/reference.md:1922 msgid "`getProtocol` — instance _(class: `Socket`)_" msgstr "`getProtocol` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1921 +#: src/api/reference.md:1923 msgid "`getSession` — instance _(class: `Socket`)_" msgstr "`getSession` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1922 +#: src/api/reference.md:1924 msgid "`getSharedSigalgs` — instance _(class: `Socket`)_" msgstr "`getSharedSigalgs` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1923 +#: src/api/reference.md:1925 msgid "`getTypeOfService` — instance _(class: `Socket`)_" msgstr "`getTypeOfService` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1924 +#: src/api/reference.md:1926 msgid "`getX509Certificate` — instance _(class: `Socket`)_" msgstr "`getX509Certificate` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1925 +#: src/api/reference.md:1927 msgid "`isBlockList` — module _(class: `BlockList`)_" msgstr "`isBlockList` — módulo _(clase: `BlockList`)_" -#: src/api/reference.md:1926 +#: src/api/reference.md:1928 msgid "`isIP` — module" msgstr "`isIP` — módulo" -#: src/api/reference.md:1927 +#: src/api/reference.md:1929 msgid "`isIPv4` — module" msgstr "`isIPv4` — módulo" -#: src/api/reference.md:1928 +#: src/api/reference.md:1930 msgid "`isIPv6` — module" msgstr "`isIPv6` — módulo" -#: src/api/reference.md:1929 +#: src/api/reference.md:1931 msgid "`isSessionReused` — instance _(class: `Socket`)_" msgstr "`isSessionReused` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1930 +#: src/api/reference.md:1932 #, fuzzy msgid "`isSocketAddress` — module _(class: `SocketAddress`)_" msgstr "`parse` — módulo _(clase: `SocketAddress`)_" -#: src/api/reference.md:1931 src/api/reference.md:3314 +#: src/api/reference.md:1933 src/api/reference.md:3323 msgid "`listen` — instance _(class: `Server`)_" msgstr "`listen` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1933 src/api/reference.md:3315 +#: src/api/reference.md:1935 src/api/reference.md:3324 msgid "`listenerCount` — instance _(class: `Server`)_" msgstr "`listenerCount` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1934 +#: src/api/reference.md:1936 msgid "`listeners` — instance _(class: `Socket`)_" msgstr "`listeners` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1935 +#: src/api/reference.md:1937 msgid "`listeners` — instance _(class: `Server`)_" msgstr "`listeners` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1936 +#: src/api/reference.md:1938 msgid "`listening` — instance _(class: `Server`)_" msgstr "`listening` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1937 +#: src/api/reference.md:1939 msgid "`localAddress` — instance _(class: `Socket`)_" msgstr "`localAddress` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1938 +#: src/api/reference.md:1940 msgid "`localFamily` — instance _(class: `Socket`)_" msgstr "`localFamily` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1939 +#: src/api/reference.md:1941 msgid "`localPort` — instance _(class: `Socket`)_" msgstr "`localPort` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1940 +#: src/api/reference.md:1942 msgid "`maxConnections` — instance _(class: `Server`)_" msgstr "`maxConnections` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1942 src/api/reference.md:3316 +#: src/api/reference.md:1944 src/api/reference.md:3325 msgid "`off` — instance _(class: `Server`)_" msgstr "`off` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1945 src/api/reference.md:3318 +#: src/api/reference.md:1947 src/api/reference.md:3327 msgid "`once` — instance _(class: `Server`)_" msgstr "`once` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1946 +#: src/api/reference.md:1948 msgid "`parse` — module _(class: `SocketAddress`)_" msgstr "`parse` — módulo _(clase: `SocketAddress`)_" -#: src/api/reference.md:1947 +#: src/api/reference.md:1949 msgid "`pause` — instance _(class: `Socket`)_" msgstr "`pause` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1948 +#: src/api/reference.md:1950 msgid "`pending` — instance _(class: `Socket`)_" msgstr "`pending` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1949 +#: src/api/reference.md:1951 #, fuzzy msgid "`pipe` — instance _(class: `Socket`)_" msgstr "`pause` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1950 +#: src/api/reference.md:1952 msgid "`port` — instance _(class: `SocketAddress`)_" msgstr "`port` — instancia _(clase: `SocketAddress`)_" -#: src/api/reference.md:1951 +#: src/api/reference.md:1953 #, fuzzy msgid "`prependListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1952 +#: src/api/reference.md:1954 #, fuzzy msgid "`prependOnceListener` — instance _(class: `Socket`)_" msgstr "`removeListener` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1953 +#: src/api/reference.md:1955 msgid "`rawListeners` — instance _(class: `Socket`)_" msgstr "`rawListeners` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1954 +#: src/api/reference.md:1956 msgid "`rawListeners` — instance _(class: `Server`)_" msgstr "`rawListeners` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1955 +#: src/api/reference.md:1957 #, fuzzy msgid "`read` — instance _(class: `Socket`)_" msgstr "`end` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1956 src/api/reference.md:3056 +#: src/api/reference.md:1958 src/api/reference.md:3065 msgid "`readable` — instance" msgstr "`readable` — instancia" -#: src/api/reference.md:1957 src/api/reference.md:3060 +#: src/api/reference.md:1959 src/api/reference.md:3069 msgid "`readableEnded` — instance" msgstr "`readableEnded` — instancia" -#: src/api/reference.md:1958 +#: src/api/reference.md:1960 msgid "`readyState` — instance _(class: `Socket`)_" msgstr "`readyState` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1961 +#: src/api/reference.md:1963 msgid "`remoteFamily` — instance _(class: `Socket`)_" msgstr "`remoteFamily` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1962 +#: src/api/reference.md:1964 msgid "`remotePort` — instance _(class: `Socket`)_" msgstr "`remotePort` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1963 +#: src/api/reference.md:1965 msgid "`removeAllListeners` — instance _(class: `Socket`)_" msgstr "`removeAllListeners` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1964 src/api/reference.md:3319 +#: src/api/reference.md:1966 src/api/reference.md:3328 msgid "`removeAllListeners` — instance _(class: `Server`)_" msgstr "`removeAllListeners` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1966 src/api/reference.md:3320 +#: src/api/reference.md:1968 src/api/reference.md:3329 msgid "`removeListener` — instance _(class: `Server`)_" msgstr "`removeListener` — instancia _(clase: `Server`)_" -#: src/api/reference.md:1967 +#: src/api/reference.md:1969 msgid "`resetAndDestroy` — instance _(class: `Socket`)_" msgstr "`resetAndDestroy` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1968 +#: src/api/reference.md:1970 msgid "`resume` — instance _(class: `Socket`)_" msgstr "`resume` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1969 +#: src/api/reference.md:1971 msgid "`rules` — instance _(class: `BlockList`)_" msgstr "`rules` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1970 +#: src/api/reference.md:1972 msgid "`setDefaultAutoSelectFamily` — module" msgstr "`setDefaultAutoSelectFamily` — módulo" -#: src/api/reference.md:1971 +#: src/api/reference.md:1973 msgid "`setDefaultAutoSelectFamilyAttemptTimeout` — module" msgstr "`setDefaultAutoSelectFamilyAttemptTimeout` — módulo" -#: src/api/reference.md:1972 +#: src/api/reference.md:1974 msgid "`setDefaultEncoding` — instance _(class: `Socket`)_" msgstr "`setDefaultEncoding` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1973 +#: src/api/reference.md:1975 msgid "`setEncoding` — instance _(class: `Socket`)_" msgstr "`setEncoding` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1974 +#: src/api/reference.md:1976 msgid "`setKeepAlive` — instance _(class: `Socket`)_" msgstr "`setKeepAlive` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1975 +#: src/api/reference.md:1977 msgid "`setKeyCert` — instance _(class: `Socket`)_" msgstr "`setKeyCert` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1976 +#: src/api/reference.md:1978 msgid "`setMaxSendFragment` — instance _(class: `Socket`)_" msgstr "`setMaxSendFragment` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1977 +#: src/api/reference.md:1979 msgid "`setNoDelay` — instance _(class: `Socket`)_" msgstr "`setNoDelay` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1978 +#: src/api/reference.md:1980 msgid "`setTimeout` — instance _(class: `Socket`)_" msgstr "`setTimeout` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1979 +#: src/api/reference.md:1981 msgid "`setTypeOfService` — instance _(class: `Socket`)_" msgstr "`setTypeOfService` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1980 +#: src/api/reference.md:1982 msgid "`timeout` — instance _(class: `Socket`)_" msgstr "`timeout` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1981 +#: src/api/reference.md:1983 msgid "`toJSON` — instance _(class: `BlockList`)_" msgstr "`toJSON` — instancia _(clase: `BlockList`)_" -#: src/api/reference.md:1982 +#: src/api/reference.md:1984 msgid "`uncork` — instance _(class: `Socket`)_" msgstr "`uncork` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1983 +#: src/api/reference.md:1985 #, fuzzy msgid "`unpipe` — instance _(class: `Socket`)_" msgstr "`unref` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1985 +#: src/api/reference.md:1987 msgid "`upgradeToTLS` — instance _(class: `Socket`)_" msgstr "`upgradeToTLS` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1986 src/api/reference.md:3074 +#: src/api/reference.md:1988 src/api/reference.md:3083 msgid "`writable` — instance" msgstr "`writable` — instancia" -#: src/api/reference.md:1987 +#: src/api/reference.md:1989 #, fuzzy msgid "`writableCorked` — instance _(class: `Socket`)_" msgstr "`write` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1988 src/api/reference.md:3076 +#: src/api/reference.md:1990 src/api/reference.md:3085 msgid "`writableEnded` — instance" msgstr "`writableEnded` — instancia" -#: src/api/reference.md:1989 +#: src/api/reference.md:1991 msgid "`write` — instance _(class: `Socket`)_" msgstr "`write` — instancia _(clase: `Socket`)_" -#: src/api/reference.md:1991 +#: src/api/reference.md:1993 msgid "`node-fetch`" msgstr "`node-fetch`" -#: src/api/reference.md:2005 +#: src/api/reference.md:2007 msgid "`node-pty`" msgstr "`node-pty`" -#: src/api/reference.md:2019 +#: src/api/reference.md:2021 msgid "`createTransport` — module" msgstr "`createTransport` — módulo" -#: src/api/reference.md:2020 +#: src/api/reference.md:2022 msgid "`sendMail` — instance" msgstr "`sendMail` — instancia" -#: src/api/reference.md:2021 +#: src/api/reference.md:2023 msgid "`verify` — instance" msgstr "`verify` — instancia" -#: src/api/reference.md:2023 +#: src/api/reference.md:2025 msgid "`os`" msgstr "`os`" -#: src/api/reference.md:2027 +#: src/api/reference.md:2029 msgid "`arch` — module" msgstr "`arch` — módulo" -#: src/api/reference.md:2028 +#: src/api/reference.md:2030 msgid "`availableParallelism` — module" msgstr "`availableParallelism` — módulo" -#: src/api/reference.md:2029 +#: src/api/reference.md:2031 msgid "`cpus` — module" msgstr "`cpus` — módulo" -#: src/api/reference.md:2030 +#: src/api/reference.md:2032 msgid "`endianness` — module" msgstr "`endianness` — módulo" -#: src/api/reference.md:2031 +#: src/api/reference.md:2033 msgid "`freemem` — module" msgstr "`freemem` — módulo" -#: src/api/reference.md:2032 +#: src/api/reference.md:2034 msgid "`getPriority` — module" msgstr "`getPriority` — módulo" -#: src/api/reference.md:2033 +#: src/api/reference.md:2035 msgid "`homedir` — module" msgstr "`homedir` — módulo" -#: src/api/reference.md:2034 +#: src/api/reference.md:2036 msgid "`hostname` — module" msgstr "`hostname` — módulo" -#: src/api/reference.md:2035 +#: src/api/reference.md:2037 msgid "`loadavg` — module" msgstr "`loadavg` — módulo" -#: src/api/reference.md:2036 +#: src/api/reference.md:2038 msgid "`machine` — module" msgstr "`machine` — módulo" -#: src/api/reference.md:2037 +#: src/api/reference.md:2039 msgid "`networkInterfaces` — module" msgstr "`networkInterfaces` — módulo" -#: src/api/reference.md:2038 +#: src/api/reference.md:2040 msgid "`platform` — module" msgstr "`platform` — módulo" -#: src/api/reference.md:2039 +#: src/api/reference.md:2041 msgid "`release` — module" msgstr "`release` — módulo" -#: src/api/reference.md:2040 +#: src/api/reference.md:2042 msgid "`setPriority` — module" msgstr "`setPriority` — módulo" -#: src/api/reference.md:2041 +#: src/api/reference.md:2043 msgid "`tmpdir` — module" msgstr "`tmpdir` — módulo" -#: src/api/reference.md:2042 +#: src/api/reference.md:2044 msgid "`totalmem` — module" msgstr "`totalmem` — módulo" -#: src/api/reference.md:2043 +#: src/api/reference.md:2045 msgid "`type` — module" msgstr "`type` — módulo" -#: src/api/reference.md:2044 src/api/reference.md:2767 +#: src/api/reference.md:2046 src/api/reference.md:2772 msgid "`uptime` — module" msgstr "`uptime` — módulo" -#: src/api/reference.md:2045 +#: src/api/reference.md:2047 msgid "`userInfo` — module" msgstr "`userInfo` — módulo" -#: src/api/reference.md:2046 +#: src/api/reference.md:2048 msgid "`version` — module" msgstr "`version` — módulo" -#: src/api/reference.md:2050 +#: src/api/reference.md:2052 msgid "`EOL`" msgstr "`EOL`" -#: src/api/reference.md:2053 +#: src/api/reference.md:2055 msgid "`devNull`" msgstr "`devNull`" -#: src/api/reference.md:2055 +#: src/api/reference.md:2057 msgid "`path`" msgstr "`path`" -#: src/api/reference.md:2059 src/api/reference.md:2085 -#: src/api/reference.md:2111 +#: src/api/reference.md:2061 src/api/reference.md:2087 +#: src/api/reference.md:2113 msgid "`_makeLong` — module" msgstr "`_makeLong` — módulo" -#: src/api/reference.md:2060 src/api/reference.md:2086 -#: src/api/reference.md:2112 +#: src/api/reference.md:2062 src/api/reference.md:2088 +#: src/api/reference.md:2114 msgid "`basename` — module" msgstr "`basename` — módulo" -#: src/api/reference.md:2061 src/api/reference.md:2087 -#: src/api/reference.md:2113 +#: src/api/reference.md:2063 src/api/reference.md:2089 +#: src/api/reference.md:2115 msgid "`dirname` — module" msgstr "`dirname` — módulo" -#: src/api/reference.md:2062 src/api/reference.md:2088 -#: src/api/reference.md:2114 +#: src/api/reference.md:2064 src/api/reference.md:2090 +#: src/api/reference.md:2116 msgid "`extname` — module" msgstr "`extname` — módulo" -#: src/api/reference.md:2063 src/api/reference.md:2089 -#: src/api/reference.md:2115 src/api/reference.md:3186 -#: src/api/reference.md:3413 src/api/reference.md:3448 +#: src/api/reference.md:2065 src/api/reference.md:2091 +#: src/api/reference.md:2117 src/api/reference.md:3195 +#: src/api/reference.md:3433 src/api/reference.md:3468 msgid "`format` — module" msgstr "`format` — módulo" -#: src/api/reference.md:2064 src/api/reference.md:2090 -#: src/api/reference.md:2116 +#: src/api/reference.md:2066 src/api/reference.md:2092 +#: src/api/reference.md:2118 msgid "`isAbsolute` — module" msgstr "`isAbsolute` — módulo" -#: src/api/reference.md:2065 src/api/reference.md:2091 -#: src/api/reference.md:2117 +#: src/api/reference.md:2067 src/api/reference.md:2093 +#: src/api/reference.md:2119 msgid "`join` — module" msgstr "`join` — módulo" -#: src/api/reference.md:2066 src/api/reference.md:2092 -#: src/api/reference.md:2118 +#: src/api/reference.md:2068 src/api/reference.md:2094 +#: src/api/reference.md:2120 msgid "`matchesGlob` — module" msgstr "`matchesGlob` — módulo" -#: src/api/reference.md:2067 src/api/reference.md:2093 -#: src/api/reference.md:2119 +#: src/api/reference.md:2069 src/api/reference.md:2095 +#: src/api/reference.md:2121 msgid "`normalize` — module" msgstr "`normalize` — módulo" -#: src/api/reference.md:2068 src/api/reference.md:2094 -#: src/api/reference.md:2120 src/api/reference.md:2825 -#: src/api/reference.md:3414 +#: src/api/reference.md:2070 src/api/reference.md:2096 +#: src/api/reference.md:2122 src/api/reference.md:2830 +#: src/api/reference.md:3434 msgid "`parse` — module" msgstr "`parse` — módulo" -#: src/api/reference.md:2069 src/api/reference.md:2095 -#: src/api/reference.md:2121 +#: src/api/reference.md:2071 src/api/reference.md:2097 +#: src/api/reference.md:2123 msgid "`relative` — module" msgstr "`relative` — módulo" -#: src/api/reference.md:2071 src/api/reference.md:2097 -#: src/api/reference.md:2123 +#: src/api/reference.md:2073 src/api/reference.md:2099 +#: src/api/reference.md:2125 msgid "`toNamespacedPath` — module" msgstr "`toNamespacedPath` — módulo" -#: src/api/reference.md:2076 src/api/reference.md:2102 -#: src/api/reference.md:2128 +#: src/api/reference.md:2078 src/api/reference.md:2104 +#: src/api/reference.md:2130 msgid "`delimiter`" msgstr "`delimiter`" -#: src/api/reference.md:2077 src/api/reference.md:2103 -#: src/api/reference.md:2129 +#: src/api/reference.md:2079 src/api/reference.md:2105 +#: src/api/reference.md:2131 msgid "`posix`" msgstr "`posix`" -#: src/api/reference.md:2078 src/api/reference.md:2104 -#: src/api/reference.md:2130 +#: src/api/reference.md:2080 src/api/reference.md:2106 +#: src/api/reference.md:2132 msgid "`sep`" msgstr "`sep`" -#: src/api/reference.md:2079 src/api/reference.md:2105 -#: src/api/reference.md:2131 +#: src/api/reference.md:2081 src/api/reference.md:2107 +#: src/api/reference.md:2133 msgid "`win32`" msgstr "`win32`" -#: src/api/reference.md:2081 +#: src/api/reference.md:2083 msgid "`path/posix`" msgstr "`path/posix`" -#: src/api/reference.md:2107 +#: src/api/reference.md:2109 msgid "`path/win32`" msgstr "`path/win32`" -#: src/api/reference.md:2133 +#: src/api/reference.md:2135 msgid "`perf_hooks`" msgstr "`perf_hooks`" -#: src/api/reference.md:2137 +#: src/api/reference.md:2139 msgid "`Performance`" msgstr "`Performance`" -#: src/api/reference.md:2138 +#: src/api/reference.md:2140 msgid "`PerformanceEntry`" msgstr "`PerformanceEntry`" -#: src/api/reference.md:2139 +#: src/api/reference.md:2141 msgid "`PerformanceMark`" msgstr "`PerformanceMark`" -#: src/api/reference.md:2140 +#: src/api/reference.md:2142 msgid "`PerformanceMeasure`" msgstr "`PerformanceMeasure`" -#: src/api/reference.md:2141 +#: src/api/reference.md:2143 msgid "`PerformanceObserver`" msgstr "`PerformanceObserver`" -#: src/api/reference.md:2142 +#: src/api/reference.md:2144 msgid "`PerformanceObserverEntryList`" msgstr "`PerformanceObserverEntryList`" -#: src/api/reference.md:2143 +#: src/api/reference.md:2145 msgid "`PerformanceResourceTiming`" msgstr "`PerformanceResourceTiming`" -#: src/api/reference.md:2147 +#: src/api/reference.md:2149 msgid "`createHistogram` — module" msgstr "`createHistogram` — módulo" -#: src/api/reference.md:2148 +#: src/api/reference.md:2150 msgid "`disconnect` — instance _(class: `PerformanceObserver`)_" msgstr "`disconnect` — instancia _(clase: `PerformanceObserver`)_" -#: src/api/reference.md:2149 +#: src/api/reference.md:2151 msgid "`eventLoopUtilization` — module" msgstr "`eventLoopUtilization` — módulo" -#: src/api/reference.md:2150 +#: src/api/reference.md:2152 msgid "`monitorEventLoopDelay` — module" msgstr "`monitorEventLoopDelay` — módulo" -#: src/api/reference.md:2151 +#: src/api/reference.md:2153 msgid "`observe` — instance _(class: `PerformanceObserver`)_" msgstr "`observe` — instancia _(clase: `PerformanceObserver`)_" -#: src/api/reference.md:2152 +#: src/api/reference.md:2154 msgid "`takeRecords` — instance _(class: `PerformanceObserver`)_" msgstr "`takeRecords` — instancia _(clase: `PerformanceObserver`)_" -#: src/api/reference.md:2153 +#: src/api/reference.md:2155 msgid "`timerify` — module" msgstr "`timerify` — módulo" -#: src/api/reference.md:2159 +#: src/api/reference.md:2161 msgid "`performance`" msgstr "`performance`" -#: src/api/reference.md:2161 src/contributing/architecture.md:26 +#: src/api/reference.md:2163 src/contributing/architecture.md:27 msgid "`perry`" msgstr "`perry`" -#: src/api/reference.md:2165 +#: src/api/reference.md:2167 msgid "`embeddedFiles` — module" msgstr "`embeddedFiles` — módulo" -#: src/api/reference.md:2166 +#: src/api/reference.md:2168 msgid "`readEmbedded` — module" msgstr "`readEmbedded` — módulo" -#: src/api/reference.md:2176 +#: src/api/reference.md:2178 msgid "`js_ads_banner_create` — module" msgstr "`js_ads_banner_create` — módulo" -#: src/api/reference.md:2177 +#: src/api/reference.md:2179 msgid "`js_ads_banner_destroy` — module" msgstr "`js_ads_banner_destroy` — módulo" -#: src/api/reference.md:2178 +#: src/api/reference.md:2180 msgid "`js_ads_interstitial_load` — module" msgstr "`js_ads_interstitial_load` — módulo" -#: src/api/reference.md:2179 +#: src/api/reference.md:2181 msgid "`js_ads_interstitial_show` — module" msgstr "`js_ads_interstitial_show` — módulo" -#: src/api/reference.md:2180 +#: src/api/reference.md:2182 msgid "`js_ads_request_consent` — module" msgstr "`js_ads_request_consent` — módulo" -#: src/api/reference.md:2181 +#: src/api/reference.md:2183 msgid "`js_ads_rewarded_load` — module" msgstr "`js_ads_rewarded_load` — módulo" -#: src/api/reference.md:2182 +#: src/api/reference.md:2184 msgid "`js_ads_rewarded_show` — module" msgstr "`js_ads_rewarded_show` — módulo" -#: src/api/reference.md:2184 +#: src/api/reference.md:2186 msgid "`perry/audio`" msgstr "`perry/audio`" -#: src/api/reference.md:2188 +#: src/api/reference.md:2190 msgid "`createBus` — module" msgstr "`createBus` — módulo" -#: src/api/reference.md:2189 +#: src/api/reference.md:2191 msgid "`crossfade` — module" msgstr "`crossfade` — módulo" -#: src/api/reference.md:2190 +#: src/api/reference.md:2192 msgid "`destroyBus` — module" msgstr "`destroyBus` — módulo" -#: src/api/reference.md:2191 +#: src/api/reference.md:2193 msgid "`fadeIn` — module" msgstr "`fadeIn` — módulo" -#: src/api/reference.md:2192 +#: src/api/reference.md:2194 msgid "`fadeOut` — module" msgstr "`fadeOut` — módulo" -#: src/api/reference.md:2193 src/api/reference.md:2332 +#: src/api/reference.md:2195 src/api/reference.md:2337 msgid "`getDuration` — module" msgstr "`getDuration` — módulo" -#: src/api/reference.md:2194 +#: src/api/reference.md:2196 msgid "`getPosition` — module" msgstr "`getPosition` — módulo" -#: src/api/reference.md:2195 src/api/reference.md:2334 +#: src/api/reference.md:2197 src/api/reference.md:2339 msgid "`isPlaying` — module" msgstr "`isPlaying` — módulo" -#: src/api/reference.md:2196 +#: src/api/reference.md:2198 msgid "`loadSound` — module" msgstr "`loadSound` — módulo" -#: src/api/reference.md:2197 +#: src/api/reference.md:2199 msgid "`muteBus` — module" msgstr "`muteBus` — módulo" -#: src/api/reference.md:2198 +#: src/api/reference.md:2200 msgid "`onEnded` — module" msgstr "`onEnded` — módulo" -#: src/api/reference.md:2199 +#: src/api/reference.md:2201 msgid "`onLoaded` — module" msgstr "`onLoaded` — módulo" -#: src/api/reference.md:2200 src/api/reference.md:2337 +#: src/api/reference.md:2202 src/api/reference.md:2342 msgid "`pause` — module" msgstr "`pause` — módulo" -#: src/api/reference.md:2201 src/api/reference.md:2338 +#: src/api/reference.md:2203 src/api/reference.md:2343 msgid "`play` — module" msgstr "`play` — módulo" -#: src/api/reference.md:2202 +#: src/api/reference.md:2204 msgid "`resume` — module" msgstr "`resume` — módulo" -#: src/api/reference.md:2203 +#: src/api/reference.md:2205 msgid "`resumeAll` — module" msgstr "`resumeAll` — módulo" -#: src/api/reference.md:2204 +#: src/api/reference.md:2206 msgid "`setMasterVolume` — module" msgstr "`setMasterVolume` — módulo" -#: src/api/reference.md:2205 +#: src/api/reference.md:2207 msgid "`setPan` — module" msgstr "`setPan` — módulo" -#: src/api/reference.md:2206 src/api/reference.md:2341 +#: src/api/reference.md:2208 src/api/reference.md:2346 msgid "`setRate` — module" msgstr "`setRate` — módulo" -#: src/api/reference.md:2207 src/api/reference.md:2342 +#: src/api/reference.md:2209 src/api/reference.md:2347 msgid "`setVolume` — module" msgstr "`setVolume` — módulo" -#: src/api/reference.md:2208 +#: src/api/reference.md:2210 msgid "`soloBus` — module" msgstr "`soloBus` — módulo" -#: src/api/reference.md:2209 src/api/reference.md:2247 -#: src/api/reference.md:2276 src/api/reference.md:2289 -#: src/api/reference.md:2343 +#: src/api/reference.md:2211 src/api/reference.md:2252 +#: src/api/reference.md:2281 src/api/reference.md:2294 +#: src/api/reference.md:2348 msgid "`stop` — module" msgstr "`stop` — módulo" -#: src/api/reference.md:2210 +#: src/api/reference.md:2212 msgid "`suspend` — module" msgstr "`suspend` — módulo" -#: src/api/reference.md:2211 +#: src/api/reference.md:2213 msgid "`unload` — module" msgstr "`unload` — módulo" -#: src/api/reference.md:2213 +#: src/api/reference.md:2215 msgid "`perry/background`" msgstr "`perry/background`" -#: src/api/reference.md:2217 +#: src/api/reference.md:2219 msgid "`cancel` — module" msgstr "`cancel` — módulo" -#: src/api/reference.md:2218 +#: src/api/reference.md:2220 msgid "`registerTask` — module" msgstr "`registerTask` — módulo" -#: src/api/reference.md:2219 +#: src/api/reference.md:2221 msgid "`schedule` — module" msgstr "`schedule` — módulo" -#: src/api/reference.md:2221 +#: src/api/reference.md:2223 #, fuzzy msgid "`perry/component`" msgstr "`perry/compose`" -#: src/api/reference.md:2225 +#: src/api/reference.md:2227 #, fuzzy msgid "`callComponent` — module" msgstr "`compose` — módulo" -#: src/api/reference.md:2226 +#: src/api/reference.md:2228 #, fuzzy msgid "`callComponentImport` — module" msgstr "`composeUp` — módulo" -#: src/api/reference.md:2227 +#: src/api/reference.md:2229 +#, fuzzy +msgid "`closeComponentHttpServer` — module" +msgstr "`compose` — módulo" + +#: src/api/reference.md:2230 #, fuzzy msgid "`createComponentHostResource` — module" msgstr "`createInterface` — módulo" -#: src/api/reference.md:2228 +#: src/api/reference.md:2231 +#, fuzzy +msgid "`createComponentHttpServer` — module" +msgstr "`createInterface` — módulo" + +#: src/api/reference.md:2232 #, fuzzy msgid "`createComponentResource` — module" msgstr "`createInterface` — módulo" -#: src/api/reference.md:2229 +#: src/api/reference.md:2233 #, fuzzy msgid "`disposeComponent` — module" msgstr "`isContext` — módulo" -#: src/api/reference.md:2230 +#: src/api/reference.md:2234 #, fuzzy msgid "`getComponentHostResourceValue` — module" msgstr "`executionAsyncResource` — módulo" -#: src/api/reference.md:2231 +#: src/api/reference.md:2235 #, fuzzy msgid "`getComponentResourceValue` — module" msgstr "`executionAsyncResource` — módulo" -#: src/api/reference.md:2232 +#: src/api/reference.md:2236 +#, fuzzy +msgid "`handleComponentHttpRequest` — module" +msgstr "`createInterface` — módulo" + +#: src/api/reference.md:2237 #, fuzzy msgid "`loadComponent` — module" msgstr "`moment` — módulo" -#: src/api/reference.md:2233 +#: src/api/reference.md:2238 #, fuzzy msgid "`registerComponentExports` — module" msgstr "`registerHooks` — módulo" -#: src/api/reference.md:2234 +#: src/api/reference.md:2239 #, fuzzy msgid "`runComponentCommand` — module" msgstr "`runInContext` — módulo" -#: src/api/reference.md:2236 +#: src/api/reference.md:2241 msgid "`perry/compose`" msgstr "`perry/compose`" -#: src/api/reference.md:2240 src/api/reference.md:2282 +#: src/api/reference.md:2245 src/api/reference.md:2287 msgid "`config` — module" msgstr "`config` — módulo" -#: src/api/reference.md:2241 src/api/reference.md:2283 +#: src/api/reference.md:2246 src/api/reference.md:2288 msgid "`down` — module" msgstr "`down` — módulo" -#: src/api/reference.md:2243 src/api/reference.md:2266 -#: src/api/reference.md:2285 +#: src/api/reference.md:2248 src/api/reference.md:2271 +#: src/api/reference.md:2290 msgid "`logs` — module" msgstr "`logs` — módulo" -#: src/api/reference.md:2244 src/api/reference.md:2286 +#: src/api/reference.md:2249 src/api/reference.md:2291 msgid "`ps` — module" msgstr "`ps` — módulo" -#: src/api/reference.md:2245 src/api/reference.md:2287 +#: src/api/reference.md:2250 src/api/reference.md:2292 msgid "`restart` — module" msgstr "`restart` — módulo" -#: src/api/reference.md:2246 src/api/reference.md:2275 -#: src/api/reference.md:2288 +#: src/api/reference.md:2251 src/api/reference.md:2280 +#: src/api/reference.md:2293 msgid "`start` — module" msgstr "`start` — módulo" -#: src/api/reference.md:2248 src/api/reference.md:2290 +#: src/api/reference.md:2253 src/api/reference.md:2295 msgid "`up` — module" msgstr "`up` — módulo" -#: src/api/reference.md:2250 +#: src/api/reference.md:2255 msgid "`perry/container`" msgstr "`perry/container`" -#: src/api/reference.md:2254 +#: src/api/reference.md:2259 msgid "`composeUp` — module" msgstr "`composeUp` — módulo" -#: src/api/reference.md:2256 +#: src/api/reference.md:2261 msgid "`detectBackend` — module" msgstr "`detectBackend` — módulo" -#: src/api/reference.md:2257 +#: src/api/reference.md:2262 msgid "`downAll` — module" msgstr "`downAll` — módulo" -#: src/api/reference.md:2258 +#: src/api/reference.md:2263 msgid "`downByProject` — module" msgstr "`downByProject` — módulo" -#: src/api/reference.md:2260 +#: src/api/reference.md:2265 msgid "`getAvailableBackends` — module" msgstr "`getAvailableBackends` — módulo" -#: src/api/reference.md:2261 +#: src/api/reference.md:2266 msgid "`getBackend` — module" msgstr "`getBackend` — módulo" -#: src/api/reference.md:2262 +#: src/api/reference.md:2267 msgid "`getBackendPriority` — module" msgstr "`getBackendPriority` — módulo" -#: src/api/reference.md:2263 src/api/reference.md:3193 -#: src/api/reference.md:3455 +#: src/api/reference.md:2268 src/api/reference.md:3202 +#: src/api/reference.md:3475 msgid "`inspect` — module" msgstr "`inspect` — módulo" -#: src/api/reference.md:2264 +#: src/api/reference.md:2269 msgid "`list` — module" msgstr "`list` — módulo" -#: src/api/reference.md:2265 +#: src/api/reference.md:2270 msgid "`listImages` — module" msgstr "`listImages` — módulo" -#: src/api/reference.md:2267 +#: src/api/reference.md:2272 msgid "`pullImage` — module" msgstr "`pullImage` — módulo" -#: src/api/reference.md:2268 +#: src/api/reference.md:2273 msgid "`remove` — module" msgstr "`remove` — módulo" -#: src/api/reference.md:2269 +#: src/api/reference.md:2274 msgid "`removeIfExists` — module" msgstr "`removeIfExists` — módulo" -#: src/api/reference.md:2270 +#: src/api/reference.md:2275 msgid "`removeImage` — module" msgstr "`removeImage` — módulo" -#: src/api/reference.md:2271 src/api/reference.md:2498 -#: src/api/reference.md:3231 +#: src/api/reference.md:2276 src/api/reference.md:2503 +#: src/api/reference.md:3240 msgid "`run` — module" msgstr "`run` — módulo" -#: src/api/reference.md:2272 +#: src/api/reference.md:2277 msgid "`selectBackendFor` — module" msgstr "`selectBackendFor` — módulo" -#: src/api/reference.md:2273 +#: src/api/reference.md:2278 msgid "`setBackend` — module" msgstr "`setBackend` — módulo" -#: src/api/reference.md:2274 +#: src/api/reference.md:2279 msgid "`setBackends` — module" msgstr "`setBackends` — módulo" -#: src/api/reference.md:2278 +#: src/api/reference.md:2283 msgid "`perry/container-compose`" msgstr "`perry/container-compose`" -#: src/api/reference.md:2292 +#: src/api/reference.md:2297 msgid "`perry/gc`" msgstr "`perry/gc`" -#: src/api/reference.md:2296 +#: src/api/reference.md:2301 msgid "`collect` — module" msgstr "`collect` — módulo" -#: src/api/reference.md:2297 +#: src/api/reference.md:2302 msgid "`idleHint` — module" msgstr "`idleHint` — módulo" -#: src/api/reference.md:2298 +#: src/api/reference.md:2303 msgid "`minor` — module" msgstr "`minor` — módulo" -#: src/api/reference.md:2300 +#: src/api/reference.md:2305 msgid "`perry/i18n`" msgstr "`perry/i18n`" -#: src/api/reference.md:2304 +#: src/api/reference.md:2309 msgid "`Currency` — module" msgstr "`Currency` — módulo" -#: src/api/reference.md:2305 +#: src/api/reference.md:2310 msgid "`FormatNumber` — module" msgstr "`FormatNumber` — módulo" -#: src/api/reference.md:2306 +#: src/api/reference.md:2311 msgid "`FormatTime` — module" msgstr "`FormatTime` — módulo" -#: src/api/reference.md:2307 +#: src/api/reference.md:2312 msgid "`LongDate` — module" msgstr "`LongDate` — módulo" -#: src/api/reference.md:2308 +#: src/api/reference.md:2313 msgid "`Percent` — module" msgstr "`Percent` — módulo" -#: src/api/reference.md:2309 +#: src/api/reference.md:2314 msgid "`Raw` — module" msgstr "`Raw` — módulo" -#: src/api/reference.md:2310 +#: src/api/reference.md:2315 msgid "`ShortDate` — module" msgstr "`ShortDate` — módulo" -#: src/api/reference.md:2311 +#: src/api/reference.md:2316 msgid "`t` — module" msgstr "`t` — módulo" -#: src/api/reference.md:2313 +#: src/api/reference.md:2318 msgid "`perry/ios`" msgstr "`perry/ios`" -#: src/api/reference.md:2317 +#: src/api/reference.md:2322 msgid "`createLanguageModelSession` — module" msgstr "`createLanguageModelSession` — módulo" -#: src/api/reference.md:2318 +#: src/api/reference.md:2323 msgid "`destroyLanguageModelSession` — module" msgstr "`destroyLanguageModelSession` — módulo" -#: src/api/reference.md:2319 +#: src/api/reference.md:2324 msgid "`foundationModelAvailability` — module" msgstr "`foundationModelAvailability` — módulo" -#: src/api/reference.md:2320 +#: src/api/reference.md:2325 msgid "`getLayoutEnvironment` — module" msgstr "`getLayoutEnvironment` — módulo" -#: src/api/reference.md:2321 +#: src/api/reference.md:2326 msgid "`offLayoutChange` — module" msgstr "`offLayoutChange` — módulo" -#: src/api/reference.md:2322 +#: src/api/reference.md:2327 msgid "`onLayoutChange` — module" msgstr "`onLayoutChange` — módulo" -#: src/api/reference.md:2323 +#: src/api/reference.md:2328 msgid "`respond` — module" msgstr "`respond` — módulo" -#: src/api/reference.md:2325 +#: src/api/reference.md:2330 msgid "`perry/media`" msgstr "`perry/media`" -#: src/api/reference.md:2329 +#: src/api/reference.md:2334 msgid "`createPlayer` — module" msgstr "`createPlayer` — módulo" -#: src/api/reference.md:2330 +#: src/api/reference.md:2335 msgid "`destroy` — module" msgstr "`destroy` — módulo" -#: src/api/reference.md:2331 +#: src/api/reference.md:2336 msgid "`getCurrentTime` — module" msgstr "`getCurrentTime` — módulo" -#: src/api/reference.md:2333 +#: src/api/reference.md:2338 msgid "`getState` — module" msgstr "`getState` — módulo" -#: src/api/reference.md:2335 +#: src/api/reference.md:2340 msgid "`onStateChange` — module" msgstr "`onStateChange` — módulo" -#: src/api/reference.md:2336 +#: src/api/reference.md:2341 msgid "`onTimeUpdate` — module" msgstr "`onTimeUpdate` — módulo" -#: src/api/reference.md:2339 +#: src/api/reference.md:2344 msgid "`seek` — module" msgstr "`seek` — módulo" -#: src/api/reference.md:2340 +#: src/api/reference.md:2345 msgid "`setNowPlaying` — module" msgstr "`setNowPlaying` — módulo" -#: src/api/reference.md:2345 +#: src/api/reference.md:2350 msgid "`perry/native`" msgstr "`perry/native`" -#: src/api/reference.md:2349 +#: src/api/reference.md:2354 msgid "`alignof` — module _(intrinsic)_" msgstr "`alignof` — módulo _(intrínseco)_" -#: src/api/reference.md:2350 +#: src/api/reference.md:2355 msgid "`f32` — module" msgstr "`f32` — módulo" -#: src/api/reference.md:2351 +#: src/api/reference.md:2356 msgid "`f64` — module" msgstr "`f64` — módulo" -#: src/api/reference.md:2352 +#: src/api/reference.md:2357 msgid "`i16` — module" msgstr "`i16` — módulo" -#: src/api/reference.md:2353 +#: src/api/reference.md:2358 msgid "`i32` — module" msgstr "`i32` — módulo" -#: src/api/reference.md:2354 +#: src/api/reference.md:2359 msgid "`i64` — module" msgstr "`i64` — módulo" -#: src/api/reference.md:2355 +#: src/api/reference.md:2360 msgid "`i8` — module" msgstr "`i8` — módulo" -#: src/api/reference.md:2356 +#: src/api/reference.md:2361 msgid "`isize` — module" msgstr "`isize` — módulo" -#: src/api/reference.md:2357 +#: src/api/reference.md:2362 msgid "`offsetof` — module _(intrinsic)_" msgstr "`offsetof` — módulo _(intrínseco)_" -#: src/api/reference.md:2358 +#: src/api/reference.md:2363 msgid "`sizeof` — module _(intrinsic)_" msgstr "`sizeof` — módulo _(intrínseco)_" -#: src/api/reference.md:2359 +#: src/api/reference.md:2364 msgid "`u16` — module" msgstr "`u16` — módulo" -#: src/api/reference.md:2360 +#: src/api/reference.md:2365 msgid "`u32` — module" msgstr "`u32` — módulo" -#: src/api/reference.md:2361 +#: src/api/reference.md:2366 msgid "`u64` — module" msgstr "`u64` — módulo" -#: src/api/reference.md:2362 +#: src/api/reference.md:2367 msgid "`u8` — module" msgstr "`u8` — módulo" -#: src/api/reference.md:2363 +#: src/api/reference.md:2368 msgid "`usize` — module" msgstr "`usize` — módulo" -#: src/api/reference.md:2367 +#: src/api/reference.md:2372 msgid "`NativeArena`" msgstr "`NativeArena`" -#: src/api/reference.md:2369 +#: src/api/reference.md:2374 msgid "`perry/plugin`" msgstr "`perry/plugin`" -#: src/api/reference.md:2373 +#: src/api/reference.md:2378 msgid "`PluginApi`" msgstr "`PluginApi`" -#: src/api/reference.md:2377 +#: src/api/reference.md:2382 msgid "`discoverPlugins` — module" msgstr "`discoverPlugins` — módulo" -#: src/api/reference.md:2378 +#: src/api/reference.md:2383 msgid "`emitEvent` — module" msgstr "`emitEvent` — módulo" -#: src/api/reference.md:2379 +#: src/api/reference.md:2384 msgid "`emitHook` — module" msgstr "`emitHook` — módulo" -#: src/api/reference.md:2380 +#: src/api/reference.md:2385 msgid "`initPlugins` — module" msgstr "`initPlugins` — módulo" -#: src/api/reference.md:2381 +#: src/api/reference.md:2386 msgid "`invokeTool` — module" msgstr "`invokeTool` — módulo" -#: src/api/reference.md:2382 +#: src/api/reference.md:2387 msgid "`listHooks` — module" msgstr "`listHooks` — módulo" -#: src/api/reference.md:2383 +#: src/api/reference.md:2388 msgid "`listPlugins` — module" msgstr "`listPlugins` — módulo" -#: src/api/reference.md:2384 +#: src/api/reference.md:2389 msgid "`listTools` — module" msgstr "`listTools` — módulo" -#: src/api/reference.md:2385 +#: src/api/reference.md:2390 msgid "`loadPlugin` — module" msgstr "`loadPlugin` — módulo" -#: src/api/reference.md:2386 +#: src/api/reference.md:2391 msgid "`pluginCount` — module" msgstr "`pluginCount` — módulo" -#: src/api/reference.md:2387 +#: src/api/reference.md:2392 msgid "`setPluginConfig` — module" msgstr "`setPluginConfig` — módulo" -#: src/api/reference.md:2388 +#: src/api/reference.md:2393 msgid "`unloadPlugin` — module" msgstr "`unloadPlugin` — módulo" -#: src/api/reference.md:2390 +#: src/api/reference.md:2395 msgid "`perry/system`" msgstr "`perry/system`" -#: src/api/reference.md:2394 +#: src/api/reference.md:2399 msgid "`appGetLaunchUrl` — module" msgstr "`appGetLaunchUrl` — módulo" -#: src/api/reference.md:2395 +#: src/api/reference.md:2400 msgid "`appGroupDelete` — module" msgstr "`appGroupDelete` — módulo" -#: src/api/reference.md:2396 +#: src/api/reference.md:2401 msgid "`appGroupGet` — module" msgstr "`appGroupGet` — módulo" -#: src/api/reference.md:2397 +#: src/api/reference.md:2402 msgid "`appGroupSet` — module" msgstr "`appGroupSet` — módulo" -#: src/api/reference.md:2398 +#: src/api/reference.md:2403 msgid "`appOnOpenUrl` — module" msgstr "`appOnOpenUrl` — módulo" -#: src/api/reference.md:2399 +#: src/api/reference.md:2404 msgid "`audioGetLevel` — module" msgstr "`audioGetLevel` — módulo" -#: src/api/reference.md:2400 +#: src/api/reference.md:2405 msgid "`audioGetPeak` — module" msgstr "`audioGetPeak` — módulo" -#: src/api/reference.md:2401 +#: src/api/reference.md:2406 msgid "`audioGetWaveform` — module" msgstr "`audioGetWaveform` — módulo" -#: src/api/reference.md:2402 +#: src/api/reference.md:2407 msgid "`audioRegisterCallback` — module" msgstr "`audioRegisterCallback` — módulo" -#: src/api/reference.md:2403 +#: src/api/reference.md:2408 msgid "`audioSetOutputFilename` — module" msgstr "`audioSetOutputFilename` — módulo" -#: src/api/reference.md:2404 +#: src/api/reference.md:2409 msgid "`audioStart` — module" msgstr "`audioStart` — módulo" -#: src/api/reference.md:2405 +#: src/api/reference.md:2410 msgid "`audioStartRecording` — module" msgstr "`audioStartRecording` — módulo" -#: src/api/reference.md:2406 +#: src/api/reference.md:2411 msgid "`audioStop` — module" msgstr "`audioStop` — módulo" -#: src/api/reference.md:2407 +#: src/api/reference.md:2412 msgid "`audioStopRecording` — module" msgstr "`audioStopRecording` — módulo" -#: src/api/reference.md:2408 +#: src/api/reference.md:2413 msgid "`audioUnregisterCallback` — module" msgstr "`audioUnregisterCallback` — módulo" -#: src/api/reference.md:2409 +#: src/api/reference.md:2414 msgid "`geolocationGetCurrent` — module" msgstr "`geolocationGetCurrent` — módulo" -#: src/api/reference.md:2410 +#: src/api/reference.md:2415 msgid "`geolocationRequestPermission` — module" msgstr "`geolocationRequestPermission` — módulo" -#: src/api/reference.md:2411 +#: src/api/reference.md:2416 msgid "`geolocationStopWatch` — module" msgstr "`geolocationStopWatch` — módulo" -#: src/api/reference.md:2412 +#: src/api/reference.md:2417 msgid "`geolocationWatch` — module" msgstr "`geolocationWatch` — módulo" -#: src/api/reference.md:2413 +#: src/api/reference.md:2418 msgid "`getAppBuildNumber` — module" msgstr "`getAppBuildNumber` — módulo" -#: src/api/reference.md:2414 +#: src/api/reference.md:2419 msgid "`getAppIcon` — module" msgstr "`getAppIcon` — módulo" -#: src/api/reference.md:2415 +#: src/api/reference.md:2420 msgid "`getAppVersion` — module" msgstr "`getAppVersion` — módulo" -#: src/api/reference.md:2416 +#: src/api/reference.md:2421 msgid "`getBundleId` — module" msgstr "`getBundleId` — módulo" -#: src/api/reference.md:2417 +#: src/api/reference.md:2422 msgid "`getDeviceIdiom` — module" msgstr "`getDeviceIdiom` — módulo" -#: src/api/reference.md:2418 +#: src/api/reference.md:2423 msgid "`getDeviceModel` — module" msgstr "`getDeviceModel` — módulo" -#: src/api/reference.md:2419 +#: src/api/reference.md:2424 msgid "`getLocale` — module" msgstr "`getLocale` — módulo" -#: src/api/reference.md:2420 +#: src/api/reference.md:2425 msgid "`getOSVersion` — module" msgstr "`getOSVersion` — módulo" -#: src/api/reference.md:2421 +#: src/api/reference.md:2426 msgid "`getSafeAreaInsets` — module" msgstr "`getSafeAreaInsets` — módulo" -#: src/api/reference.md:2422 +#: src/api/reference.md:2427 msgid "`hapticPlay` — module" msgstr "`hapticPlay` — módulo" -#: src/api/reference.md:2423 +#: src/api/reference.md:2428 msgid "`imagePickerPick` — module" msgstr "`imagePickerPick` — módulo" -#: src/api/reference.md:2424 +#: src/api/reference.md:2429 msgid "`isDarkMode` — module" msgstr "`isDarkMode` — módulo" -#: src/api/reference.md:2425 +#: src/api/reference.md:2430 msgid "`keychainDelete` — module" msgstr "`keychainDelete` — módulo" -#: src/api/reference.md:2426 +#: src/api/reference.md:2431 msgid "`keychainGet` — module" msgstr "`keychainGet` — módulo" -#: src/api/reference.md:2427 +#: src/api/reference.md:2432 msgid "`keychainSave` — module" msgstr "`keychainSave` — módulo" -#: src/api/reference.md:2428 +#: src/api/reference.md:2433 msgid "`networkGetStatus` — module" msgstr "`networkGetStatus` — módulo" -#: src/api/reference.md:2429 +#: src/api/reference.md:2434 msgid "`networkOnChange` — module" msgstr "`networkOnChange` — módulo" -#: src/api/reference.md:2430 +#: src/api/reference.md:2435 msgid "`networkStopOnChange` — module" msgstr "`networkStopOnChange` — módulo" -#: src/api/reference.md:2431 +#: src/api/reference.md:2436 msgid "`notificationCancel` — module" msgstr "`notificationCancel` — módulo" -#: src/api/reference.md:2432 +#: src/api/reference.md:2437 msgid "`notificationOnBackgroundReceive` — module" msgstr "`notificationOnBackgroundReceive` — módulo" -#: src/api/reference.md:2433 +#: src/api/reference.md:2438 msgid "`notificationOnReceive` — module" msgstr "`notificationOnReceive` — módulo" -#: src/api/reference.md:2434 +#: src/api/reference.md:2439 msgid "`notificationOnTap` — module" msgstr "`notificationOnTap` — módulo" -#: src/api/reference.md:2435 +#: src/api/reference.md:2440 msgid "`notificationRegisterRemote` — module" msgstr "`notificationRegisterRemote` — módulo" -#: src/api/reference.md:2436 +#: src/api/reference.md:2441 msgid "`notificationSend` — module" msgstr "`notificationSend` — módulo" -#: src/api/reference.md:2437 +#: src/api/reference.md:2442 msgid "`openURL` — module" msgstr "`openURL` — módulo" -#: src/api/reference.md:2438 +#: src/api/reference.md:2443 msgid "`preferencesGet` — module" msgstr "`preferencesGet` — módulo" -#: src/api/reference.md:2439 +#: src/api/reference.md:2444 msgid "`preferencesSet` — module" msgstr "`preferencesSet` — módulo" -#: src/api/reference.md:2440 +#: src/api/reference.md:2445 msgid "`shareText` — module" msgstr "`shareText` — módulo" -#: src/api/reference.md:2441 +#: src/api/reference.md:2446 msgid "`shareUrl` — module" msgstr "`shareUrl` — módulo" -#: src/api/reference.md:2442 +#: src/api/reference.md:2447 msgid "`takeScreenshot` — module" msgstr "`takeScreenshot` — módulo" -#: src/api/reference.md:2444 +#: src/api/reference.md:2449 msgid "`perry/thread`" msgstr "`perry/thread`" -#: src/api/reference.md:2448 +#: src/api/reference.md:2453 msgid "`parallelFilter` — module" msgstr "`parallelFilter` — módulo" -#: src/api/reference.md:2449 +#: src/api/reference.md:2454 msgid "`parallelMap` — module" msgstr "`parallelMap` — módulo" -#: src/api/reference.md:2456 +#: src/api/reference.md:2461 msgid "`AnimatedSpinner` — module" msgstr "`AnimatedSpinner` — módulo" -#: src/api/reference.md:2457 +#: src/api/reference.md:2462 msgid "`Box` — module" msgstr "`Box` — módulo" -#: src/api/reference.md:2458 +#: src/api/reference.md:2463 msgid "`Input` — module" msgstr "`Input` — módulo" -#: src/api/reference.md:2459 +#: src/api/reference.md:2464 msgid "`InputAt` — module" msgstr "`InputAt` — módulo" -#: src/api/reference.md:2460 +#: src/api/reference.md:2465 msgid "`List` — module" msgstr "`List` — módulo" -#: src/api/reference.md:2461 +#: src/api/reference.md:2466 msgid "`ProgressBar` — module" msgstr "`ProgressBar` — módulo" -#: src/api/reference.md:2462 +#: src/api/reference.md:2467 msgid "`Select` — module" msgstr "`Select` — módulo" -#: src/api/reference.md:2463 src/api/reference.md:2544 +#: src/api/reference.md:2468 src/api/reference.md:2549 msgid "`Spacer` — module" msgstr "`Spacer` — módulo" -#: src/api/reference.md:2464 +#: src/api/reference.md:2469 msgid "`Spinner` — module" msgstr "`Spinner` — módulo" -#: src/api/reference.md:2465 src/api/reference.md:2548 +#: src/api/reference.md:2470 src/api/reference.md:2553 msgid "`Table` — module" msgstr "`Table` — módulo" -#: src/api/reference.md:2466 +#: src/api/reference.md:2471 msgid "`Tabs` — module" msgstr "`Tabs` — módulo" -#: src/api/reference.md:2467 src/api/reference.md:2549 +#: src/api/reference.md:2472 src/api/reference.md:2554 msgid "`Text` — module" msgstr "`Text` — módulo" -#: src/api/reference.md:2468 src/api/reference.md:2550 +#: src/api/reference.md:2473 src/api/reference.md:2555 msgid "`TextArea` — module" msgstr "`TextArea` — módulo" -#: src/api/reference.md:2469 +#: src/api/reference.md:2474 msgid "`TextStyled` — module" msgstr "`TextStyled` — módulo" -#: src/api/reference.md:2470 +#: src/api/reference.md:2475 msgid "`boxSetAlignItems` — module" msgstr "`boxSetAlignItems` — módulo" -#: src/api/reference.md:2471 +#: src/api/reference.md:2476 msgid "`boxSetFlexBasis` — module" msgstr "`boxSetFlexBasis` — módulo" -#: src/api/reference.md:2472 +#: src/api/reference.md:2477 msgid "`boxSetFlexBasisPct` — module" msgstr "`boxSetFlexBasisPct` — módulo" -#: src/api/reference.md:2473 +#: src/api/reference.md:2478 msgid "`boxSetFlexDirection` — module" msgstr "`boxSetFlexDirection` — módulo" -#: src/api/reference.md:2474 +#: src/api/reference.md:2479 msgid "`boxSetFlexGrow` — module" msgstr "`boxSetFlexGrow` — módulo" -#: src/api/reference.md:2475 +#: src/api/reference.md:2480 msgid "`boxSetFlexShrink` — module" msgstr "`boxSetFlexShrink` — módulo" -#: src/api/reference.md:2476 +#: src/api/reference.md:2481 msgid "`boxSetGap` — module" msgstr "`boxSetGap` — módulo" -#: src/api/reference.md:2477 +#: src/api/reference.md:2482 msgid "`boxSetHeight` — module" msgstr "`boxSetHeight` — módulo" -#: src/api/reference.md:2478 +#: src/api/reference.md:2483 msgid "`boxSetHeightPct` — module" msgstr "`boxSetHeightPct` — módulo" -#: src/api/reference.md:2479 +#: src/api/reference.md:2484 msgid "`boxSetJustifyContent` — module" msgstr "`boxSetJustifyContent` — módulo" -#: src/api/reference.md:2480 +#: src/api/reference.md:2485 msgid "`boxSetPadding` — module" msgstr "`boxSetPadding` — módulo" -#: src/api/reference.md:2481 +#: src/api/reference.md:2486 msgid "`boxSetPaddingEach` — module" msgstr "`boxSetPaddingEach` — módulo" -#: src/api/reference.md:2482 +#: src/api/reference.md:2487 msgid "`boxSetWidth` — module" msgstr "`boxSetWidth` — módulo" -#: src/api/reference.md:2483 +#: src/api/reference.md:2488 msgid "`boxSetWidthPct` — module" msgstr "`boxSetWidthPct` — módulo" -#: src/api/reference.md:2484 +#: src/api/reference.md:2489 msgid "`columns` — instance _(class: `TuiStdout`)_" msgstr "`columns` — instancia _(clase: `TuiStdout`)_" -#: src/api/reference.md:2485 +#: src/api/reference.md:2490 msgid "`enter` — module" msgstr "`enter` — módulo" -#: src/api/reference.md:2486 src/api/reference.md:2735 +#: src/api/reference.md:2491 src/api/reference.md:2740 msgid "`exit` — module" msgstr "`exit` — módulo" -#: src/api/reference.md:2487 +#: src/api/reference.md:2492 msgid "`exit` — instance _(class: `TuiApp`)_" msgstr "`exit` — instancia _(clase: `TuiApp`)_" -#: src/api/reference.md:2488 src/api/reference.md:2588 +#: src/api/reference.md:2493 src/api/reference.md:2593 msgid "`focus` — module" msgstr "`focus` — módulo" -#: src/api/reference.md:2489 +#: src/api/reference.md:2494 msgid "`focus` — instance _(class: `FocusManager`)_" msgstr "`focus` — instancia _(clase: `FocusManager`)_" -#: src/api/reference.md:2490 +#: src/api/reference.md:2495 msgid "`focusNext` — module" msgstr "`focusNext` — módulo" -#: src/api/reference.md:2491 +#: src/api/reference.md:2496 msgid "`focusNext` — instance _(class: `FocusManager`)_" msgstr "`focusNext` — instancia _(clase: `FocusManager`)_" -#: src/api/reference.md:2492 +#: src/api/reference.md:2497 msgid "`focusPrevious` — module" msgstr "`focusPrevious` — módulo" -#: src/api/reference.md:2493 +#: src/api/reference.md:2498 msgid "`focusPrevious` — instance _(class: `FocusManager`)_" msgstr "`focusPrevious` — instancia _(clase: `FocusManager`)_" -#: src/api/reference.md:2494 +#: src/api/reference.md:2499 msgid "`get` — instance _(class: `State`)_" msgstr "`get` — instancia _(clase: `State`)_" -#: src/api/reference.md:2495 +#: src/api/reference.md:2500 msgid "`get` — instance _(class: `RefBox`)_" msgstr "`get` — instancia _(clase: `RefBox`)_" -#: src/api/reference.md:2496 +#: src/api/reference.md:2501 msgid "`render` — module" msgstr "`render` — módulo" -#: src/api/reference.md:2497 +#: src/api/reference.md:2502 msgid "`rows` — instance _(class: `TuiStdout`)_" msgstr "`rows` — instancia _(clase: `TuiStdout`)_" -#: src/api/reference.md:2499 +#: src/api/reference.md:2504 msgid "`set` — instance _(class: `State`)_" msgstr "`set` — instancia _(clase: `State`)_" -#: src/api/reference.md:2500 +#: src/api/reference.md:2505 msgid "`set` — instance _(class: `RefBox`)_" msgstr "`set` — instancia _(clase: `RefBox`)_" -#: src/api/reference.md:2501 +#: src/api/reference.md:2506 msgid "`state` — module" msgstr "`state` — módulo" -#: src/api/reference.md:2502 +#: src/api/reference.md:2507 msgid "`useApp` — module" msgstr "`useApp` — módulo" -#: src/api/reference.md:2503 +#: src/api/reference.md:2508 msgid "`useEffect` — module" msgstr "`useEffect` — módulo" -#: src/api/reference.md:2504 +#: src/api/reference.md:2509 msgid "`useFocus` — module" msgstr "`useFocus` — módulo" -#: src/api/reference.md:2505 +#: src/api/reference.md:2510 msgid "`useFocusManager` — module" msgstr "`useFocusManager` — módulo" -#: src/api/reference.md:2506 +#: src/api/reference.md:2511 msgid "`useInput` — module" msgstr "`useInput` — módulo" -#: src/api/reference.md:2507 +#: src/api/reference.md:2512 msgid "`useMemo` — module" msgstr "`useMemo` — módulo" -#: src/api/reference.md:2508 +#: src/api/reference.md:2513 msgid "`useRef` — module" msgstr "`useRef` — módulo" -#: src/api/reference.md:2509 +#: src/api/reference.md:2514 msgid "`useState` — module" msgstr "`useState` — módulo" -#: src/api/reference.md:2510 +#: src/api/reference.md:2515 msgid "`useStateSet` — module" msgstr "`useStateSet` — módulo" -#: src/api/reference.md:2511 +#: src/api/reference.md:2516 msgid "`useStateTuple` — module" msgstr "`useStateTuple` — módulo" -#: src/api/reference.md:2512 +#: src/api/reference.md:2517 msgid "`useStdout` — module" msgstr "`useStdout` — módulo" -#: src/api/reference.md:2513 +#: src/api/reference.md:2518 msgid "`waitUntilExit` — module" msgstr "`waitUntilExit` — módulo" -#: src/api/reference.md:2514 +#: src/api/reference.md:2519 msgid "`waitUntilExit` — instance _(class: `TuiApp`)_" msgstr "`waitUntilExit` — instancia _(clase: `TuiApp`)_" -#: src/api/reference.md:2515 +#: src/api/reference.md:2520 msgid "`write` — instance _(class: `TuiStdout`)_" msgstr "`write` — instancia _(clase: `TuiStdout`)_" -#: src/api/reference.md:2517 +#: src/api/reference.md:2522 msgid "`perry/ui`" msgstr "`perry/ui`" -#: src/api/reference.md:2521 +#: src/api/reference.md:2526 msgid "`App` — module" msgstr "`App` — módulo" -#: src/api/reference.md:2522 +#: src/api/reference.md:2527 msgid "`AttributedText` — module" msgstr "`AttributedText` — módulo" -#: src/api/reference.md:2523 +#: src/api/reference.md:2528 msgid "`BloomView` — module" msgstr "`BloomView` — módulo" -#: src/api/reference.md:2524 +#: src/api/reference.md:2529 msgid "`BottomNavigation` — module" msgstr "`BottomNavigation` — módulo" -#: src/api/reference.md:2525 +#: src/api/reference.md:2530 msgid "`Button` — module" msgstr "`Button` — módulo" -#: src/api/reference.md:2526 +#: src/api/reference.md:2531 msgid "`CameraView` — module" msgstr "`CameraView` — módulo" -#: src/api/reference.md:2527 +#: src/api/reference.md:2532 msgid "`Canvas` — module" msgstr "`Canvas` — módulo" -#: src/api/reference.md:2528 +#: src/api/reference.md:2533 msgid "`Divider` — module" msgstr "`Divider` — módulo" -#: src/api/reference.md:2529 +#: src/api/reference.md:2534 msgid "`ForEach` — module" msgstr "`ForEach` — módulo" -#: src/api/reference.md:2530 +#: src/api/reference.md:2535 msgid "`HStack` — module" msgstr "`HStack` — módulo" -#: src/api/reference.md:2531 +#: src/api/reference.md:2536 msgid "`HStackWithInsets` — module" msgstr "`HStackWithInsets` — módulo" -#: src/api/reference.md:2532 +#: src/api/reference.md:2537 msgid "`Image` — module" msgstr "`Image` — módulo" -#: src/api/reference.md:2533 +#: src/api/reference.md:2538 msgid "`ImageFile` — module" msgstr "`ImageFile` — módulo" -#: src/api/reference.md:2534 +#: src/api/reference.md:2539 msgid "`ImageGallery` — module" msgstr "`ImageGallery` — módulo" -#: src/api/reference.md:2535 +#: src/api/reference.md:2540 msgid "`ImageSymbol` — module" msgstr "`ImageSymbol` — módulo" -#: src/api/reference.md:2536 +#: src/api/reference.md:2541 msgid "`LazyVStack` — module" msgstr "`LazyVStack` — módulo" -#: src/api/reference.md:2537 +#: src/api/reference.md:2542 msgid "`NavStack` — module" msgstr "`NavStack` — módulo" -#: src/api/reference.md:2538 +#: src/api/reference.md:2543 msgid "`Picker` — module" msgstr "`Picker` — módulo" -#: src/api/reference.md:2539 +#: src/api/reference.md:2544 msgid "`ProgressView` — module" msgstr "`ProgressView` — módulo" -#: src/api/reference.md:2540 +#: src/api/reference.md:2545 msgid "`ScrollView` — module" msgstr "`ScrollView` — módulo" -#: src/api/reference.md:2541 +#: src/api/reference.md:2546 msgid "`Section` — module" msgstr "`Section` — módulo" -#: src/api/reference.md:2542 +#: src/api/reference.md:2547 msgid "`SecureField` — module" msgstr "`SecureField` — módulo" -#: src/api/reference.md:2543 +#: src/api/reference.md:2548 msgid "`Slider` — module" msgstr "`Slider` — módulo" -#: src/api/reference.md:2545 +#: src/api/reference.md:2550 msgid "`SplitView` — module" msgstr "`SplitView` — módulo" -#: src/api/reference.md:2546 +#: src/api/reference.md:2551 msgid "`State` — module" msgstr "`State` — módulo" -#: src/api/reference.md:2547 +#: src/api/reference.md:2552 msgid "`TabBar` — module" msgstr "`TabBar` — módulo" -#: src/api/reference.md:2551 +#: src/api/reference.md:2556 msgid "`TextField` — module" msgstr "`TextField` — módulo" -#: src/api/reference.md:2552 +#: src/api/reference.md:2557 msgid "`Toggle` — module" msgstr "`Toggle` — módulo" -#: src/api/reference.md:2553 +#: src/api/reference.md:2558 msgid "`VStack` — module" msgstr "`VStack` — módulo" -#: src/api/reference.md:2554 +#: src/api/reference.md:2559 msgid "`VStackWithInsets` — module" msgstr "`VStackWithInsets` — módulo" -#: src/api/reference.md:2555 +#: src/api/reference.md:2560 msgid "`WebView` — module" msgstr "`WebView` — módulo" -#: src/api/reference.md:2556 +#: src/api/reference.md:2561 msgid "`WheelPicker` — module" msgstr "`WheelPicker` — módulo" -#: src/api/reference.md:2557 +#: src/api/reference.md:2562 msgid "`Window` — module" msgstr "`Window` — módulo" -#: src/api/reference.md:2558 +#: src/api/reference.md:2563 msgid "`ZStack` — module" msgstr "`ZStack` — módulo" -#: src/api/reference.md:2559 +#: src/api/reference.md:2564 msgid "`addKeyboardShortcut` — module" msgstr "`addKeyboardShortcut` — módulo" -#: src/api/reference.md:2560 +#: src/api/reference.md:2565 msgid "`alert` — module" msgstr "`alert` — módulo" -#: src/api/reference.md:2561 +#: src/api/reference.md:2566 msgid "`alertWithButtons` — module" msgstr "`alertWithButtons` — módulo" -#: src/api/reference.md:2562 +#: src/api/reference.md:2567 msgid "`appSetActivationPolicy` — module" msgstr "`appSetActivationPolicy` — módulo" -#: src/api/reference.md:2563 +#: src/api/reference.md:2568 msgid "`appSetMaxSize` — module" msgstr "`appSetMaxSize` — módulo" -#: src/api/reference.md:2564 +#: src/api/reference.md:2569 msgid "`appSetMinSize` — module" msgstr "`appSetMinSize` — módulo" -#: src/api/reference.md:2565 +#: src/api/reference.md:2570 msgid "`appSetTimer` — module" msgstr "`appSetTimer` — módulo" -#: src/api/reference.md:2566 +#: src/api/reference.md:2571 msgid "`attributedTextAppend` — module" msgstr "`attributedTextAppend` — módulo" -#: src/api/reference.md:2567 +#: src/api/reference.md:2572 msgid "`attributedTextClear` — module" msgstr "`attributedTextClear` — módulo" -#: src/api/reference.md:2568 +#: src/api/reference.md:2573 msgid "`bloomViewGetHwnd` — module" msgstr "`bloomViewGetHwnd` — módulo" -#: src/api/reference.md:2569 +#: src/api/reference.md:2574 msgid "`bloomViewGetNativeHandle` — module" msgstr "`bloomViewGetNativeHandle` — módulo" -#: src/api/reference.md:2570 +#: src/api/reference.md:2575 msgid "`blur` — module" msgstr "`blur` — módulo" -#: src/api/reference.md:2571 +#: src/api/reference.md:2576 msgid "`bottomNavAddItem` — module" msgstr "`bottomNavAddItem` — módulo" -#: src/api/reference.md:2572 +#: src/api/reference.md:2577 msgid "`bottomNavSetBadge` — module" msgstr "`bottomNavSetBadge` — módulo" -#: src/api/reference.md:2573 +#: src/api/reference.md:2578 msgid "`bottomNavSetSelected` — module" msgstr "`bottomNavSetSelected` — módulo" -#: src/api/reference.md:2574 +#: src/api/reference.md:2579 msgid "`bottomNavSetTintColor` — module" msgstr "`bottomNavSetTintColor` — módulo" -#: src/api/reference.md:2575 +#: src/api/reference.md:2580 msgid "`bottomNavSetUnselectedTintColor` — module" msgstr "`bottomNavSetUnselectedTintColor` — módulo" -#: src/api/reference.md:2576 +#: src/api/reference.md:2581 msgid "`cameraFreeze` — module" msgstr "`cameraFreeze` — módulo" -#: src/api/reference.md:2577 +#: src/api/reference.md:2582 msgid "`cameraRegisterFrameCallback` — module" msgstr "`cameraRegisterFrameCallback` — módulo" -#: src/api/reference.md:2578 +#: src/api/reference.md:2583 msgid "`cameraSampleColor` — module" msgstr "`cameraSampleColor` — módulo" -#: src/api/reference.md:2579 +#: src/api/reference.md:2584 msgid "`cameraSetOnTap` — module" msgstr "`cameraSetOnTap` — módulo" -#: src/api/reference.md:2580 +#: src/api/reference.md:2585 msgid "`cameraStart` — module" msgstr "`cameraStart` — módulo" -#: src/api/reference.md:2581 +#: src/api/reference.md:2586 msgid "`cameraStop` — module" msgstr "`cameraStop` — módulo" -#: src/api/reference.md:2582 +#: src/api/reference.md:2587 msgid "`cameraUnfreeze` — module" msgstr "`cameraUnfreeze` — módulo" -#: src/api/reference.md:2583 +#: src/api/reference.md:2588 msgid "`cameraUnregisterFrameCallback` — module" msgstr "`cameraUnregisterFrameCallback` — módulo" -#: src/api/reference.md:2584 +#: src/api/reference.md:2589 msgid "`clipboardRead` — module" msgstr "`clipboardRead` — módulo" -#: src/api/reference.md:2585 +#: src/api/reference.md:2590 msgid "`clipboardWrite` — module" msgstr "`clipboardWrite` — módulo" -#: src/api/reference.md:2586 +#: src/api/reference.md:2591 msgid "`currentModifiers` — module" msgstr "`currentModifiers` — módulo" -#: src/api/reference.md:2587 +#: src/api/reference.md:2592 msgid "`embedNSView` — module" msgstr "`embedNSView` — módulo" -#: src/api/reference.md:2589 +#: src/api/reference.md:2594 msgid "`frameSplitAddChild` — module" msgstr "`frameSplitAddChild` — módulo" -#: src/api/reference.md:2590 +#: src/api/reference.md:2595 msgid "`frameSplitCreate` — module" msgstr "`frameSplitCreate` — módulo" -#: src/api/reference.md:2591 +#: src/api/reference.md:2596 msgid "`imageGalleryAddImage` — module" msgstr "`imageGalleryAddImage` — módulo" -#: src/api/reference.md:2592 +#: src/api/reference.md:2597 msgid "`imageGallerySetIndex` — module" msgstr "`imageGallerySetIndex` — módulo" -#: src/api/reference.md:2593 +#: src/api/reference.md:2598 msgid "`isKeyDown` — module" msgstr "`isKeyDown` — módulo" -#: src/api/reference.md:2594 +#: src/api/reference.md:2599 msgid "`lazyvstackEndRefreshing` — module" msgstr "`lazyvstackEndRefreshing` — módulo" -#: src/api/reference.md:2595 +#: src/api/reference.md:2600 msgid "`lazyvstackSetRefreshControl` — module" msgstr "`lazyvstackSetRefreshControl` — módulo" -#: src/api/reference.md:2596 +#: src/api/reference.md:2601 msgid "`lazyvstackSetScrollEndCallback` — module" msgstr "`lazyvstackSetScrollEndCallback` — módulo" -#: src/api/reference.md:2597 +#: src/api/reference.md:2602 msgid "`loadImage` — module" msgstr "`loadImage` — módulo" -#: src/api/reference.md:2598 +#: src/api/reference.md:2603 msgid "`menuAddItem` — module" msgstr "`menuAddItem` — módulo" -#: src/api/reference.md:2599 +#: src/api/reference.md:2604 msgid "`menuAddItemWithShortcut` — module" msgstr "`menuAddItemWithShortcut` — módulo" -#: src/api/reference.md:2600 +#: src/api/reference.md:2605 msgid "`menuAddSeparator` — module" msgstr "`menuAddSeparator` — módulo" -#: src/api/reference.md:2601 +#: src/api/reference.md:2606 msgid "`menuAddStandardAction` — module" msgstr "`menuAddStandardAction` — módulo" -#: src/api/reference.md:2602 +#: src/api/reference.md:2607 msgid "`menuAddSubmenu` — module" msgstr "`menuAddSubmenu` — módulo" -#: src/api/reference.md:2603 +#: src/api/reference.md:2608 msgid "`menuBarAddMenu` — module" msgstr "`menuBarAddMenu` — módulo" -#: src/api/reference.md:2604 +#: src/api/reference.md:2609 msgid "`menuBarAttach` — module" msgstr "`menuBarAttach` — módulo" -#: src/api/reference.md:2605 +#: src/api/reference.md:2610 msgid "`menuBarCreate` — module" msgstr "`menuBarCreate` — módulo" -#: src/api/reference.md:2606 +#: src/api/reference.md:2611 msgid "`menuClear` — module" msgstr "`menuClear` — módulo" -#: src/api/reference.md:2607 +#: src/api/reference.md:2612 msgid "`menuCreate` — module" msgstr "`menuCreate` — módulo" -#: src/api/reference.md:2608 +#: src/api/reference.md:2613 msgid "`onActivate` — module" msgstr "`onActivate` — módulo" -#: src/api/reference.md:2609 +#: src/api/reference.md:2614 msgid "`onAppKeyDown` — module" msgstr "`onAppKeyDown` — módulo" -#: src/api/reference.md:2610 +#: src/api/reference.md:2615 msgid "`onAppKeyUp` — module" msgstr "`onAppKeyUp` — módulo" -#: src/api/reference.md:2611 +#: src/api/reference.md:2616 msgid "`onKeyDown` — module" msgstr "`onKeyDown` — módulo" -#: src/api/reference.md:2612 +#: src/api/reference.md:2617 msgid "`onKeyUp` — module" msgstr "`onKeyUp` — módulo" -#: src/api/reference.md:2613 +#: src/api/reference.md:2618 msgid "`onTerminate` — module" msgstr "`onTerminate` — módulo" -#: src/api/reference.md:2614 +#: src/api/reference.md:2619 msgid "`openFileDialog` — module" msgstr "`openFileDialog` — módulo" -#: src/api/reference.md:2615 +#: src/api/reference.md:2620 msgid "`openFolderDialog` — module" msgstr "`openFolderDialog` — módulo" -#: src/api/reference.md:2616 +#: src/api/reference.md:2621 msgid "`pollOpenFile` — module" msgstr "`pollOpenFile` — módulo" -#: src/api/reference.md:2617 +#: src/api/reference.md:2622 msgid "`registerGlobalHotkey` — module" msgstr "`registerGlobalHotkey` — módulo" -#: src/api/reference.md:2618 +#: src/api/reference.md:2623 msgid "`saveFileDialog` — module" msgstr "`saveFileDialog` — módulo" -#: src/api/reference.md:2619 +#: src/api/reference.md:2624 msgid "`scrollViewSetScrollEndCallback` — module" msgstr "`scrollViewSetScrollEndCallback` — módulo" -#: src/api/reference.md:2620 +#: src/api/reference.md:2625 msgid "`scrollviewSetScrollEndCallback` — module" msgstr "`scrollviewSetScrollEndCallback` — módulo" -#: src/api/reference.md:2621 +#: src/api/reference.md:2626 msgid "`setText` — module" msgstr "`setText` — módulo" -#: src/api/reference.md:2622 +#: src/api/reference.md:2627 msgid "`sheetCreate` — module" msgstr "`sheetCreate` — módulo" -#: src/api/reference.md:2623 +#: src/api/reference.md:2628 msgid "`sheetDismiss` — module" msgstr "`sheetDismiss` — módulo" -#: src/api/reference.md:2624 +#: src/api/reference.md:2629 msgid "`sheetPresent` — module" msgstr "`sheetPresent` — módulo" -#: src/api/reference.md:2625 +#: src/api/reference.md:2630 msgid "`showToast` — module" msgstr "`showToast` — módulo" -#: src/api/reference.md:2626 +#: src/api/reference.md:2631 msgid "`toolbarAddItem` — module" msgstr "`toolbarAddItem` — módulo" -#: src/api/reference.md:2627 +#: src/api/reference.md:2632 msgid "`toolbarAttach` — module" msgstr "`toolbarAttach` — módulo" -#: src/api/reference.md:2628 +#: src/api/reference.md:2633 msgid "`toolbarCreate` — module" msgstr "`toolbarCreate` — módulo" -#: src/api/reference.md:2629 +#: src/api/reference.md:2634 msgid "`trayAttachMenu` — module" msgstr "`trayAttachMenu` — módulo" -#: src/api/reference.md:2630 +#: src/api/reference.md:2635 msgid "`trayCreate` — module" msgstr "`trayCreate` — módulo" -#: src/api/reference.md:2631 +#: src/api/reference.md:2636 msgid "`trayDestroy` — module" msgstr "`trayDestroy` — módulo" -#: src/api/reference.md:2632 +#: src/api/reference.md:2637 msgid "`trayOnClick` — module" msgstr "`trayOnClick` — módulo" -#: src/api/reference.md:2633 +#: src/api/reference.md:2638 msgid "`traySetIcon` — module" msgstr "`traySetIcon` — módulo" -#: src/api/reference.md:2634 +#: src/api/reference.md:2639 msgid "`traySetTooltip` — module" msgstr "`traySetTooltip` — módulo" -#: src/api/reference.md:2635 +#: src/api/reference.md:2640 msgid "`webviewCanGoBack` — module" msgstr "`webviewCanGoBack` — módulo" -#: src/api/reference.md:2636 +#: src/api/reference.md:2641 msgid "`webviewClearCookies` — module" msgstr "`webviewClearCookies` — módulo" -#: src/api/reference.md:2637 +#: src/api/reference.md:2642 msgid "`webviewEvaluateJs` — module" msgstr "`webviewEvaluateJs` — módulo" -#: src/api/reference.md:2638 +#: src/api/reference.md:2643 msgid "`webviewGoBack` — module" msgstr "`webviewGoBack` — módulo" -#: src/api/reference.md:2639 +#: src/api/reference.md:2644 msgid "`webviewGoForward` — module" msgstr "`webviewGoForward` — módulo" -#: src/api/reference.md:2640 +#: src/api/reference.md:2645 msgid "`webviewLoadUrl` — module" msgstr "`webviewLoadUrl` — módulo" -#: src/api/reference.md:2641 +#: src/api/reference.md:2646 msgid "`webviewReload` — module" msgstr "`webviewReload` — módulo" -#: src/api/reference.md:2642 +#: src/api/reference.md:2647 msgid "`wheelPickerAddItem` — module" msgstr "`wheelPickerAddItem` — módulo" -#: src/api/reference.md:2643 +#: src/api/reference.md:2648 msgid "`wheelPickerGetSelected` — module" msgstr "`wheelPickerGetSelected` — módulo" -#: src/api/reference.md:2644 +#: src/api/reference.md:2649 msgid "`wheelPickerSetSelected` — module" msgstr "`wheelPickerSetSelected` — módulo" -#: src/api/reference.md:2646 +#: src/api/reference.md:2651 msgid "`perry/updater`" msgstr "`perry/updater`" -#: src/api/reference.md:2650 +#: src/api/reference.md:2655 msgid "`clearSentinel` — module" msgstr "`clearSentinel` — módulo" -#: src/api/reference.md:2651 +#: src/api/reference.md:2656 msgid "`compareVersions` — module" msgstr "`compareVersions` — módulo" -#: src/api/reference.md:2652 +#: src/api/reference.md:2657 msgid "`computeFileSha256` — module" msgstr "`computeFileSha256` — módulo" -#: src/api/reference.md:2653 +#: src/api/reference.md:2658 msgid "`embeddedCheckHeaders` — module" msgstr "`embeddedCheckHeaders` — módulo" -#: src/api/reference.md:2654 +#: src/api/reference.md:2659 msgid "`embeddedCheckUrl` — module" msgstr "`embeddedCheckUrl` — módulo" -#: src/api/reference.md:2655 +#: src/api/reference.md:2660 msgid "`embeddedRefreshDue` — module" msgstr "`embeddedRefreshDue` — módulo" -#: src/api/reference.md:2656 +#: src/api/reference.md:2661 msgid "`getBackupPath` — module" msgstr "`getBackupPath` — módulo" -#: src/api/reference.md:2657 +#: src/api/reference.md:2662 msgid "`getEmbeddedConfig` — module" msgstr "`getEmbeddedConfig` — módulo" -#: src/api/reference.md:2658 +#: src/api/reference.md:2663 msgid "`getExePath` — module" msgstr "`getExePath` — módulo" -#: src/api/reference.md:2659 +#: src/api/reference.md:2664 msgid "`getSentinelPath` — module" msgstr "`getSentinelPath` — módulo" -#: src/api/reference.md:2660 +#: src/api/reference.md:2665 msgid "`installUpdate` — module" msgstr "`installUpdate` — módulo" -#: src/api/reference.md:2661 +#: src/api/reference.md:2666 msgid "`performRollback` — module" msgstr "`performRollback` — módulo" -#: src/api/reference.md:2662 +#: src/api/reference.md:2667 msgid "`readSentinel` — module" msgstr "`readSentinel` — módulo" -#: src/api/reference.md:2663 +#: src/api/reference.md:2668 msgid "`recordEmbeddedResponse` — module" msgstr "`recordEmbeddedResponse` — módulo" -#: src/api/reference.md:2664 +#: src/api/reference.md:2669 msgid "`relaunch` — module" msgstr "`relaunch` — módulo" -#: src/api/reference.md:2665 +#: src/api/reference.md:2670 msgid "`verifyHash` — module" msgstr "`verifyHash` — módulo" -#: src/api/reference.md:2666 +#: src/api/reference.md:2671 msgid "`verifySignature` — module" msgstr "`verifySignature` — módulo" -#: src/api/reference.md:2667 +#: src/api/reference.md:2672 msgid "`verifySignatureV2` — module" msgstr "`verifySignatureV2` — módulo" -#: src/api/reference.md:2668 +#: src/api/reference.md:2673 msgid "`writeSentinel` — module" msgstr "`writeSentinel` — módulo" -#: src/api/reference.md:2670 +#: src/api/reference.md:2675 msgid "`perry/widget`" msgstr "`perry/widget`" -#: src/api/reference.md:2674 +#: src/api/reference.md:2679 msgid "`Widget` — module" msgstr "`Widget` — módulo" -#: src/api/reference.md:2676 +#: src/api/reference.md:2681 msgid "`perry/workloads`" msgstr "`perry/workloads`" -#: src/api/reference.md:2680 +#: src/api/reference.md:2685 msgid "`graph` — module" msgstr "`graph` — módulo" -#: src/api/reference.md:2681 +#: src/api/reference.md:2686 msgid "`inspectGraph` — module" msgstr "`inspectGraph` — módulo" -#: src/api/reference.md:2682 +#: src/api/reference.md:2687 msgid "`node` — module" msgstr "`node` — módulo" -#: src/api/reference.md:2683 +#: src/api/reference.md:2688 msgid "`runGraph` — module" msgstr "`runGraph` — módulo" -#: src/api/reference.md:2687 +#: src/api/reference.md:2692 msgid "`policy`" msgstr "`policy`" -#: src/api/reference.md:2688 +#: src/api/reference.md:2693 msgid "`runtime`" msgstr "`runtime`" -#: src/api/reference.md:2690 +#: src/api/reference.md:2695 msgid "`perry/yoga`" msgstr "`perry/yoga`" -#: src/api/reference.md:2694 +#: src/api/reference.md:2699 msgid "`calculateLayout` — module" msgstr "`calculateLayout` — módulo" -#: src/api/reference.md:2695 +#: src/api/reference.md:2700 msgid "`childCount` — module" msgstr "`childCount` — módulo" -#: src/api/reference.md:2696 +#: src/api/reference.md:2701 msgid "`getComputed` — module" msgstr "`getComputed` — módulo" -#: src/api/reference.md:2697 +#: src/api/reference.md:2702 msgid "`getComputedEdge` — module" msgstr "`getComputedEdge` — módulo" -#: src/api/reference.md:2698 +#: src/api/reference.md:2703 msgid "`insertChild` — module" msgstr "`insertChild` — módulo" -#: src/api/reference.md:2699 +#: src/api/reference.md:2704 msgid "`nodeFree` — module" msgstr "`nodeFree` — módulo" -#: src/api/reference.md:2700 +#: src/api/reference.md:2705 msgid "`nodeNew` — module" msgstr "`nodeNew` — módulo" -#: src/api/reference.md:2701 +#: src/api/reference.md:2706 msgid "`removeChild` — module" msgstr "`removeChild` — módulo" -#: src/api/reference.md:2702 +#: src/api/reference.md:2707 msgid "`setEdge` — module" msgstr "`setEdge` — módulo" -#: src/api/reference.md:2703 +#: src/api/reference.md:2708 msgid "`setEnum` — module" msgstr "`setEnum` — módulo" -#: src/api/reference.md:2704 +#: src/api/reference.md:2709 msgid "`setGap` — module" msgstr "`setGap` — módulo" -#: src/api/reference.md:2705 +#: src/api/reference.md:2710 msgid "`setMeasureFunc` — module" msgstr "`setMeasureFunc` — módulo" -#: src/api/reference.md:2706 +#: src/api/reference.md:2711 msgid "`setNumber` — module" msgstr "`setNumber` — módulo" -#: src/api/reference.md:2707 +#: src/api/reference.md:2712 msgid "`unsetMeasureFunc` — module" msgstr "`unsetMeasureFunc` — módulo" -#: src/api/reference.md:2709 +#: src/api/reference.md:2714 msgid "`process`" msgstr "`process`" -#: src/api/reference.md:2713 +#: src/api/reference.md:2718 msgid "`_debugEnd` — module" msgstr "`_debugEnd` — módulo" -#: src/api/reference.md:2714 +#: src/api/reference.md:2719 msgid "`_debugProcess` — module" msgstr "`_debugProcess` — módulo" -#: src/api/reference.md:2715 +#: src/api/reference.md:2720 msgid "`_fatalException` — module" msgstr "`_fatalException` — módulo" -#: src/api/reference.md:2716 +#: src/api/reference.md:2721 msgid "`_getActiveHandles` — module" msgstr "`_getActiveHandles` — módulo" -#: src/api/reference.md:2717 +#: src/api/reference.md:2722 msgid "`_getActiveRequests` — module" msgstr "`_getActiveRequests` — módulo" -#: src/api/reference.md:2718 +#: src/api/reference.md:2723 msgid "`_kill` — module" msgstr "`_kill` — módulo" -#: src/api/reference.md:2719 +#: src/api/reference.md:2724 msgid "`_linkedBinding` — module" msgstr "`_linkedBinding` — módulo" -#: src/api/reference.md:2720 +#: src/api/reference.md:2725 msgid "`_rawDebug` — module" msgstr "`_rawDebug` — módulo" -#: src/api/reference.md:2721 +#: src/api/reference.md:2726 msgid "`_startProfilerIdleNotifier` — module" msgstr "`_startProfilerIdleNotifier` — módulo" -#: src/api/reference.md:2722 +#: src/api/reference.md:2727 msgid "`_stopProfilerIdleNotifier` — module" msgstr "`_stopProfilerIdleNotifier` — módulo" -#: src/api/reference.md:2723 +#: src/api/reference.md:2728 msgid "`_tickCallback` — module" msgstr "`_tickCallback` — módulo" -#: src/api/reference.md:2724 +#: src/api/reference.md:2729 msgid "`abort` — module" msgstr "`abort` — módulo" -#: src/api/reference.md:2725 +#: src/api/reference.md:2730 msgid "`addUncaughtExceptionCaptureCallback` — module" msgstr "`addUncaughtExceptionCaptureCallback` — módulo" -#: src/api/reference.md:2726 +#: src/api/reference.md:2731 msgid "`availableMemory` — module" msgstr "`availableMemory` — módulo" -#: src/api/reference.md:2727 +#: src/api/reference.md:2732 msgid "`binding` — module" msgstr "`binding` — módulo" -#: src/api/reference.md:2728 +#: src/api/reference.md:2733 msgid "`chdir` — module" msgstr "`chdir` — módulo" -#: src/api/reference.md:2729 +#: src/api/reference.md:2734 msgid "`constrainedMemory` — module" msgstr "`constrainedMemory` — módulo" -#: src/api/reference.md:2730 +#: src/api/reference.md:2735 msgid "`cpuUsage` — module" msgstr "`cpuUsage` — módulo" -#: src/api/reference.md:2731 +#: src/api/reference.md:2736 msgid "`cwd` — module" msgstr "`cwd` — módulo" -#: src/api/reference.md:2733 +#: src/api/reference.md:2738 msgid "`emitWarning` — module" msgstr "`emitWarning` — módulo" -#: src/api/reference.md:2734 +#: src/api/reference.md:2739 msgid "`execve` — module" msgstr "`execve` — módulo" -#: src/api/reference.md:2736 +#: src/api/reference.md:2741 msgid "`getActiveResourcesInfo` — module" msgstr "`getActiveResourcesInfo` — módulo" -#: src/api/reference.md:2737 +#: src/api/reference.md:2742 msgid "`getBuiltinModule` — module" msgstr "`getBuiltinModule` — módulo" -#: src/api/reference.md:2738 +#: src/api/reference.md:2743 msgid "`getegid` — module" msgstr "`getegid` — módulo" -#: src/api/reference.md:2739 +#: src/api/reference.md:2744 msgid "`geteuid` — module" msgstr "`geteuid` — módulo" -#: src/api/reference.md:2740 +#: src/api/reference.md:2745 msgid "`getgid` — module" msgstr "`getgid` — módulo" -#: src/api/reference.md:2741 +#: src/api/reference.md:2746 msgid "`getgroups` — module" msgstr "`getgroups` — módulo" -#: src/api/reference.md:2742 +#: src/api/reference.md:2747 msgid "`getuid` — module" msgstr "`getuid` — módulo" -#: src/api/reference.md:2743 +#: src/api/reference.md:2748 msgid "`hasUncaughtExceptionCaptureCallback` — module" msgstr "`hasUncaughtExceptionCaptureCallback` — módulo" -#: src/api/reference.md:2744 +#: src/api/reference.md:2749 msgid "`hrtime` — module" msgstr "`hrtime` — módulo" -#: src/api/reference.md:2745 +#: src/api/reference.md:2750 msgid "`initgroups` — module" msgstr "`initgroups` — módulo" -#: src/api/reference.md:2746 +#: src/api/reference.md:2751 msgid "`kill` — module" msgstr "`kill` — módulo" -#: src/api/reference.md:2747 +#: src/api/reference.md:2752 msgid "`loadEnvFile` — module" msgstr "`loadEnvFile` — módulo" -#: src/api/reference.md:2748 +#: src/api/reference.md:2753 msgid "`memoryUsage` — module" msgstr "`memoryUsage` — módulo" -#: src/api/reference.md:2749 +#: src/api/reference.md:2754 msgid "`nextTick` — module" msgstr "`nextTick` — módulo" -#: src/api/reference.md:2750 +#: src/api/reference.md:2755 msgid "`openStdin` — module" msgstr "`openStdin` — módulo" -#: src/api/reference.md:2751 +#: src/api/reference.md:2756 msgid "`reallyExit` — module" msgstr "`reallyExit` — módulo" -#: src/api/reference.md:2752 +#: src/api/reference.md:2757 msgid "`ref` — module" msgstr "`ref` — módulo" -#: src/api/reference.md:2753 +#: src/api/reference.md:2758 msgid "`resourceUsage` — module" msgstr "`resourceUsage` — módulo" -#: src/api/reference.md:2754 src/api/reference.md:2755 +#: src/api/reference.md:2759 src/api/reference.md:2760 msgid "`setSourceMapsEnabled` — module" msgstr "`setSourceMapsEnabled` — módulo" -#: src/api/reference.md:2756 +#: src/api/reference.md:2761 msgid "`setUncaughtExceptionCaptureCallback` — module" msgstr "`setUncaughtExceptionCaptureCallback` — módulo" -#: src/api/reference.md:2757 +#: src/api/reference.md:2762 msgid "`setegid` — module" msgstr "`setegid` — módulo" -#: src/api/reference.md:2758 +#: src/api/reference.md:2763 msgid "`seteuid` — module" msgstr "`seteuid` — módulo" -#: src/api/reference.md:2759 +#: src/api/reference.md:2764 msgid "`setgid` — module" msgstr "`setgid` — módulo" -#: src/api/reference.md:2760 +#: src/api/reference.md:2765 msgid "`setgroups` — module" msgstr "`setgroups` — módulo" -#: src/api/reference.md:2761 +#: src/api/reference.md:2766 msgid "`setuid` — module" msgstr "`setuid` — módulo" -#: src/api/reference.md:2762 src/api/reference.md:2763 +#: src/api/reference.md:2767 src/api/reference.md:2768 msgid "`sourceMapsEnabled` — module" msgstr "`sourceMapsEnabled` — módulo" -#: src/api/reference.md:2764 +#: src/api/reference.md:2769 msgid "`threadCpuUsage` — module" msgstr "`threadCpuUsage` — módulo" -#: src/api/reference.md:2765 +#: src/api/reference.md:2770 msgid "`umask` — module" msgstr "`umask` — módulo" -#: src/api/reference.md:2766 +#: src/api/reference.md:2771 msgid "`unref` — module" msgstr "`unref` — módulo" -#: src/api/reference.md:2771 +#: src/api/reference.md:2776 msgid "`_eval`" msgstr "`_eval`" -#: src/api/reference.md:2772 +#: src/api/reference.md:2777 msgid "`_events`" msgstr "`_events`" -#: src/api/reference.md:2773 +#: src/api/reference.md:2778 msgid "`_eventsCount`" msgstr "`_eventsCount`" -#: src/api/reference.md:2774 +#: src/api/reference.md:2779 msgid "`_exiting`" msgstr "`_exiting`" -#: src/api/reference.md:2775 +#: src/api/reference.md:2780 msgid "`_maxListeners`" msgstr "`_maxListeners`" -#: src/api/reference.md:2776 +#: src/api/reference.md:2781 msgid "`_preload_modules`" msgstr "`_preload_modules`" -#: src/api/reference.md:2777 +#: src/api/reference.md:2782 msgid "`allowedNodeEnvironmentFlags`" msgstr "`allowedNodeEnvironmentFlags`" -#: src/api/reference.md:2778 +#: src/api/reference.md:2783 msgid "`arch`" msgstr "`arch`" -#: src/api/reference.md:2779 +#: src/api/reference.md:2784 msgid "`argv`" msgstr "`argv`" -#: src/api/reference.md:2780 +#: src/api/reference.md:2785 msgid "`argv0`" msgstr "`argv0`" -#: src/api/reference.md:2781 src/widgets/creating-widgets.md:55 +#: src/api/reference.md:2786 src/widgets/creating-widgets.md:55 msgid "`config`" msgstr "`config`" -#: src/api/reference.md:2782 +#: src/api/reference.md:2787 msgid "`debugPort`" msgstr "`debugPort`" -#: src/api/reference.md:2784 src/container/containers.md:52 +#: src/api/reference.md:2789 src/container/containers.md:52 msgid "`env`" msgstr "`env`" -#: src/api/reference.md:2785 +#: src/api/reference.md:2790 msgid "`execArgv`" msgstr "`execArgv`" -#: src/api/reference.md:2786 +#: src/api/reference.md:2791 msgid "`execPath`" msgstr "`execPath`" -#: src/api/reference.md:2787 +#: src/api/reference.md:2792 msgid "`features`" msgstr "`features`" -#: src/api/reference.md:2788 +#: src/api/reference.md:2793 msgid "`finalization`" msgstr "`finalization`" -#: src/api/reference.md:2789 +#: src/api/reference.md:2794 msgid "`moduleLoadList`" msgstr "`moduleLoadList`" -#: src/api/reference.md:2790 +#: src/api/reference.md:2795 msgid "`permission`" msgstr "`permission`" -#: src/api/reference.md:2791 +#: src/api/reference.md:2796 msgid "`pid`" msgstr "`pid`" -#: src/api/reference.md:2792 +#: src/api/reference.md:2797 msgid "`platform`" msgstr "`platform`" -#: src/api/reference.md:2793 +#: src/api/reference.md:2798 msgid "`ppid`" msgstr "`ppid`" -#: src/api/reference.md:2794 src/contributing/building.md:59 +#: src/api/reference.md:2799 src/contributing/building.md:59 msgid "`release`" msgstr "`release`" -#: src/api/reference.md:2795 +#: src/api/reference.md:2800 msgid "`report`" msgstr "`report`" -#: src/api/reference.md:2801 +#: src/api/reference.md:2806 msgid "`versions`" msgstr "`versions`" -#: src/api/reference.md:2803 +#: src/api/reference.md:2808 msgid "`punycode`" msgstr "`punycode`" -#: src/api/reference.md:2807 src/api/reference.md:2822 +#: src/api/reference.md:2812 src/api/reference.md:2827 msgid "`decode` — module" msgstr "`decode` — módulo" -#: src/api/reference.md:2808 src/api/reference.md:2823 +#: src/api/reference.md:2813 src/api/reference.md:2828 msgid "`encode` — module" msgstr "`encode` — módulo" -#: src/api/reference.md:2809 +#: src/api/reference.md:2814 msgid "`toASCII` — module" msgstr "`toASCII` — módulo" -#: src/api/reference.md:2810 +#: src/api/reference.md:2815 msgid "`toUnicode` — module" msgstr "`toUnicode` — módulo" -#: src/api/reference.md:2815 +#: src/api/reference.md:2820 msgid "`ucs2`" msgstr "`ucs2`" -#: src/api/reference.md:2818 +#: src/api/reference.md:2823 msgid "`querystring`" msgstr "`querystring`" -#: src/api/reference.md:2824 +#: src/api/reference.md:2829 msgid "`escape` — module" msgstr "`escape` — módulo" -#: src/api/reference.md:2826 +#: src/api/reference.md:2831 msgid "`stringify` — module" msgstr "`stringify` — módulo" -#: src/api/reference.md:2827 +#: src/api/reference.md:2832 msgid "`unescape` — module" msgstr "`unescape` — módulo" -#: src/api/reference.md:2828 +#: src/api/reference.md:2833 msgid "`unescapeBuffer` — module" msgstr "`unescapeBuffer` — módulo" -#: src/api/reference.md:2834 +#: src/api/reference.md:2839 msgid "`readline`" msgstr "`readline`" -#: src/api/reference.md:2838 +#: src/api/reference.md:2843 msgid "`clearLine` — module" msgstr "`clearLine` — módulo" -#: src/api/reference.md:2839 +#: src/api/reference.md:2844 msgid "`clearScreenDown` — module" msgstr "`clearScreenDown` — módulo" -#: src/api/reference.md:2841 src/api/reference.md:2868 +#: src/api/reference.md:2846 src/api/reference.md:2877 msgid "`createInterface` — module" msgstr "`createInterface` — módulo" -#: src/api/reference.md:2842 +#: src/api/reference.md:2847 msgid "`cursorTo` — module" msgstr "`cursorTo` — módulo" -#: src/api/reference.md:2843 +#: src/api/reference.md:2848 msgid "`emitKeypressEvents` — module" msgstr "`emitKeypressEvents` — módulo" -#: src/api/reference.md:2844 +#: src/api/reference.md:2849 msgid "`getCursorPos` — instance" msgstr "`getCursorPos` — instancia" -#: src/api/reference.md:2845 +#: src/api/reference.md:2850 msgid "`getPrompt` — instance" msgstr "`getPrompt` — instancia" -#: src/api/reference.md:2846 +#: src/api/reference.md:2851 msgid "`iterator` — instance" msgstr "`iterator` — instancia" -#: src/api/reference.md:2847 +#: src/api/reference.md:2852 msgid "`line` — instance" msgstr "`line` — instancia" -#: src/api/reference.md:2848 +#: src/api/reference.md:2853 msgid "`moveCursor` — module" msgstr "`moveCursor` — módulo" -#: src/api/reference.md:2850 src/api/reference.md:3048 +#: src/api/reference.md:2855 src/api/reference.md:3057 msgid "`pause` — instance" msgstr "`pause` — instancia" -#: src/api/reference.md:2851 +#: src/api/reference.md:2856 msgid "`prompt` — instance" msgstr "`prompt` — instancia" -#: src/api/reference.md:2852 src/api/reference.md:2869 +#: src/api/reference.md:2857 src/api/reference.md:2878 msgid "`question` — instance" msgstr "`question` — instancia" -#: src/api/reference.md:2853 src/api/reference.md:3067 +#: src/api/reference.md:2858 src/api/reference.md:3076 msgid "`resume` — instance" msgstr "`resume` — instancia" -#: src/api/reference.md:2854 +#: src/api/reference.md:2859 msgid "`setPrompt` — instance" msgstr "`setPrompt` — instancia" -#: src/api/reference.md:2855 +#: src/api/reference.md:2860 msgid "`terminal` — instance" msgstr "`terminal` — instancia" -#: src/api/reference.md:2856 src/api/reference.md:3082 +#: src/api/reference.md:2861 src/api/reference.md:3091 msgid "`write` — instance" msgstr "`write` — instancia" -#: src/api/reference.md:2858 +#: src/api/reference.md:2867 msgid "`readline/promises`" msgstr "`readline/promises`" -#: src/api/reference.md:2862 +#: src/api/reference.md:2871 msgid "`Interface`" msgstr "`Interface`" -#: src/api/reference.md:2863 +#: src/api/reference.md:2872 msgid "`Readline`" msgstr "`Readline`" -#: src/api/reference.md:2871 +#: src/api/reference.md:2880 msgid "`repl`" msgstr "`repl`" -#: src/api/reference.md:2875 +#: src/api/reference.md:2884 msgid "`REPLServer`" msgstr "`REPLServer`" -#: src/api/reference.md:2876 +#: src/api/reference.md:2885 msgid "`Recoverable`" msgstr "`Recoverable`" -#: src/api/reference.md:2880 +#: src/api/reference.md:2889 msgid "" "`REPLServer` — module ⚠ **stub** — REPLServer shape only: never reads the " "input stream, and .write() evaluates just numeric literals, context lookups, " @@ -44328,43 +44072,43 @@ msgstr "" "flujo de entrada, y .write() evalúa sólo literales numéricos, búsquedas de " "contexto, y un solo '+'; no real JS eval loop (#4916)" -#: src/api/reference.md:2881 +#: src/api/reference.md:2890 msgid "`Recoverable` — module" msgstr "`Recoverable` — módulo" -#: src/api/reference.md:2882 +#: src/api/reference.md:2891 msgid "`addListener` — instance _(class: `REPLServer`)_" msgstr "`addListener` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2883 +#: src/api/reference.md:2892 msgid "`clearBufferedCommand` — instance _(class: `REPLServer`)_" msgstr "`clearBufferedCommand` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2884 +#: src/api/reference.md:2893 msgid "`defineCommand` — instance _(class: `REPLServer`)_" msgstr "`defineCommand` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2885 +#: src/api/reference.md:2894 msgid "`displayPrompt` — instance _(class: `REPLServer`)_" msgstr "`displayPrompt` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2886 +#: src/api/reference.md:2895 msgid "`emit` — instance _(class: `REPLServer`)_" msgstr "`emit` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2887 +#: src/api/reference.md:2896 msgid "`on` — instance _(class: `REPLServer`)_" msgstr "`on` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2888 +#: src/api/reference.md:2897 msgid "`once` — instance _(class: `REPLServer`)_" msgstr "`once` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2889 +#: src/api/reference.md:2898 msgid "`setupHistory` — instance _(class: `REPLServer`)_" msgstr "`setupHistory` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2890 +#: src/api/reference.md:2899 msgid "" "`start` — module ⚠ **stub** — REPLServer shape only: never reads the input " "stream, and .write() evaluates just numeric literals, context lookups, and a " @@ -44374,1411 +44118,1426 @@ msgstr "" "flujo de entrada, y .write() evalúa sólo literales numéricos, búsquedas de " "contexto, y un solo '+'; no real JS eval loop (#4916)" -#: src/api/reference.md:2891 +#: src/api/reference.md:2900 msgid "`write` — instance _(class: `REPLServer`)_" msgstr "`write` — instancia _(clase: `REPLServer`)_" -#: src/api/reference.md:2895 +#: src/api/reference.md:2904 msgid "`REPL_MODE_SLOPPY`" msgstr "`REPL_MODE_SLOPPY`" -#: src/api/reference.md:2896 +#: src/api/reference.md:2905 msgid "`REPL_MODE_STRICT`" msgstr "`REPL_MODE_STRICT`" -#: src/api/reference.md:2900 +#: src/api/reference.md:2909 msgid "`sea`" msgstr "`sea`" -#: src/api/reference.md:2904 +#: src/api/reference.md:2913 msgid "`getAsset` — module" msgstr "`getAsset` — módulo" -#: src/api/reference.md:2905 +#: src/api/reference.md:2914 msgid "`getAssetAsBlob` — module" msgstr "`getAssetAsBlob` — módulo" -#: src/api/reference.md:2906 +#: src/api/reference.md:2915 msgid "`getAssetKeys` — module" msgstr "`getAssetKeys` — módulo" -#: src/api/reference.md:2907 +#: src/api/reference.md:2916 msgid "`getRawAsset` — module" msgstr "`getRawAsset` — módulo" -#: src/api/reference.md:2908 +#: src/api/reference.md:2917 msgid "`isSea` — module" msgstr "`isSea` — módulo" -#: src/api/reference.md:2918 +#: src/api/reference.md:2927 msgid "`autoOrient` — instance" msgstr "`autoOrient` — instancia" -#: src/api/reference.md:2919 +#: src/api/reference.md:2928 msgid "`avif` — instance" msgstr "`avif` — instancia" -#: src/api/reference.md:2920 +#: src/api/reference.md:2929 msgid "`blur` — instance" msgstr "`blur` — instancia" -#: src/api/reference.md:2921 +#: src/api/reference.md:2930 msgid "`composite` — instance" msgstr "`composite` — instancia" -#: src/api/reference.md:2923 +#: src/api/reference.md:2932 msgid "`extend` — instance" msgstr "`extend` — instancia" -#: src/api/reference.md:2924 +#: src/api/reference.md:2933 msgid "`extract` — instance" msgstr "`extract` — instancia" -#: src/api/reference.md:2925 +#: src/api/reference.md:2934 msgid "`flip` — instance" msgstr "`flip` — instancia" -#: src/api/reference.md:2926 +#: src/api/reference.md:2935 msgid "`flop` — instance" msgstr "`flop` — instancia" -#: src/api/reference.md:2927 +#: src/api/reference.md:2936 msgid "`grayscale` — instance" msgstr "`grayscale` — instancia" -#: src/api/reference.md:2928 +#: src/api/reference.md:2937 msgid "`height` — instance" msgstr "`height` — instancia" -#: src/api/reference.md:2929 +#: src/api/reference.md:2938 msgid "`jpeg` — instance" msgstr "`jpeg` — instancia" -#: src/api/reference.md:2930 +#: src/api/reference.md:2939 msgid "`metadata` — instance" msgstr "`metadata` — instancia" -#: src/api/reference.md:2931 +#: src/api/reference.md:2940 msgid "`png` — instance" msgstr "`png` — instancia" -#: src/api/reference.md:2932 +#: src/api/reference.md:2941 msgid "`resize` — instance" msgstr "`resize` — instancia" -#: src/api/reference.md:2933 +#: src/api/reference.md:2942 msgid "`rotate` — instance" msgstr "`rotate` — instancia" -#: src/api/reference.md:2934 +#: src/api/reference.md:2943 msgid "`sharp` — module" msgstr "`sharp` — módulo" -#: src/api/reference.md:2935 +#: src/api/reference.md:2944 msgid "`sharpen` — instance" msgstr "`sharpen` — instancia" -#: src/api/reference.md:2936 +#: src/api/reference.md:2945 msgid "`toBuffer` — instance" msgstr "`toBuffer` — instancia" -#: src/api/reference.md:2937 +#: src/api/reference.md:2946 msgid "`toFile` — instance" msgstr "`toFile` — instancia" -#: src/api/reference.md:2938 +#: src/api/reference.md:2947 msgid "`trim` — instance" msgstr "`trim` — instancia" -#: src/api/reference.md:2939 +#: src/api/reference.md:2948 msgid "`webp` — instance" msgstr "`webp` — instancia" -#: src/api/reference.md:2940 +#: src/api/reference.md:2949 msgid "`width` — instance" msgstr "`width` — instancia" -#: src/api/reference.md:2942 +#: src/api/reference.md:2951 msgid "`sqlite`" msgstr "`sqlite`" -#: src/api/reference.md:2946 +#: src/api/reference.md:2955 msgid "`DatabaseSync`" msgstr "`DatabaseSync`" -#: src/api/reference.md:2947 +#: src/api/reference.md:2956 msgid "`SQLTagStore`" msgstr "`SQLTagStore`" -#: src/api/reference.md:2949 +#: src/api/reference.md:2958 msgid "`StatementSync`" msgstr "`StatementSync`" -#: src/api/reference.md:2953 +#: src/api/reference.md:2962 msgid "`@@__perry_wk_dispose` — instance" msgstr "`@@__perry_wk_dispose` — instancia" -#: src/api/reference.md:2954 +#: src/api/reference.md:2963 msgid "`DatabaseSync` — module" msgstr "`DatabaseSync` — módulo" -#: src/api/reference.md:2956 +#: src/api/reference.md:2965 msgid "`StatementSync` — module" msgstr "`StatementSync` — módulo" -#: src/api/reference.md:2957 +#: src/api/reference.md:2966 msgid "`__perry_dispose__` — instance" msgstr "`__perry_dispose__` — instancia" -#: src/api/reference.md:2958 +#: src/api/reference.md:2967 msgid "`aggregate` — instance _(class: `DatabaseSync`)_" msgstr "`aggregate` — instancia _(clase: `DatabaseSync`)_" -#: src/api/reference.md:2959 +#: src/api/reference.md:2968 msgid "`all` — instance _(class: `SQLTagStore`)_" msgstr "`all` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2961 +#: src/api/reference.md:2970 msgid "`applyChangeset` — instance" msgstr "`applyChangeset` — instancia" -#: src/api/reference.md:2962 +#: src/api/reference.md:2971 msgid "`backup` — module" msgstr "`backup` — módulo" -#: src/api/reference.md:2963 +#: src/api/reference.md:2972 msgid "`capacity` — instance _(class: `SQLTagStore`)_" msgstr "`capacity` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2964 +#: src/api/reference.md:2973 msgid "`changeset` — instance" msgstr "`changeset` — instancia" -#: src/api/reference.md:2965 +#: src/api/reference.md:2974 msgid "`clear` — instance _(class: `SQLTagStore`)_" msgstr "`clear` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2968 +#: src/api/reference.md:2977 msgid "`createSession` — instance" msgstr "`createSession` — instancia" -#: src/api/reference.md:2969 +#: src/api/reference.md:2978 msgid "`createTagStore` — instance _(class: `DatabaseSync`)_" msgstr "`createTagStore` — instancia _(clase: `DatabaseSync`)_" -#: src/api/reference.md:2970 +#: src/api/reference.md:2979 msgid "`db` — instance _(class: `SQLTagStore`)_" msgstr "`db` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2971 +#: src/api/reference.md:2980 msgid "`deserialize` — instance" msgstr "`deserialize` — instancia" -#: src/api/reference.md:2972 +#: src/api/reference.md:2981 msgid "`enableDefensive` — instance _(class: `DatabaseSync`)_" msgstr "`enableDefensive` — instancia _(clase: `DatabaseSync`)_" -#: src/api/reference.md:2973 +#: src/api/reference.md:2982 msgid "`enableLoadExtension` — instance" msgstr "`enableLoadExtension` — instancia" -#: src/api/reference.md:2975 +#: src/api/reference.md:2984 msgid "`expandedSQL` — instance" msgstr "`expandedSQL` — instancia" -#: src/api/reference.md:2976 +#: src/api/reference.md:2985 msgid "`function` — instance _(class: `DatabaseSync`)_" msgstr "`function` — instancia _(clase: `DatabaseSync`)_" -#: src/api/reference.md:2977 +#: src/api/reference.md:2986 msgid "`get` — instance _(class: `SQLTagStore`)_" msgstr "`get` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2979 +#: src/api/reference.md:2988 msgid "`isOpen` — instance" msgstr "`isOpen` — instancia" -#: src/api/reference.md:2980 +#: src/api/reference.md:2989 msgid "`isTransaction` — instance" msgstr "`isTransaction` — instancia" -#: src/api/reference.md:2981 +#: src/api/reference.md:2990 msgid "`iterate` — instance _(class: `SQLTagStore`)_" msgstr "`iterate` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2983 +#: src/api/reference.md:2992 msgid "`limits` — instance" msgstr "`limits` — instancia" -#: src/api/reference.md:2984 +#: src/api/reference.md:2993 msgid "`loadExtension` — instance" msgstr "`loadExtension` — instancia" -#: src/api/reference.md:2985 +#: src/api/reference.md:2994 msgid "`location` — instance" msgstr "`location` — instancia" -#: src/api/reference.md:2986 +#: src/api/reference.md:2995 msgid "`open` — instance" msgstr "`open` — instancia" -#: src/api/reference.md:2987 +#: src/api/reference.md:2996 msgid "`patchset` — instance" msgstr "`patchset` — instancia" -#: src/api/reference.md:2989 +#: src/api/reference.md:2998 msgid "`run` — instance _(class: `SQLTagStore`)_" msgstr "`run` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2991 +#: src/api/reference.md:3000 msgid "`serialize` — instance" msgstr "`serialize` — instancia" -#: src/api/reference.md:2992 +#: src/api/reference.md:3001 msgid "`setAllowBareNamedParameters` — instance" msgstr "`setAllowBareNamedParameters` — instancia" -#: src/api/reference.md:2993 +#: src/api/reference.md:3002 msgid "`setAllowUnknownNamedParameters` — instance" msgstr "`setAllowUnknownNamedParameters` — instancia" -#: src/api/reference.md:2994 +#: src/api/reference.md:3003 msgid "`setAuthorizer` — instance _(class: `DatabaseSync`)_" msgstr "`setAuthorizer` — instancia _(clase: `DatabaseSync`)_" -#: src/api/reference.md:2995 +#: src/api/reference.md:3004 msgid "`setReadBigInts` — instance" msgstr "`setReadBigInts` — instancia" -#: src/api/reference.md:2996 +#: src/api/reference.md:3005 msgid "`setReturnArrays` — instance" msgstr "`setReturnArrays` — instancia" -#: src/api/reference.md:2997 +#: src/api/reference.md:3006 msgid "`size` — instance _(class: `SQLTagStore`)_" msgstr "`size` — instancia _(clase: `SQLTagStore`)_" -#: src/api/reference.md:2998 +#: src/api/reference.md:3007 msgid "`sourceSQL` — instance" msgstr "`sourceSQL` — instancia" -#: src/api/reference.md:3004 +#: src/api/reference.md:3013 msgid "`stream`" msgstr "`stream`" -#: src/api/reference.md:3008 +#: src/api/reference.md:3017 msgid "`Duplex`" msgstr "`Duplex`" -#: src/api/reference.md:3009 +#: src/api/reference.md:3018 msgid "`PassThrough`" msgstr "`PassThrough`" -#: src/api/reference.md:3010 +#: src/api/reference.md:3019 msgid "`Readable`" msgstr "`Readable`" -#: src/api/reference.md:3012 +#: src/api/reference.md:3021 msgid "`Transform`" msgstr "`Transform`" -#: src/api/reference.md:3013 +#: src/api/reference.md:3022 msgid "`Writable`" msgstr "`Writable`" -#: src/api/reference.md:3017 +#: src/api/reference.md:3026 msgid "`_isArrayBufferView` — module" msgstr "`_isArrayBufferView` — módulo" -#: src/api/reference.md:3018 +#: src/api/reference.md:3027 msgid "`_isUint8Array` — module" msgstr "`_isUint8Array` — módulo" -#: src/api/reference.md:3019 +#: src/api/reference.md:3028 msgid "`_uint8ArrayToBuffer` — module" msgstr "`_uint8ArrayToBuffer` — módulo" -#: src/api/reference.md:3020 +#: src/api/reference.md:3029 msgid "`addAbortSignal` — module" msgstr "`addAbortSignal` — módulo" -#: src/api/reference.md:3022 +#: src/api/reference.md:3031 msgid "`allowHalfOpen` — instance" msgstr "`allowHalfOpen` — instancia" -#: src/api/reference.md:3023 +#: src/api/reference.md:3032 msgid "`closed` — instance" msgstr "`closed` — instancia" -#: src/api/reference.md:3024 +#: src/api/reference.md:3033 msgid "`compose` — module" msgstr "`compose` — módulo" -#: src/api/reference.md:3025 +#: src/api/reference.md:3034 msgid "`cork` — instance" msgstr "`cork` — instancia" -#: src/api/reference.md:3027 +#: src/api/reference.md:3036 msgid "`destroy` — instance" msgstr "`destroy` — instancia" -#: src/api/reference.md:3028 +#: src/api/reference.md:3037 msgid "`destroyed` — instance" msgstr "`destroyed` — instancia" -#: src/api/reference.md:3029 +#: src/api/reference.md:3038 msgid "`duplexPair` — module" msgstr "`duplexPair` — módulo" -#: src/api/reference.md:3031 +#: src/api/reference.md:3040 msgid "`end` — instance" msgstr "`end` — instancia" -#: src/api/reference.md:3032 +#: src/api/reference.md:3041 msgid "`errored` — instance" msgstr "`errored` — instancia" -#: src/api/reference.md:3034 src/api/reference.md:3108 -#: src/api/reference.md:3109 +#: src/api/reference.md:3043 src/api/reference.md:3117 +#: src/api/reference.md:3118 msgid "`finished` — module" msgstr "`finished` — módulo" -#: src/api/reference.md:3035 +#: src/api/reference.md:3044 msgid "`getDefaultHighWaterMark` — module" msgstr "`getDefaultHighWaterMark` — módulo" -#: src/api/reference.md:3037 +#: src/api/reference.md:3046 msgid "`isDestroyed` — module" msgstr "`isDestroyed` — módulo" -#: src/api/reference.md:3038 +#: src/api/reference.md:3047 msgid "`isDisturbed` — module" msgstr "`isDisturbed` — módulo" -#: src/api/reference.md:3039 +#: src/api/reference.md:3048 msgid "`isErrored` — module" msgstr "`isErrored` — módulo" -#: src/api/reference.md:3040 +#: src/api/reference.md:3049 msgid "`isPaused` — instance" msgstr "`isPaused` — instancia" -#: src/api/reference.md:3041 +#: src/api/reference.md:3050 msgid "`isReadable` — module" msgstr "`isReadable` — módulo" -#: src/api/reference.md:3042 +#: src/api/reference.md:3051 msgid "`isWritable` — module" msgstr "`isWritable` — módulo" -#: src/api/reference.md:3049 +#: src/api/reference.md:3058 msgid "`pipe` — instance" msgstr "`pipe` — instancia" -#: src/api/reference.md:3050 src/api/reference.md:3110 -#: src/api/reference.md:3111 +#: src/api/reference.md:3059 src/api/reference.md:3119 +#: src/api/reference.md:3120 msgid "`pipeline` — module" msgstr "`pipeline` — módulo" -#: src/api/reference.md:3053 +#: src/api/reference.md:3062 msgid "`push` — instance" msgstr "`push` — instancia" -#: src/api/reference.md:3055 +#: src/api/reference.md:3064 msgid "`read` — instance" msgstr "`read` — instancia" -#: src/api/reference.md:3057 +#: src/api/reference.md:3066 msgid "`readableAborted` — instance" msgstr "`readableAborted` — instancia" -#: src/api/reference.md:3058 +#: src/api/reference.md:3067 msgid "`readableDidRead` — instance" msgstr "`readableDidRead` — instancia" -#: src/api/reference.md:3059 +#: src/api/reference.md:3068 msgid "`readableEncoding` — instance" msgstr "`readableEncoding` — instancia" -#: src/api/reference.md:3061 +#: src/api/reference.md:3070 msgid "`readableFlowing` — instance" msgstr "`readableFlowing` — instancia" -#: src/api/reference.md:3062 +#: src/api/reference.md:3071 msgid "`readableHighWaterMark` — instance" msgstr "`readableHighWaterMark` — instancia" -#: src/api/reference.md:3063 +#: src/api/reference.md:3072 msgid "`readableLength` — instance" msgstr "`readableLength` — instancia" -#: src/api/reference.md:3064 +#: src/api/reference.md:3073 msgid "`readableObjectMode` — instance" msgstr "`readableObjectMode` — instancia" -#: src/api/reference.md:3068 +#: src/api/reference.md:3077 msgid "`setDefaultHighWaterMark` — module" msgstr "`setDefaultHighWaterMark` — módulo" -#: src/api/reference.md:3069 +#: src/api/reference.md:3078 msgid "`setEncoding` — instance" msgstr "`setEncoding` — instancia" -#: src/api/reference.md:3071 +#: src/api/reference.md:3080 msgid "`uncork` — instance" msgstr "`uncork` — instancia" -#: src/api/reference.md:3072 +#: src/api/reference.md:3081 msgid "`unpipe` — instance" msgstr "`unpipe` — instancia" -#: src/api/reference.md:3073 +#: src/api/reference.md:3082 msgid "`unshift` — instance" msgstr "`unshift` — instancia" -#: src/api/reference.md:3075 +#: src/api/reference.md:3084 msgid "`writableCorked` — instance" msgstr "`writableCorked` — instancia" -#: src/api/reference.md:3077 +#: src/api/reference.md:3086 msgid "`writableFinished` — instance" msgstr "`writableFinished` — instancia" -#: src/api/reference.md:3078 +#: src/api/reference.md:3087 msgid "`writableHighWaterMark` — instance" msgstr "`writableHighWaterMark` — instancia" -#: src/api/reference.md:3079 +#: src/api/reference.md:3088 msgid "`writableLength` — instance" msgstr "`writableLength` — instancia" -#: src/api/reference.md:3080 +#: src/api/reference.md:3089 msgid "`writableNeedDrain` — instance" msgstr "`writableNeedDrain` — instancia" -#: src/api/reference.md:3081 +#: src/api/reference.md:3090 msgid "`writableObjectMode` — instance" msgstr "`writableObjectMode` — instancia" -#: src/api/reference.md:3089 +#: src/api/reference.md:3098 msgid "`stream/consumers`" msgstr "`stream/consumers`" -#: src/api/reference.md:3093 +#: src/api/reference.md:3102 msgid "`arrayBuffer` — module" msgstr "`arrayBuffer` — módulo" -#: src/api/reference.md:3094 +#: src/api/reference.md:3103 msgid "`blob` — module" msgstr "`blob` — módulo" -#: src/api/reference.md:3095 +#: src/api/reference.md:3104 msgid "`buffer` — module" msgstr "`buffer` — módulo" -#: src/api/reference.md:3096 +#: src/api/reference.md:3105 msgid "`bytes` — module" msgstr "`bytes` — módulo" -#: src/api/reference.md:3097 +#: src/api/reference.md:3106 msgid "`json` — module" msgstr "`json` — módulo" -#: src/api/reference.md:3098 +#: src/api/reference.md:3107 msgid "`text` — module" msgstr "`text` — módulo" -#: src/api/reference.md:3104 +#: src/api/reference.md:3113 msgid "`stream/promises`" msgstr "`stream/promises`" -#: src/api/reference.md:3113 +#: src/api/reference.md:3122 msgid "`stream/web`" msgstr "`stream/web`" -#: src/api/reference.md:3117 src/api/reference.md:3143 +#: src/api/reference.md:3126 src/api/reference.md:3152 msgid "`ByteLengthQueuingStrategy`" msgstr "`ByteLengthQueuingStrategy`" -#: src/api/reference.md:3118 +#: src/api/reference.md:3127 msgid "`CompressionStream`" msgstr "`CompressionStream`" -#: src/api/reference.md:3119 src/api/reference.md:3144 +#: src/api/reference.md:3128 src/api/reference.md:3153 msgid "`CountQueuingStrategy`" msgstr "`CountQueuingStrategy`" -#: src/api/reference.md:3120 src/api/reference.md:3145 +#: src/api/reference.md:3129 src/api/reference.md:3154 msgid "`DecompressionStream`" msgstr "`DecompressionStream`" -#: src/api/reference.md:3121 +#: src/api/reference.md:3130 msgid "`ReadableByteStreamController`" msgstr "`ReadableByteStreamController`" -#: src/api/reference.md:3122 src/api/reference.md:3146 +#: src/api/reference.md:3131 src/api/reference.md:3155 msgid "`ReadableStream`" msgstr "`ReadableStream`" -#: src/api/reference.md:3123 +#: src/api/reference.md:3132 msgid "`ReadableStreamBYOBReader`" msgstr "`ReadableStreamBYOBReader`" -#: src/api/reference.md:3124 +#: src/api/reference.md:3133 msgid "`ReadableStreamBYOBRequest`" msgstr "`ReadableStreamBYOBRequest`" -#: src/api/reference.md:3125 +#: src/api/reference.md:3134 msgid "`ReadableStreamDefaultController`" msgstr "`ReadableStreamDefaultController`" -#: src/api/reference.md:3126 +#: src/api/reference.md:3135 msgid "`ReadableStreamDefaultReader`" msgstr "`ReadableStreamDefaultReader`" -#: src/api/reference.md:3127 +#: src/api/reference.md:3136 msgid "`TextDecoderStream`" msgstr "`TextDecoderStream`" -#: src/api/reference.md:3128 +#: src/api/reference.md:3137 msgid "`TextEncoderStream`" msgstr "`TextEncoderStream`" -#: src/api/reference.md:3129 src/api/reference.md:3149 +#: src/api/reference.md:3138 src/api/reference.md:3158 msgid "`TransformStream`" msgstr "`TransformStream`" -#: src/api/reference.md:3130 +#: src/api/reference.md:3139 msgid "`TransformStreamDefaultController`" msgstr "`TransformStreamDefaultController`" -#: src/api/reference.md:3131 src/api/reference.md:3150 +#: src/api/reference.md:3140 src/api/reference.md:3159 msgid "`WritableStream`" msgstr "`WritableStream`" -#: src/api/reference.md:3132 +#: src/api/reference.md:3141 msgid "`WritableStreamDefaultController`" msgstr "`WritableStreamDefaultController`" -#: src/api/reference.md:3133 +#: src/api/reference.md:3142 msgid "`WritableStreamDefaultWriter`" msgstr "`WritableStreamDefaultWriter`" -#: src/api/reference.md:3139 +#: src/api/reference.md:3148 msgid "`streams`" msgstr "`streams`" -#: src/api/reference.md:3147 src/api/reference.md:3169 -#: src/api/reference.md:3431 +#: src/api/reference.md:3156 src/api/reference.md:3178 +#: src/api/reference.md:3451 msgid "`TextDecoder`" msgstr "`TextDecoder`" -#: src/api/reference.md:3148 src/api/reference.md:3170 -#: src/api/reference.md:3432 +#: src/api/reference.md:3157 src/api/reference.md:3179 +#: src/api/reference.md:3452 msgid "`TextEncoder`" msgstr "`TextEncoder`" -#: src/api/reference.md:3152 +#: src/api/reference.md:3161 msgid "`string_decoder`" msgstr "`string_decoder`" -#: src/api/reference.md:3156 +#: src/api/reference.md:3165 msgid "`StringDecoder`" msgstr "`StringDecoder`" -#: src/api/reference.md:3160 +#: src/api/reference.md:3169 msgid "`end` — instance _(class: `StringDecoder`)_" msgstr "`end` — instancia _(clase: `StringDecoder`)_" -#: src/api/reference.md:3161 +#: src/api/reference.md:3170 msgid "`write` — instance _(class: `StringDecoder`)_" msgstr "`write` — instancia _(clase: `StringDecoder`)_" -#: src/api/reference.md:3163 +#: src/api/reference.md:3172 msgid "`sys`" msgstr "`sys`" -#: src/api/reference.md:3167 src/api/reference.md:3429 +#: src/api/reference.md:3176 src/api/reference.md:3449 msgid "`MIMEParams`" msgstr "`MIMEParams`" -#: src/api/reference.md:3168 src/api/reference.md:3430 +#: src/api/reference.md:3177 src/api/reference.md:3450 msgid "`MIMEType`" msgstr "`MIMEType`" -#: src/api/reference.md:3174 src/api/reference.md:3436 +#: src/api/reference.md:3183 src/api/reference.md:3456 msgid "`MIMEParams` — module" msgstr "`MIMEParams` — módulo" -#: src/api/reference.md:3175 src/api/reference.md:3437 +#: src/api/reference.md:3184 src/api/reference.md:3457 msgid "`MIMEType` — module" msgstr "`MIMEType` — módulo" -#: src/api/reference.md:3176 src/api/reference.md:3438 +#: src/api/reference.md:3185 src/api/reference.md:3458 msgid "`_errnoException` — module" msgstr "`_errnoException` — módulo" -#: src/api/reference.md:3177 src/api/reference.md:3439 +#: src/api/reference.md:3186 src/api/reference.md:3459 msgid "`_exceptionWithHostPort` — module" msgstr "`_exceptionWithHostPort` — módulo" -#: src/api/reference.md:3178 src/api/reference.md:3440 +#: src/api/reference.md:3187 src/api/reference.md:3460 msgid "`_extend` — module" msgstr "`_extend` — módulo" -#: src/api/reference.md:3179 src/api/reference.md:3441 +#: src/api/reference.md:3188 src/api/reference.md:3461 msgid "`aborted` — module" msgstr "`aborted` — módulo" -#: src/api/reference.md:3180 src/api/reference.md:3442 +#: src/api/reference.md:3189 src/api/reference.md:3462 msgid "`callbackify` — module" msgstr "`callbackify` — módulo" -#: src/api/reference.md:3181 src/api/reference.md:3443 +#: src/api/reference.md:3190 src/api/reference.md:3463 msgid "`convertProcessSignalToExitCode` — module" msgstr "`convertProcessSignalToExitCode` — módulo" -#: src/api/reference.md:3183 src/api/reference.md:3445 +#: src/api/reference.md:3192 src/api/reference.md:3465 msgid "`debuglog` — module" msgstr "`debuglog` — módulo" -#: src/api/reference.md:3184 src/api/reference.md:3446 +#: src/api/reference.md:3193 src/api/reference.md:3466 msgid "`deprecate` — module" msgstr "`deprecate` — módulo" -#: src/api/reference.md:3185 src/api/reference.md:3447 +#: src/api/reference.md:3194 src/api/reference.md:3467 msgid "`diff` — module" msgstr "`diff` — módulo" -#: src/api/reference.md:3187 src/api/reference.md:3449 +#: src/api/reference.md:3196 src/api/reference.md:3469 msgid "`formatWithOptions` — module" msgstr "`formatWithOptions` — módulo" -#: src/api/reference.md:3188 src/api/reference.md:3450 +#: src/api/reference.md:3197 src/api/reference.md:3470 msgid "`getCallSites` — module" msgstr "`getCallSites` — módulo" -#: src/api/reference.md:3189 src/api/reference.md:3451 +#: src/api/reference.md:3198 src/api/reference.md:3471 msgid "`getSystemErrorMap` — module" msgstr "`getSystemErrorMap` — módulo" -#: src/api/reference.md:3190 src/api/reference.md:3452 +#: src/api/reference.md:3199 src/api/reference.md:3472 msgid "`getSystemErrorMessage` — module" msgstr "`getSystemErrorMessage` — módulo" -#: src/api/reference.md:3191 src/api/reference.md:3453 +#: src/api/reference.md:3200 src/api/reference.md:3473 msgid "`getSystemErrorName` — module" msgstr "`getSystemErrorName` — módulo" -#: src/api/reference.md:3192 src/api/reference.md:3454 +#: src/api/reference.md:3201 src/api/reference.md:3474 msgid "`inherits` — module" msgstr "`inherits` — módulo" -#: src/api/reference.md:3194 src/api/reference.md:3456 +#: src/api/reference.md:3203 src/api/reference.md:3476 msgid "`isArray` — module" msgstr "`isArray` — módulo" -#: src/api/reference.md:3195 src/api/reference.md:3457 +#: src/api/reference.md:3204 src/api/reference.md:3477 msgid "`isDeepStrictEqual` — module" msgstr "`isDeepStrictEqual` — módulo" -#: src/api/reference.md:3196 src/api/reference.md:3458 +#: src/api/reference.md:3205 src/api/reference.md:3478 msgid "`parseArgs` — module" msgstr "`parseArgs` — módulo" -#: src/api/reference.md:3197 src/api/reference.md:3459 +#: src/api/reference.md:3206 src/api/reference.md:3479 msgid "`parseEnv` — module" msgstr "`parseEnv` — módulo" -#: src/api/reference.md:3198 src/api/reference.md:3460 +#: src/api/reference.md:3207 src/api/reference.md:3480 msgid "`promisify` — module" msgstr "`promisify` — módulo" -#: src/api/reference.md:3199 src/api/reference.md:3461 +#: src/api/reference.md:3208 src/api/reference.md:3481 msgid "`setTraceSigInt` — module" msgstr "`setTraceSigInt` — módulo" -#: src/api/reference.md:3200 src/api/reference.md:3462 +#: src/api/reference.md:3209 src/api/reference.md:3482 msgid "`stripVTControlCharacters` — module" msgstr "`stripVTControlCharacters` — módulo" -#: src/api/reference.md:3201 src/api/reference.md:3463 +#: src/api/reference.md:3210 src/api/reference.md:3483 msgid "`styleText` — module" msgstr "`styleText` — módulo" -#: src/api/reference.md:3202 src/api/reference.md:3464 +#: src/api/reference.md:3211 src/api/reference.md:3484 msgid "`toUSVString` — module" msgstr "`toUSVString` — módulo" -#: src/api/reference.md:3203 src/api/reference.md:3465 +#: src/api/reference.md:3212 src/api/reference.md:3485 msgid "`transferableAbortController` — module" msgstr "`transferableAbortController` — módulo" -#: src/api/reference.md:3204 src/api/reference.md:3466 +#: src/api/reference.md:3213 src/api/reference.md:3486 msgid "`transferableAbortSignal` — module" msgstr "`transferableAbortSignal` — módulo" -#: src/api/reference.md:3209 src/api/reference.md:3471 src/cli/commands.md:26 +#: src/api/reference.md:3218 src/api/reference.md:3491 src/cli/commands.md:26 msgid "`types`" msgstr "`types`" -#: src/api/reference.md:3211 +#: src/api/reference.md:3220 msgid "`test`" msgstr "`test`" -#: src/api/reference.md:3215 +#: src/api/reference.md:3224 msgid "`after` — module" msgstr "`after` — módulo" -#: src/api/reference.md:3216 +#: src/api/reference.md:3225 msgid "`afterEach` — module" msgstr "`afterEach` — módulo" -#: src/api/reference.md:3217 +#: src/api/reference.md:3226 msgid "`before` — module" msgstr "`before` — módulo" -#: src/api/reference.md:3218 +#: src/api/reference.md:3227 msgid "`beforeEach` — module" msgstr "`beforeEach` — módulo" -#: src/api/reference.md:3220 +#: src/api/reference.md:3229 msgid "`describe` — module" msgstr "`describe` — módulo" -#: src/api/reference.md:3221 +#: src/api/reference.md:3230 msgid "`enable` — module _(class: `timers`)_" msgstr "`enable` — módulo _(clase: `timers`)_" -#: src/api/reference.md:3222 +#: src/api/reference.md:3231 msgid "`expectFailure` — module" msgstr "`expectFailure` — módulo" -#: src/api/reference.md:3223 +#: src/api/reference.md:3232 msgid "`fn` — module _(class: `mock`)_" msgstr "`fn` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3224 +#: src/api/reference.md:3233 msgid "`getter` — module _(class: `mock`)_" msgstr "`getter` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3225 +#: src/api/reference.md:3234 msgid "`it` — module" msgstr "`it` — módulo" -#: src/api/reference.md:3226 +#: src/api/reference.md:3235 msgid "`method` — module _(class: `mock`)_" msgstr "`method` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3227 +#: src/api/reference.md:3236 msgid "`only` — module" msgstr "`only` — módulo" -#: src/api/reference.md:3228 +#: src/api/reference.md:3237 msgid "`property` — module _(class: `mock`)_" msgstr "`property` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3229 +#: src/api/reference.md:3238 msgid "`reset` — module _(class: `mock`)_" msgstr "`reset` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3230 +#: src/api/reference.md:3239 msgid "`restoreAll` — module _(class: `mock`)_" msgstr "`restoreAll` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3232 +#: src/api/reference.md:3241 msgid "`runAll` — module _(class: `timers`)_" msgstr "`runAll` — módulo _(clase: `timers`)_" -#: src/api/reference.md:3233 +#: src/api/reference.md:3242 msgid "`setDefaultSnapshotSerializers` — module _(class: `snapshot`)_" msgstr "`setDefaultSnapshotSerializers` — módulo _(clase: `snapshot`)_" -#: src/api/reference.md:3234 +#: src/api/reference.md:3243 msgid "`setResolveSnapshotPath` — module _(class: `snapshot`)_" msgstr "`setResolveSnapshotPath` — módulo _(clase: `snapshot`)_" -#: src/api/reference.md:3235 +#: src/api/reference.md:3244 msgid "`setTime` — module _(class: `timers`)_" msgstr "`setTime` — módulo _(clase: `timers`)_" -#: src/api/reference.md:3236 +#: src/api/reference.md:3245 msgid "`setter` — module _(class: `mock`)_" msgstr "`setter` — módulo _(clase: `mock`)_" -#: src/api/reference.md:3237 +#: src/api/reference.md:3246 msgid "`skip` — module" msgstr "`skip` — módulo" -#: src/api/reference.md:3238 +#: src/api/reference.md:3247 msgid "`suite` — module" msgstr "`suite` — módulo" -#: src/api/reference.md:3239 +#: src/api/reference.md:3248 msgid "`test` — module" msgstr "`test` — módulo" -#: src/api/reference.md:3240 +#: src/api/reference.md:3249 msgid "`tick` — module _(class: `timers`)_" msgstr "`tick` — módulo _(clase: `timers`)_" -#: src/api/reference.md:3241 +#: src/api/reference.md:3250 msgid "`todo` — module" msgstr "`todo` — módulo" -#: src/api/reference.md:3246 +#: src/api/reference.md:3255 msgid "`mock`" msgstr "`mock`" -#: src/api/reference.md:3247 +#: src/api/reference.md:3256 msgid "`snapshot`" msgstr "`snapshot`" -#: src/api/reference.md:3249 +#: src/api/reference.md:3258 msgid "`test/reporters`" msgstr "`test/reporters`" -#: src/api/reference.md:3253 +#: src/api/reference.md:3262 msgid "`dot` — module" msgstr "`dot` — módulo" -#: src/api/reference.md:3254 +#: src/api/reference.md:3263 msgid "`junit` — module" msgstr "`junit` — módulo" -#: src/api/reference.md:3255 +#: src/api/reference.md:3264 msgid "`lcov` — module" msgstr "`lcov` — módulo" -#: src/api/reference.md:3256 +#: src/api/reference.md:3265 msgid "`spec` — module" msgstr "`spec` — módulo" -#: src/api/reference.md:3257 +#: src/api/reference.md:3266 msgid "`tap` — module" msgstr "`tap` — módulo" -#: src/api/reference.md:3263 +#: src/api/reference.md:3272 msgid "`timers`" msgstr "`timers`" -#: src/api/reference.md:3267 +#: src/api/reference.md:3276 msgid "`clearImmediate` — module" msgstr "`clearImmediate` — módulo" -#: src/api/reference.md:3268 +#: src/api/reference.md:3277 msgid "`clearInterval` — module" msgstr "`clearInterval` — módulo" -#: src/api/reference.md:3269 +#: src/api/reference.md:3278 msgid "`clearTimeout` — module" msgstr "`clearTimeout` — módulo" -#: src/api/reference.md:3270 src/api/reference.md:3282 +#: src/api/reference.md:3279 src/api/reference.md:3291 msgid "`setImmediate` — module" msgstr "`setImmediate` — módulo" -#: src/api/reference.md:3271 src/api/reference.md:3283 +#: src/api/reference.md:3280 src/api/reference.md:3292 msgid "`setInterval` — module" msgstr "`setInterval` — módulo" -#: src/api/reference.md:3272 src/api/reference.md:3284 +#: src/api/reference.md:3281 src/api/reference.md:3293 msgid "`setTimeout` — module" msgstr "`setTimeout` — módulo" -#: src/api/reference.md:3278 +#: src/api/reference.md:3287 msgid "`timers/promises`" msgstr "`timers/promises`" -#: src/api/reference.md:3288 +#: src/api/reference.md:3297 msgid "`scheduler`" msgstr "`scheduler`" -#: src/api/reference.md:3290 +#: src/api/reference.md:3299 msgid "`tls`" msgstr "`tls`" -#: src/api/reference.md:3294 +#: src/api/reference.md:3303 msgid "`SecureContext`" msgstr "`SecureContext`" -#: src/api/reference.md:3298 +#: src/api/reference.md:3307 msgid "`SecureContext` — module" msgstr "`SecureContext` — módulo" -#: src/api/reference.md:3300 +#: src/api/reference.md:3309 msgid "`TLSSocket` — module" msgstr "`TLSSocket` — módulo" -#: src/api/reference.md:3303 +#: src/api/reference.md:3312 msgid "`checkServerIdentity` — module" msgstr "`checkServerIdentity` — módulo" -#: src/api/reference.md:3306 +#: src/api/reference.md:3315 msgid "`convertALPNProtocols` — module" msgstr "`convertALPNProtocols` — módulo" -#: src/api/reference.md:3307 +#: src/api/reference.md:3316 msgid "`createSecureContext` — module" msgstr "`createSecureContext` — módulo" -#: src/api/reference.md:3310 +#: src/api/reference.md:3319 msgid "`getCACertificates` — module" msgstr "`getCACertificates` — módulo" -#: src/api/reference.md:3311 +#: src/api/reference.md:3320 msgid "`getCertificateCompressionAlgorithms` — module" msgstr "`getCertificateCompressionAlgorithms` — módulo" -#: src/api/reference.md:3313 +#: src/api/reference.md:3322 msgid "`getTicketKeys` — instance _(class: `Server`)_" msgstr "`getTicketKeys` — instancia _(clase: `Server`)_" -#: src/api/reference.md:3317 +#: src/api/reference.md:3326 msgid "`on` — instance _(class: `Server`)_" msgstr "`on` — instancia _(clase: `Server`)_" -#: src/api/reference.md:3321 +#: src/api/reference.md:3330 msgid "`setDefaultCACertificates` — module" msgstr "`setDefaultCACertificates` — módulo" -#: src/api/reference.md:3322 +#: src/api/reference.md:3331 msgid "`setSecureContext` — instance _(class: `Server`)_" msgstr "`setSecureContext` — instancia _(clase: `Server`)_" -#: src/api/reference.md:3323 +#: src/api/reference.md:3332 msgid "`setTicketKeys` — instance _(class: `Server`)_" msgstr "`setTicketKeys` — instancia _(clase: `Server`)_" -#: src/api/reference.md:3327 +#: src/api/reference.md:3336 msgid "`CLIENT_RENEG_LIMIT`" msgstr "`CLIENT_RENEG_LIMIT`" -#: src/api/reference.md:3328 +#: src/api/reference.md:3337 msgid "`CLIENT_RENEG_WINDOW`" msgstr "`CLIENT_RENEG_WINDOW`" -#: src/api/reference.md:3329 +#: src/api/reference.md:3338 msgid "`DEFAULT_CIPHERS`" msgstr "`DEFAULT_CIPHERS`" -#: src/api/reference.md:3330 +#: src/api/reference.md:3339 msgid "`DEFAULT_ECDH_CURVE`" msgstr "`DEFAULT_ECDH_CURVE`" -#: src/api/reference.md:3331 +#: src/api/reference.md:3340 msgid "`DEFAULT_MAX_VERSION`" msgstr "`DEFAULT_MAX_VERSION`" -#: src/api/reference.md:3332 +#: src/api/reference.md:3341 msgid "`DEFAULT_MIN_VERSION`" msgstr "`DEFAULT_MIN_VERSION`" -#: src/api/reference.md:3333 +#: src/api/reference.md:3342 msgid "`rootCertificates`" msgstr "`rootCertificates`" -#: src/api/reference.md:3335 +#: src/api/reference.md:3344 +#, fuzzy +msgid "`trace_events`" +msgstr "`_events`" + +#: src/api/reference.md:3348 +#, fuzzy +msgid "`createTracing` — module" +msgstr "`createDomain` — módulo" + +#: src/api/reference.md:3349 +#, fuzzy +msgid "`getEnabledCategories` — module" +msgstr "`generatePrime` — módulo" + +#: src/api/reference.md:3355 msgid "`tty`" msgstr "`tty`" -#: src/api/reference.md:3344 +#: src/api/reference.md:3364 msgid "`ReadStream` — module" msgstr "`ReadStream` — módulo" -#: src/api/reference.md:3345 +#: src/api/reference.md:3365 msgid "`WriteStream` — module" msgstr "`WriteStream` — módulo" -#: src/api/reference.md:3346 +#: src/api/reference.md:3366 msgid "`_refreshSize` — instance _(class: `WriteStream`)_" msgstr "`_refreshSize` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3347 +#: src/api/reference.md:3367 msgid "`addListener` — instance _(class: `WriteStream`)_" msgstr "`addListener` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3348 +#: src/api/reference.md:3368 msgid "`clearLine` — instance _(class: `WriteStream`)_" msgstr "`clearLine` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3349 +#: src/api/reference.md:3369 msgid "`clearScreenDown` — instance _(class: `WriteStream`)_" msgstr "`clearScreenDown` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3350 +#: src/api/reference.md:3370 msgid "`cursorTo` — instance _(class: `WriteStream`)_" msgstr "`cursorTo` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3351 +#: src/api/reference.md:3371 msgid "`getColorDepth` — instance _(class: `WriteStream`)_" msgstr "`getColorDepth` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3352 +#: src/api/reference.md:3372 msgid "`getWindowSize` — instance _(class: `WriteStream`)_" msgstr "`getWindowSize` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3353 +#: src/api/reference.md:3373 msgid "`hasColors` — instance _(class: `WriteStream`)_" msgstr "`hasColors` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3354 +#: src/api/reference.md:3374 msgid "`isatty` — module" msgstr "`isatty` — módulo" -#: src/api/reference.md:3355 +#: src/api/reference.md:3375 msgid "`moveCursor` — instance _(class: `WriteStream`)_" msgstr "`moveCursor` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3356 +#: src/api/reference.md:3376 msgid "`off` — instance _(class: `WriteStream`)_" msgstr "`off` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3357 +#: src/api/reference.md:3377 msgid "`on` — instance _(class: `WriteStream`)_" msgstr "`on` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3358 +#: src/api/reference.md:3378 msgid "`once` — instance _(class: `WriteStream`)_" msgstr "`once` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3359 +#: src/api/reference.md:3379 msgid "`removeAllListeners` — instance _(class: `WriteStream`)_" msgstr "`removeAllListeners` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3360 +#: src/api/reference.md:3380 msgid "`removeListener` — instance _(class: `WriteStream`)_" msgstr "`removeListener` — instancia _(clase: `WriteStream`)_" -#: src/api/reference.md:3361 +#: src/api/reference.md:3381 msgid "`setRawMode` — instance _(class: `ReadStream`)_" msgstr "`setRawMode` — instancia _(clase: `ReadStream`)_" -#: src/api/reference.md:3367 +#: src/api/reference.md:3387 msgid "`flattenDiagnosticMessageText` — module" msgstr "`flattenDiagnosticMessageText` — módulo" -#: src/api/reference.md:3368 +#: src/api/reference.md:3388 msgid "`transpileModule` — module" msgstr "`transpileModule` — módulo" -#: src/api/reference.md:3372 +#: src/api/reference.md:3392 msgid "`DiagnosticCategory`" msgstr "`DiagnosticCategory`" -#: src/api/reference.md:3373 +#: src/api/reference.md:3393 msgid "`ModuleKind`" msgstr "`ModuleKind`" -#: src/api/reference.md:3374 +#: src/api/reference.md:3394 msgid "`ScriptTarget`" msgstr "`ScriptTarget`" -#: src/api/reference.md:3381 +#: src/api/reference.md:3401 msgid "`ProxyAgent`" msgstr "`ProxyAgent`" -#: src/api/reference.md:3386 +#: src/api/reference.md:3406 msgid "`ProxyAgent` — module" msgstr "`ProxyAgent` — módulo" -#: src/api/reference.md:3387 +#: src/api/reference.md:3407 msgid "`close` — instance _(class: `ProxyAgent`)_" msgstr "`close` — instancia _(clase: `ProxyAgent`)_" -#: src/api/reference.md:3389 +#: src/api/reference.md:3409 msgid "`destroy` — instance _(class: `ProxyAgent`)_" msgstr "`destroy` — instancia _(clase: `ProxyAgent`)_" -#: src/api/reference.md:3391 +#: src/api/reference.md:3411 msgid "`fetch` — module" msgstr "`fetch` — módulo" -#: src/api/reference.md:3392 +#: src/api/reference.md:3412 msgid "`getGlobalDispatcher` — module" msgstr "`getGlobalDispatcher` — módulo" -#: src/api/reference.md:3394 +#: src/api/reference.md:3414 msgid "`setGlobalDispatcher` — module" msgstr "`setGlobalDispatcher` — módulo" -#: src/api/reference.md:3400 +#: src/api/reference.md:3420 msgid "`URL`" msgstr "`URL`" -#: src/api/reference.md:3401 +#: src/api/reference.md:3421 msgid "`URLPattern`" msgstr "`URLPattern`" -#: src/api/reference.md:3402 +#: src/api/reference.md:3422 msgid "`URLSearchParams`" msgstr "`URLSearchParams`" -#: src/api/reference.md:3403 +#: src/api/reference.md:3423 msgid "`Url`" msgstr "`Url`" -#: src/api/reference.md:3407 +#: src/api/reference.md:3427 msgid "`Url` — module" msgstr "`Url` — módulo" -#: src/api/reference.md:3408 +#: src/api/reference.md:3428 msgid "`domainToASCII` — module" msgstr "`domainToASCII` — módulo" -#: src/api/reference.md:3409 +#: src/api/reference.md:3429 msgid "`domainToUnicode` — module" msgstr "`domainToUnicode` — módulo" -#: src/api/reference.md:3410 +#: src/api/reference.md:3430 msgid "`exec` — instance _(class: `URLPattern`)_" msgstr "`exec` — instancia _(clase: `URLPattern`)_" -#: src/api/reference.md:3412 +#: src/api/reference.md:3432 msgid "`fileURLToPathBuffer` — module" msgstr "`fileURLToPathBuffer` — módulo" -#: src/api/reference.md:3417 +#: src/api/reference.md:3437 msgid "`resolveObject` — module" msgstr "`resolveObject` — módulo" -#: src/api/reference.md:3418 +#: src/api/reference.md:3438 msgid "`test` — instance _(class: `URLPattern`)_" msgstr "`test` — instancia _(clase: `URLPattern`)_" -#: src/api/reference.md:3419 +#: src/api/reference.md:3439 msgid "`urlToHttpOptions` — module" msgstr "`urlToHttpOptions` — módulo" -#: src/api/reference.md:3425 +#: src/api/reference.md:3445 msgid "`util`" msgstr "`util`" -#: src/api/reference.md:3473 +#: src/api/reference.md:3493 msgid "`util/types`" msgstr "`util/types`" -#: src/api/reference.md:3477 +#: src/api/reference.md:3497 msgid "`isAnyArrayBuffer` — module" msgstr "`isAnyArrayBuffer` — módulo" -#: src/api/reference.md:3478 +#: src/api/reference.md:3498 msgid "`isArgumentsObject` — module" msgstr "`isArgumentsObject` — módulo" -#: src/api/reference.md:3479 +#: src/api/reference.md:3499 msgid "`isArrayBuffer` — module" msgstr "`isArrayBuffer` — módulo" -#: src/api/reference.md:3480 +#: src/api/reference.md:3500 msgid "`isArrayBufferView` — module" msgstr "`isArrayBufferView` — módulo" -#: src/api/reference.md:3481 +#: src/api/reference.md:3501 msgid "`isAsyncFunction` — module" msgstr "`isAsyncFunction` — módulo" -#: src/api/reference.md:3482 +#: src/api/reference.md:3502 msgid "`isBigInt64Array` — module" msgstr "`isBigInt64Array` — módulo" -#: src/api/reference.md:3483 +#: src/api/reference.md:3503 msgid "`isBigIntObject` — module" msgstr "`isBigIntObject` — módulo" -#: src/api/reference.md:3484 +#: src/api/reference.md:3504 msgid "`isBigUint64Array` — module" msgstr "`isBigUint64Array` — módulo" -#: src/api/reference.md:3485 +#: src/api/reference.md:3505 msgid "`isBooleanObject` — module" msgstr "`isBooleanObject` — módulo" -#: src/api/reference.md:3486 +#: src/api/reference.md:3506 msgid "`isBoxedPrimitive` — module" msgstr "`isBoxedPrimitive` — módulo" -#: src/api/reference.md:3487 +#: src/api/reference.md:3507 msgid "`isCryptoKey` — module" msgstr "`isCryptoKey` — módulo" -#: src/api/reference.md:3488 +#: src/api/reference.md:3508 msgid "`isDataView` — module" msgstr "`isDataView` — módulo" -#: src/api/reference.md:3489 +#: src/api/reference.md:3509 msgid "`isDate` — module" msgstr "`isDate` — módulo" -#: src/api/reference.md:3490 +#: src/api/reference.md:3510 msgid "`isExternal` — module" msgstr "`isExternal` — módulo" -#: src/api/reference.md:3491 +#: src/api/reference.md:3511 msgid "`isFloat16Array` — module" msgstr "`isFloat16Array` — módulo" -#: src/api/reference.md:3492 +#: src/api/reference.md:3512 msgid "`isFloat32Array` — module" msgstr "`isFloat32Array` — módulo" -#: src/api/reference.md:3493 +#: src/api/reference.md:3513 msgid "`isFloat64Array` — module" msgstr "`isFloat64Array` — módulo" -#: src/api/reference.md:3494 +#: src/api/reference.md:3514 msgid "`isGeneratorFunction` — module" msgstr "`isGeneratorFunction` — módulo" -#: src/api/reference.md:3495 +#: src/api/reference.md:3515 msgid "`isGeneratorObject` — module" msgstr "`isGeneratorObject` — módulo" -#: src/api/reference.md:3496 +#: src/api/reference.md:3516 msgid "`isInt16Array` — module" msgstr "`isInt16Array` — módulo" -#: src/api/reference.md:3497 +#: src/api/reference.md:3517 msgid "`isInt32Array` — module" msgstr "`isInt32Array` — módulo" -#: src/api/reference.md:3498 +#: src/api/reference.md:3518 msgid "`isInt8Array` — module" msgstr "`isInt8Array` — módulo" -#: src/api/reference.md:3499 +#: src/api/reference.md:3519 msgid "`isKeyObject` — module" msgstr "`isKeyObject` — módulo" -#: src/api/reference.md:3500 +#: src/api/reference.md:3520 msgid "`isMap` — module" msgstr "`isMap` — módulo" -#: src/api/reference.md:3501 +#: src/api/reference.md:3521 msgid "`isMapIterator` — module" msgstr "`isMapIterator` — módulo" -#: src/api/reference.md:3502 +#: src/api/reference.md:3522 msgid "`isModuleNamespaceObject` — module" msgstr "`isModuleNamespaceObject` — módulo" -#: src/api/reference.md:3503 +#: src/api/reference.md:3523 msgid "`isNativeError` — module" msgstr "`isNativeError` — módulo" -#: src/api/reference.md:3504 +#: src/api/reference.md:3524 msgid "`isNumberObject` — module" msgstr "`isNumberObject` — módulo" -#: src/api/reference.md:3505 +#: src/api/reference.md:3525 msgid "`isPromise` — module" msgstr "`isPromise` — módulo" -#: src/api/reference.md:3506 +#: src/api/reference.md:3526 msgid "`isProxy` — module" msgstr "`isProxy` — módulo" -#: src/api/reference.md:3507 +#: src/api/reference.md:3527 msgid "`isRegExp` — module" msgstr "`isRegExp` — módulo" -#: src/api/reference.md:3508 +#: src/api/reference.md:3528 msgid "`isSet` — module" msgstr "`isSet` — módulo" -#: src/api/reference.md:3509 +#: src/api/reference.md:3529 msgid "`isSetIterator` — module" msgstr "`isSetIterator` — módulo" -#: src/api/reference.md:3510 +#: src/api/reference.md:3530 msgid "`isSharedArrayBuffer` — module" msgstr "`isSharedArrayBuffer` — módulo" -#: src/api/reference.md:3511 +#: src/api/reference.md:3531 msgid "`isStringObject` — module" msgstr "`isStringObject` — módulo" -#: src/api/reference.md:3512 +#: src/api/reference.md:3532 msgid "`isSymbolObject` — module" msgstr "`isSymbolObject` — módulo" -#: src/api/reference.md:3513 +#: src/api/reference.md:3533 msgid "`isTypedArray` — module" msgstr "`isTypedArray` — módulo" -#: src/api/reference.md:3514 +#: src/api/reference.md:3534 msgid "`isUint16Array` — module" msgstr "`isUint16Array` — módulo" -#: src/api/reference.md:3515 +#: src/api/reference.md:3535 msgid "`isUint32Array` — module" msgstr "`isUint32Array` — módulo" -#: src/api/reference.md:3516 +#: src/api/reference.md:3536 msgid "`isUint8Array` — module" msgstr "`isUint8Array` — módulo" -#: src/api/reference.md:3517 +#: src/api/reference.md:3537 msgid "`isUint8ClampedArray` — module" msgstr "`isUint8ClampedArray` — módulo" -#: src/api/reference.md:3518 +#: src/api/reference.md:3538 msgid "`isWeakMap` — module" msgstr "`isWeakMap` — módulo" -#: src/api/reference.md:3519 +#: src/api/reference.md:3539 msgid "`isWeakSet` — module" msgstr "`isWeakSet` — módulo" -#: src/api/reference.md:3521 +#: src/api/reference.md:3541 msgid "`v8`" msgstr "`v8`" -#: src/api/reference.md:3525 +#: src/api/reference.md:3545 msgid "`DefaultDeserializer`" msgstr "`DefaultDeserializer`" -#: src/api/reference.md:3526 +#: src/api/reference.md:3546 msgid "`DefaultSerializer`" msgstr "`DefaultSerializer`" -#: src/api/reference.md:3527 +#: src/api/reference.md:3547 msgid "`Deserializer`" msgstr "`Deserializer`" -#: src/api/reference.md:3528 +#: src/api/reference.md:3548 msgid "`GCProfiler`" msgstr "`GCProfiler`" -#: src/api/reference.md:3529 +#: src/api/reference.md:3549 msgid "`Serializer`" msgstr "`Serializer`" -#: src/api/reference.md:3533 +#: src/api/reference.md:3553 msgid "`addDeserializeCallback` — instance _(class: `startupSnapshot`)_" msgstr "`addDeserializeCallback` — instancia _(clase: `startupSnapshot`)_" -#: src/api/reference.md:3534 +#: src/api/reference.md:3554 msgid "`addSerializeCallback` — instance _(class: `startupSnapshot`)_" msgstr "`addSerializeCallback` — instancia _(clase: `startupSnapshot`)_" -#: src/api/reference.md:3535 +#: src/api/reference.md:3555 msgid "`cachedDataVersionTag` — module" msgstr "`cachedDataVersionTag` — módulo" -#: src/api/reference.md:3536 +#: src/api/reference.md:3556 msgid "`createHook` — instance _(class: `promiseHooks`)_" msgstr "`createHook` — instancia _(clase: `promiseHooks`)_" -#: src/api/reference.md:3537 +#: src/api/reference.md:3557 msgid "`deserialize` — module" msgstr "`deserialize` — módulo" -#: src/api/reference.md:3538 +#: src/api/reference.md:3558 msgid "`getCppHeapStatistics` — module" msgstr "`getCppHeapStatistics` — módulo" -#: src/api/reference.md:3539 +#: src/api/reference.md:3559 msgid "" "`getHeapCodeStatistics` — module ⚠ **stub** — all fields 0; Perry compiles " "AOT, there is no JIT code heap (#4916)" @@ -45786,11 +45545,11 @@ msgstr "" "`getHeapCodeStatistics` — módulo ­ **talón** — todos los campos 0; Perry " "compila AOT, no hay un montón de código JIT (#4916)" -#: src/api/reference.md:3540 +#: src/api/reference.md:3560 msgid "`getHeapSnapshot` — module" msgstr "`getHeapSnapshot` — módulo" -#: src/api/reference.md:3541 +#: src/api/reference.md:3561 msgid "" "`getHeapSpaceStatistics` — module ⚠ **stub** — Node space names with all " "live usage attributed to old_space from Perry arenas; other spaces report 0 " @@ -45800,7 +45559,7 @@ msgstr "" "con todo uso en vivo atribuido a old_space de las arenas Perry; otros " "espacios reportan 0 (#4916)" -#: src/api/reference.md:3542 +#: src/api/reference.md:3562 msgid "" "`getHeapStatistics` — module ⚠ **stub** — Node shape, Perry numbers: " "total_heap_size/used_heap_size/malloced_memory/total_allocated_bytes from " @@ -45814,87 +45573,87 @@ msgstr "" "aplicado); \\*\\_ ejecutable, external_memory, las manos globales y los " "campos zap son 0 (#4916)" -#: src/api/reference.md:3543 +#: src/api/reference.md:3563 msgid "`isBuildingSnapshot` — instance _(class: `startupSnapshot`)_" msgstr "`isBuildingSnapshot` — instancia _(clase: `startupSnapshot`)_" -#: src/api/reference.md:3544 +#: src/api/reference.md:3564 msgid "`isStringOneByteRepresentation` — module" msgstr "`isStringOneByteRepresentation` — módulo" -#: src/api/reference.md:3545 +#: src/api/reference.md:3565 msgid "`onAfter` — instance _(class: `promiseHooks`)_" msgstr "`onAfter` — instancia _(clase: `promiseHooks`)_" -#: src/api/reference.md:3546 +#: src/api/reference.md:3566 msgid "`onBefore` — instance _(class: `promiseHooks`)_" msgstr "`onBefore` — instancia _(clase: `promiseHooks`)_" -#: src/api/reference.md:3547 +#: src/api/reference.md:3567 msgid "`onInit` — instance _(class: `promiseHooks`)_" msgstr "`onInit` — instancia _(clase: `promiseHooks`)_" -#: src/api/reference.md:3548 +#: src/api/reference.md:3568 msgid "`onSettled` — instance _(class: `promiseHooks`)_" msgstr "`onSettled` — instancia _(clase: `promiseHooks`)_" -#: src/api/reference.md:3549 +#: src/api/reference.md:3569 msgid "`queryObjects` — module" msgstr "`queryObjects` — módulo" -#: src/api/reference.md:3550 +#: src/api/reference.md:3570 msgid "`readDouble` — instance _(class: `Deserializer`)_" msgstr "`readDouble` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3551 +#: src/api/reference.md:3571 msgid "`readHeader` — instance _(class: `Deserializer`)_" msgstr "`readHeader` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3552 +#: src/api/reference.md:3572 msgid "`readRawBytes` — instance _(class: `Deserializer`)_" msgstr "`readRawBytes` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3553 +#: src/api/reference.md:3573 msgid "`readUint32` — instance _(class: `Deserializer`)_" msgstr "`readUint32` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3554 +#: src/api/reference.md:3574 msgid "`readUint64` — instance _(class: `Deserializer`)_" msgstr "`readUint64` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3555 +#: src/api/reference.md:3575 msgid "`readValue` — instance _(class: `Deserializer`)_" msgstr "`readValue` — instancia _(clase: `Deserializer`)_" -#: src/api/reference.md:3556 +#: src/api/reference.md:3576 msgid "`releaseBuffer` — instance _(class: `Serializer`)_" msgstr "`releaseBuffer` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3557 +#: src/api/reference.md:3577 msgid "`serialize` — module" msgstr "`serialize` — módulo" -#: src/api/reference.md:3558 +#: src/api/reference.md:3578 msgid "`setDeserializeMainFunction` — instance _(class: `startupSnapshot`)_" msgstr "`setDeserializeMainFunction` — instancia _(clase: `startupSnapshot`)_" -#: src/api/reference.md:3559 +#: src/api/reference.md:3579 msgid "`setFlagsFromString` — module" msgstr "`setFlagsFromString` — módulo" -#: src/api/reference.md:3560 +#: src/api/reference.md:3580 msgid "`setHeapSnapshotNearHeapLimit` — module" msgstr "`setHeapSnapshotNearHeapLimit` — módulo" -#: src/api/reference.md:3561 +#: src/api/reference.md:3581 msgid "`start` — instance _(class: `GCProfiler`)_" msgstr "`start` — instancia _(clase: `GCProfiler`)_" -#: src/api/reference.md:3562 +#: src/api/reference.md:3582 msgid "`startCpuProfile` — module" msgstr "`startCpuProfile` — módulo" -#: src/api/reference.md:3563 +#: src/api/reference.md:3583 msgid "" "`stop` — instance _(class: `GCProfiler`)_ ⚠ **stub** — report has the Node " "shape but the statistics array is always empty (#4916)" @@ -45902,491 +45661,495 @@ msgstr "" "`stop` — instancia _(clase: `GCProfiler`)_ ­ **talón** — el informe tiene la " "forma del Nodo pero la matriz estadística está siempre vacía (#4916)" -#: src/api/reference.md:3564 +#: src/api/reference.md:3584 msgid "`stopCoverage` — module" msgstr "`stopCoverage` — módulo" -#: src/api/reference.md:3565 +#: src/api/reference.md:3585 msgid "`takeCoverage` — module" msgstr "`takeCoverage` — módulo" -#: src/api/reference.md:3566 +#: src/api/reference.md:3586 msgid "`writeDouble` — instance _(class: `Serializer`)_" msgstr "`writeDouble` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3567 +#: src/api/reference.md:3587 msgid "`writeHeader` — instance _(class: `Serializer`)_" msgstr "`writeHeader` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3568 +#: src/api/reference.md:3588 msgid "`writeHeapSnapshot` — module" msgstr "`writeHeapSnapshot` — módulo" -#: src/api/reference.md:3569 +#: src/api/reference.md:3589 msgid "`writeRawBytes` — instance _(class: `Serializer`)_" msgstr "`writeRawBytes` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3570 +#: src/api/reference.md:3590 msgid "`writeUint32` — instance _(class: `Serializer`)_" msgstr "`writeUint32` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3571 +#: src/api/reference.md:3591 msgid "`writeUint64` — instance _(class: `Serializer`)_" msgstr "`writeUint64` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3572 +#: src/api/reference.md:3592 msgid "`writeValue` — instance _(class: `Serializer`)_" msgstr "`writeValue` — instancia _(clase: `Serializer`)_" -#: src/api/reference.md:3576 +#: src/api/reference.md:3596 msgid "`promiseHooks`" msgstr "`promiseHooks`" -#: src/api/reference.md:3577 +#: src/api/reference.md:3597 msgid "`startupSnapshot`" msgstr "`startupSnapshot`" -#: src/api/reference.md:3579 +#: src/api/reference.md:3599 msgid "`vm`" msgstr "`vm`" -#: src/api/reference.md:3583 +#: src/api/reference.md:3603 msgid "`Script`" msgstr "`Script`" -#: src/api/reference.md:3587 +#: src/api/reference.md:3607 msgid "`compileFunction` — module" msgstr "`compileFunction` — módulo" -#: src/api/reference.md:3588 +#: src/api/reference.md:3608 msgid "`createCachedData` — instance" msgstr "`createCachedData` — instancia" -#: src/api/reference.md:3589 +#: src/api/reference.md:3609 msgid "`createContext` — module" msgstr "`createContext` — módulo" -#: src/api/reference.md:3590 +#: src/api/reference.md:3610 msgid "`createScript` — module" msgstr "`createScript` — módulo" -#: src/api/reference.md:3591 +#: src/api/reference.md:3611 msgid "`dependencySpecifiers` — instance" msgstr "`dependencySpecifiers` — instancia" -#: src/api/reference.md:3592 +#: src/api/reference.md:3612 msgid "`error` — instance" msgstr "`error` — instancia" -#: src/api/reference.md:3593 +#: src/api/reference.md:3613 msgid "`evaluate` — instance" msgstr "`evaluate` — instancia" -#: src/api/reference.md:3594 +#: src/api/reference.md:3614 msgid "`hasAsyncGraph` — instance" msgstr "`hasAsyncGraph` — instancia" -#: src/api/reference.md:3595 +#: src/api/reference.md:3615 msgid "`hasTopLevelAwait` — instance" msgstr "`hasTopLevelAwait` — instancia" -#: src/api/reference.md:3596 +#: src/api/reference.md:3616 msgid "`identifier` — instance" msgstr "`identifier` — instancia" -#: src/api/reference.md:3597 +#: src/api/reference.md:3617 msgid "`instantiate` — instance" msgstr "`instantiate` — instancia" -#: src/api/reference.md:3598 +#: src/api/reference.md:3618 msgid "`isContext` — module" msgstr "`isContext` — módulo" -#: src/api/reference.md:3599 +#: src/api/reference.md:3619 msgid "`link` — instance" msgstr "`link` — instancia" -#: src/api/reference.md:3600 +#: src/api/reference.md:3620 msgid "`linkRequests` — instance" msgstr "`linkRequests` — instancia" -#: src/api/reference.md:3601 +#: src/api/reference.md:3621 msgid "`measureMemory` — module" msgstr "`measureMemory` — módulo" -#: src/api/reference.md:3602 +#: src/api/reference.md:3622 msgid "`moduleRequests` — instance" msgstr "`moduleRequests` — instancia" -#: src/api/reference.md:3603 +#: src/api/reference.md:3623 msgid "`namespace` — instance" msgstr "`namespace` — instancia" -#: src/api/reference.md:3604 +#: src/api/reference.md:3624 msgid "`runInContext` — module" msgstr "`runInContext` — módulo" -#: src/api/reference.md:3605 +#: src/api/reference.md:3625 msgid "`runInNewContext` — module" msgstr "`runInNewContext` — módulo" -#: src/api/reference.md:3606 +#: src/api/reference.md:3626 msgid "`runInThisContext` — module" msgstr "`runInThisContext` — módulo" -#: src/api/reference.md:3607 +#: src/api/reference.md:3627 msgid "`setExport` — instance" msgstr "`setExport` — instancia" -#: src/api/reference.md:3608 +#: src/api/reference.md:3628 msgid "`status` — instance" msgstr "`status` — instancia" -#: src/api/reference.md:3615 +#: src/api/reference.md:3635 msgid "`wasi`" msgstr "`wasi`" -#: src/api/reference.md:3619 +#: src/api/reference.md:3639 msgid "`WASI`" msgstr "`WASI`" -#: src/api/reference.md:3623 +#: src/api/reference.md:3643 msgid "`WASI` — module" msgstr "`WASI` — módulo" -#: src/api/reference.md:3624 +#: src/api/reference.md:3644 msgid "`finalizeBindings` — instance _(class: `WASI`)_" msgstr "`finalizeBindings` — instancia _(clase: `WASI`)_" -#: src/api/reference.md:3625 +#: src/api/reference.md:3645 msgid "`getImportObject` — instance _(class: `WASI`)_" msgstr "`getImportObject` — instancia _(clase: `WASI`)_" -#: src/api/reference.md:3626 +#: src/api/reference.md:3646 msgid "`initialize` — instance _(class: `WASI`)_" msgstr "`initialize` — instancia _(clase: `WASI`)_" -#: src/api/reference.md:3627 +#: src/api/reference.md:3647 msgid "`start` — instance _(class: `WASI`)_" msgstr "`start` — instancia _(clase: `WASI`)_" -#: src/api/reference.md:3631 +#: src/api/reference.md:3651 msgid "`wasiImport`" msgstr "`wasiImport`" -#: src/api/reference.md:3637 +#: src/api/reference.md:3653 +msgid "`worker_threads`" +msgstr "`worker_threads`" + +#: src/api/reference.md:3657 msgid "`BroadcastChannel`" msgstr "`BroadcastChannel`" -#: src/api/reference.md:3638 +#: src/api/reference.md:3658 msgid "`MessageChannel`" msgstr "`MessageChannel`" -#: src/api/reference.md:3639 +#: src/api/reference.md:3659 msgid "`MessagePort`" msgstr "`MessagePort`" -#: src/api/reference.md:3644 +#: src/api/reference.md:3664 msgid "`BroadcastChannel` — module" msgstr "`BroadcastChannel` — módulo" -#: src/api/reference.md:3645 +#: src/api/reference.md:3665 msgid "`MessageChannel` — module" msgstr "`MessageChannel` — módulo" -#: src/api/reference.md:3646 +#: src/api/reference.md:3666 msgid "`addEventListener` — instance" msgstr "`addEventListener` — instancia" -#: src/api/reference.md:3647 +#: src/api/reference.md:3667 msgid "`cpuUsage` — instance _(class: `Worker`)_" msgstr "`cpuUsage` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3648 +#: src/api/reference.md:3668 msgid "`getEnvironmentData` — module" msgstr "`getEnvironmentData` — módulo" -#: src/api/reference.md:3649 +#: src/api/reference.md:3669 msgid "`getHeapSnapshot` — instance _(class: `Worker`)_" msgstr "`getHeapSnapshot` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3650 +#: src/api/reference.md:3670 msgid "`getHeapStatistics` — instance _(class: `Worker`)_" msgstr "`getHeapStatistics` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3651 +#: src/api/reference.md:3671 msgid "`isMarkedAsUntransferable` — module" msgstr "`isMarkedAsUntransferable` — módulo" -#: src/api/reference.md:3652 +#: src/api/reference.md:3672 msgid "`markAsUncloneable` — module" msgstr "`markAsUncloneable` — módulo" -#: src/api/reference.md:3653 +#: src/api/reference.md:3673 msgid "`markAsUntransferable` — module" msgstr "`markAsUntransferable` — módulo" -#: src/api/reference.md:3654 +#: src/api/reference.md:3674 msgid "`moveMessagePortToContext` — module" msgstr "`moveMessagePortToContext` — módulo" -#: src/api/reference.md:3655 +#: src/api/reference.md:3675 msgid "`off` — instance _(class: `Worker`)_" msgstr "`off` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3656 +#: src/api/reference.md:3676 msgid "`on` — instance _(class: `Worker`)_" msgstr "`on` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3657 +#: src/api/reference.md:3677 msgid "`once` — instance _(class: `Worker`)_" msgstr "`once` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3658 +#: src/api/reference.md:3678 msgid "`postMessageToThread` — module" msgstr "`postMessageToThread` — módulo" -#: src/api/reference.md:3659 +#: src/api/reference.md:3679 msgid "`receiveMessageOnPort` — module" msgstr "`receiveMessageOnPort` — módulo" -#: src/api/reference.md:3660 +#: src/api/reference.md:3680 msgid "`ref` — instance _(class: `Worker`)_" msgstr "`ref` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3661 +#: src/api/reference.md:3681 msgid "`reload` — instance _(class: `Worker`)_" msgstr "`reload` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3662 +#: src/api/reference.md:3682 msgid "`removeEventListener` — instance" msgstr "`removeEventListener` — instancia" -#: src/api/reference.md:3663 +#: src/api/reference.md:3683 msgid "`setEnvironmentData` — module" msgstr "`setEnvironmentData` — módulo" -#: src/api/reference.md:3664 +#: src/api/reference.md:3684 msgid "`startCpuProfile` — instance _(class: `Worker`)_" msgstr "`startCpuProfile` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3665 +#: src/api/reference.md:3685 msgid "`startHeapProfile` — instance _(class: `Worker`)_" msgstr "`startHeapProfile` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3666 +#: src/api/reference.md:3686 msgid "`terminate` — instance _(class: `Worker`)_" msgstr "`terminate` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3667 +#: src/api/reference.md:3687 msgid "`unref` — instance _(class: `Worker`)_" msgstr "`unref` — instancia _(clase: `Worker`)_" -#: src/api/reference.md:3671 +#: src/api/reference.md:3691 msgid "`SHARE_ENV`" msgstr "`SHARE_ENV`" -#: src/api/reference.md:3672 +#: src/api/reference.md:3692 msgid "`isInternalThread`" msgstr "`isInternalThread`" -#: src/api/reference.md:3673 +#: src/api/reference.md:3693 msgid "`isMainThread`" msgstr "`isMainThread`" -#: src/api/reference.md:3674 +#: src/api/reference.md:3694 msgid "`locks`" msgstr "`locks`" -#: src/api/reference.md:3675 +#: src/api/reference.md:3695 msgid "`parentPort`" msgstr "`parentPort`" -#: src/api/reference.md:3676 +#: src/api/reference.md:3696 msgid "`resourceLimits`" msgstr "`resourceLimits`" -#: src/api/reference.md:3677 +#: src/api/reference.md:3697 msgid "`threadId`" msgstr "`threadId`" -#: src/api/reference.md:3678 +#: src/api/reference.md:3698 msgid "`threadName`" msgstr "`threadName`" -#: src/api/reference.md:3679 +#: src/api/reference.md:3699 msgid "`workerData`" msgstr "`workerData`" -#: src/api/reference.md:3685 +#: src/api/reference.md:3705 msgid "`Client`" msgstr "`Client`" -#: src/api/reference.md:3687 +#: src/api/reference.md:3707 msgid "`WebSocketServer`" msgstr "`WebSocketServer`" -#: src/api/reference.md:3692 +#: src/api/reference.md:3712 msgid "`WebSocket` — module" msgstr "`WebSocket` — módulo" -#: src/api/reference.md:3693 +#: src/api/reference.md:3713 msgid "`addListener` — instance _(class: `Client`)_" msgstr "`addListener` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3694 +#: src/api/reference.md:3714 #, fuzzy msgid "`address` — instance" msgstr "`add` — instancia" -#: src/api/reference.md:3695 +#: src/api/reference.md:3715 #, fuzzy msgid "`clients` — instance" msgstr "`limits` — instancia" -#: src/api/reference.md:3697 +#: src/api/reference.md:3717 msgid "`close` — instance _(class: `Client`)_" msgstr "`close` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3698 +#: src/api/reference.md:3718 msgid "`closeClient` — module" msgstr "`closeClient` — módulo" -#: src/api/reference.md:3700 +#: src/api/reference.md:3720 msgid "`handleUpgrade` — instance" msgstr "`handleUpgrade` — instancia" -#: src/api/reference.md:3702 +#: src/api/reference.md:3722 msgid "`on` — instance _(class: `Client`)_" msgstr "`on` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3703 +#: src/api/reference.md:3723 #, fuzzy msgid "`ping` — instance" msgstr "`png` — instancia" -#: src/api/reference.md:3704 +#: src/api/reference.md:3724 #, fuzzy msgid "`ping` — instance _(class: `Client`)_" msgstr "`on` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3705 +#: src/api/reference.md:3725 #, fuzzy msgid "`pong` — instance" msgstr "`png` — instancia" -#: src/api/reference.md:3706 +#: src/api/reference.md:3726 #, fuzzy msgid "`pong` — instance _(class: `Client`)_" msgstr "`on` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3707 +#: src/api/reference.md:3727 msgid "`readyState` — instance" msgstr "`readyState` — instancia" -#: src/api/reference.md:3708 +#: src/api/reference.md:3728 msgid "`send` — instance" msgstr "`send` — instancia" -#: src/api/reference.md:3709 +#: src/api/reference.md:3729 msgid "`send` — instance _(class: `Client`)_" msgstr "`send` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3710 +#: src/api/reference.md:3730 msgid "`sendToClient` — module" msgstr "`sendToClient` — módulo" -#: src/api/reference.md:3711 +#: src/api/reference.md:3731 #, fuzzy msgid "`terminate` — instance" msgstr "`terminal` — instancia" -#: src/api/reference.md:3712 +#: src/api/reference.md:3732 #, fuzzy msgid "`terminate` — instance _(class: `Client`)_" msgstr "`send` — instancia _(clase: `Client`)_" -#: src/api/reference.md:3716 +#: src/api/reference.md:3736 msgid "`CLOSED`" msgstr "`CLOSED`" -#: src/api/reference.md:3717 +#: src/api/reference.md:3737 msgid "`CLOSING`" msgstr "`CLOSING`" -#: src/api/reference.md:3718 +#: src/api/reference.md:3738 msgid "`CONNECTING`" msgstr "`CONNECTING`" -#: src/api/reference.md:3719 +#: src/api/reference.md:3739 msgid "`OPEN`" msgstr "`OPEN`" -#: src/api/reference.md:3725 src/api/reference.md:3726 +#: src/api/reference.md:3745 src/api/reference.md:3746 msgid "`BrotliCompress`" msgstr "`BrotliCompress`" -#: src/api/reference.md:3727 src/api/reference.md:3728 +#: src/api/reference.md:3747 src/api/reference.md:3748 msgid "`BrotliDecompress`" msgstr "`BrotliDecompress`" -#: src/api/reference.md:3729 src/api/reference.md:3730 +#: src/api/reference.md:3749 src/api/reference.md:3750 msgid "`Deflate`" msgstr "`Deflate`" -#: src/api/reference.md:3731 src/api/reference.md:3732 +#: src/api/reference.md:3751 src/api/reference.md:3752 msgid "`DeflateRaw`" msgstr "`DeflateRaw`" -#: src/api/reference.md:3733 src/api/reference.md:3734 +#: src/api/reference.md:3753 src/api/reference.md:3754 msgid "`Gunzip`" msgstr "`Gunzip`" -#: src/api/reference.md:3735 src/api/reference.md:3736 +#: src/api/reference.md:3755 src/api/reference.md:3756 msgid "`Gzip`" msgstr "`Gzip`" -#: src/api/reference.md:3737 src/api/reference.md:3738 +#: src/api/reference.md:3757 src/api/reference.md:3758 msgid "`Inflate`" msgstr "`Inflate`" -#: src/api/reference.md:3739 src/api/reference.md:3740 +#: src/api/reference.md:3759 src/api/reference.md:3760 msgid "`InflateRaw`" msgstr "`InflateRaw`" -#: src/api/reference.md:3741 src/api/reference.md:3742 +#: src/api/reference.md:3761 src/api/reference.md:3762 msgid "`Unzip`" msgstr "`Unzip`" -#: src/api/reference.md:3743 +#: src/api/reference.md:3763 msgid "`ZstdCompress`" msgstr "`ZstdCompress`" -#: src/api/reference.md:3744 +#: src/api/reference.md:3764 msgid "`ZstdDecompress`" msgstr "`ZstdDecompress`" -#: src/api/reference.md:3748 +#: src/api/reference.md:3768 msgid "`brotliCompress` — module" msgstr "`brotliCompress` — módulo" -#: src/api/reference.md:3749 +#: src/api/reference.md:3769 msgid "`brotliCompressSync` — module" msgstr "`brotliCompressSync` — módulo" -#: src/api/reference.md:3750 +#: src/api/reference.md:3770 msgid "`brotliDecompress` — module" msgstr "`brotliDecompress` — módulo" -#: src/api/reference.md:3751 +#: src/api/reference.md:3771 msgid "`brotliDecompressSync` — module" msgstr "`brotliDecompressSync` — módulo" -#: src/api/reference.md:3752 +#: src/api/reference.md:3772 msgid "`crc32` — module" msgstr "`crc32` — módulo" -#: src/api/reference.md:3753 +#: src/api/reference.md:3773 msgid "" "`createBrotliCompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46394,7 +46157,7 @@ msgstr "" "`createBrotliCompress` — módulo ­ **talón** — params/quality opciones " "aceptadas pero ignoradas, advierte una vez (#4917)" -#: src/api/reference.md:3754 +#: src/api/reference.md:3774 msgid "" "`createBrotliDecompress` — module ⚠ **stub** — params/quality options " "accepted but ignored, warns once (#4917)" @@ -46402,7 +46165,7 @@ msgstr "" "`createBrotliDecompress` — módulo ­ **talón** — params/quality opciones " "aceptadas pero ignoradas, advierte una vez (#4917)" -#: src/api/reference.md:3755 +#: src/api/reference.md:3775 msgid "" "`createDeflate` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46410,7 +46173,7 @@ msgstr "" "`createDeflate` — módulo ­ **talón** — nivel cumplido; strategy/memLevel " "validado pero no aplicado (#4917)" -#: src/api/reference.md:3756 +#: src/api/reference.md:3776 msgid "" "`createDeflateRaw` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46418,11 +46181,11 @@ msgstr "" "`createDeflateRaw` — módulo ­ **talón** — nivel cumplido; strategy/memLevel " "validado pero no aplicado (#4917)" -#: src/api/reference.md:3757 +#: src/api/reference.md:3777 msgid "`createGunzip` — module" msgstr "`createGunzip` — módulo" -#: src/api/reference.md:3758 +#: src/api/reference.md:3778 msgid "" "`createGzip` — module ⚠ **stub** — level honored; strategy/memLevel " "validated but not applied (#4917)" @@ -46430,19 +46193,19 @@ msgstr "" "`createGzip` — módulo ­ **talón** — nivel cumplido; strategy/memLevel " "validado pero no aplicado (#4917)" -#: src/api/reference.md:3759 +#: src/api/reference.md:3779 msgid "`createInflate` — module" msgstr "`createInflate` — módulo" -#: src/api/reference.md:3760 +#: src/api/reference.md:3780 msgid "`createInflateRaw` — module" msgstr "`createInflateRaw` — módulo" -#: src/api/reference.md:3761 +#: src/api/reference.md:3781 msgid "`createUnzip` — module" msgstr "`createUnzip` — módulo" -#: src/api/reference.md:3762 +#: src/api/reference.md:3782 msgid "" "`createZstdCompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46450,7 +46213,7 @@ msgstr "" "`createZstdCompress` — módulo ­ **talón** — params/quality opciones aceptadas " "pero ignoradas, advierte una vez (#4917)" -#: src/api/reference.md:3763 +#: src/api/reference.md:3783 msgid "" "`createZstdDecompress` — module ⚠ **stub** — params/quality options accepted " "but ignored, warns once (#4917)" @@ -46458,71 +46221,71 @@ msgstr "" "`createZstdDecompress` — módulo ­ **talón** — params/quality opciones " "aceptadas pero ignoradas, advierte una vez (#4917)" -#: src/api/reference.md:3764 +#: src/api/reference.md:3784 msgid "`deflate` — module" msgstr "`deflate` — módulo" -#: src/api/reference.md:3765 +#: src/api/reference.md:3785 msgid "`deflateRaw` — module" msgstr "`deflateRaw` — módulo" -#: src/api/reference.md:3766 +#: src/api/reference.md:3786 msgid "`deflateRawSync` — module" msgstr "`deflateRawSync` — módulo" -#: src/api/reference.md:3767 +#: src/api/reference.md:3787 msgid "`deflateSync` — module" msgstr "`deflateSync` — módulo" -#: src/api/reference.md:3768 +#: src/api/reference.md:3788 msgid "`gunzip` — module" msgstr "`gunzip` — módulo" -#: src/api/reference.md:3769 +#: src/api/reference.md:3789 msgid "`gunzipSync` — module" msgstr "`gunzipSync` — módulo" -#: src/api/reference.md:3770 +#: src/api/reference.md:3790 msgid "`gzip` — module" msgstr "`gzip` — módulo" -#: src/api/reference.md:3771 +#: src/api/reference.md:3791 msgid "`gzipSync` — module" msgstr "`gzipSync` — módulo" -#: src/api/reference.md:3772 +#: src/api/reference.md:3792 msgid "`inflate` — module" msgstr "`inflate` — módulo" -#: src/api/reference.md:3773 +#: src/api/reference.md:3793 msgid "`inflateRaw` — module" msgstr "`inflateRaw` — módulo" -#: src/api/reference.md:3774 +#: src/api/reference.md:3794 msgid "`inflateRawSync` — module" msgstr "`inflateRawSync` — módulo" -#: src/api/reference.md:3775 +#: src/api/reference.md:3795 msgid "`inflateSync` — module" msgstr "`inflateSync` — módulo" -#: src/api/reference.md:3776 +#: src/api/reference.md:3796 msgid "`unzip` — module" msgstr "`unzip` — módulo" -#: src/api/reference.md:3777 +#: src/api/reference.md:3797 msgid "`unzipSync` — module" msgstr "`unzipSync` — módulo" -#: src/api/reference.md:3778 +#: src/api/reference.md:3798 msgid "`zstdCompress` — module" msgstr "`zstdCompress` — módulo" -#: src/api/reference.md:3779 +#: src/api/reference.md:3799 msgid "`zstdCompressSync` — module" msgstr "`zstdCompressSync` — módulo" -#: src/api/reference.md:3785 +#: src/api/reference.md:3805 msgid "`codes`" msgstr "`codes`" @@ -46737,7 +46500,7 @@ msgid "(default — none)" msgstr "(predeterminado — ninguno)" #: src/container/overview.md:72 src/container/determinism.md:60 -#: src/internals/memory-model.md:168 src/internals/memory-model.md:185 +#: src/internals/memory-model.md:153 src/internals/memory-model.md:170 msgid "Env var" msgstr "Variable de entorno" @@ -47278,8 +47041,8 @@ msgstr "`volumes`" #: src/container/containers.md:54 msgid "" -"Volume mounts in `\"host:container[:ro]\"` form, e.g. `[\"./data:/data:" -"ro\"]`." +"Volume mounts in `\"host:container[:ro]\"` form, e.g. `[\"./data:/data:ro" +"\"]`." msgstr "" "Montajes de volúmenes en formato `\"host:container[:ro]\"`, p. ej. `[\"./" "data:/data:ro\"]`." @@ -47410,8 +47173,8 @@ msgid "" msgstr "" "Para una carga de trabajo no confiable (p. ej. ejecutar código proporcionado " "por el usuario o un script de compilación de una fuente no confiable), el " -"punto de partida recomendado es \"eliminar todo y agregar solo lo " -"necesario\":" +"punto de partida recomendado es \"eliminar todo y agregar solo lo necesario" +"\":" #: src/container/containers.md:81 src/container/security.md:46 msgid "" @@ -47735,13 +47498,13 @@ msgstr "Introspección del backend" #: src/container/containers.md:212 msgid "" -"`getBackend()` is synchronous and returns the canonical backend name " -"(`\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). It will perform a " +"`getBackend()` is synchronous and returns the canonical backend name (`" +"\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). It will perform a " "synchronous in-place probe on first call so the result is always the live " "name; calls after the first hit a cached `OnceLock` and return instantly." msgstr "" -"`getBackend()` es síncrono y devuelve el nombre canónico del backend " -"(`\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). Realiza una sonda " +"`getBackend()` es síncrono y devuelve el nombre canónico del backend (`" +"\"docker\"`, `\"podman\"`, `\"apple/container\"`, etc.). Realiza una sonda " "síncrona en el lugar en la primera llamada para que el resultado sea siempre " "el nombre activo; las llamadas posteriores usan un `OnceLock` en caché y " "retornan instantáneamente." @@ -47855,8 +47618,8 @@ msgid "" " image: \"myorg/api:1.0\",\n" " depends_on: { db: { condition: \"service_healthy\" } },\n" " environment: {\n" -" DATABASE_URL: \"postgres://app:changeme@app-db:5432/" -"app\",\n" +" DATABASE_URL: \"postgres://app:changeme@app-db:5432/app" +"\",\n" " },\n" " ports: [\"8080:8080\"],\n" " networks: [\"app-db-net\", \"app-web-net\"],\n" @@ -47909,8 +47672,8 @@ msgstr "" " image: \"myorg/api:1.0\",\n" " depends_on: { db: { condition: \"service_healthy\" } },\n" " environment: {\n" -" DATABASE_URL: \"postgres://app:changeme@app-db:5432/" -"app\",\n" +" DATABASE_URL: \"postgres://app:changeme@app-db:5432/app" +"\",\n" " },\n" " ports: [\"8080:8080\"],\n" " networks: [\"app-db-net\", \"app-web-net\"],\n" @@ -49043,11 +48806,11 @@ msgstr "**Deshabilitar masquerade / NAT** (en el host)" #: src/container/networking.md:178 msgid "" -"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": " -"\"false\" }`" +"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": \"false" +"\" }`" msgstr "" -"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": " -"\"false\" }`" +"`driver_opts: { \"com.docker.network.bridge.enable_ip_masquerade\": \"false" +"\" }`" #: src/container/networking.md:179 msgid "**Custom MTU** (matches host network)" @@ -49647,8 +49410,8 @@ msgid "" "Linux capabilities to add. Specific (e.g. `[\"NET_BIND_SERVICE\"]`), not " "blanket." msgstr "" -"Capacidades de Linux a añadir. Específicas (por ejemplo, " -"`[\"NET_BIND_SERVICE\"]`), no genéricas." +"Capacidades de Linux a añadir. Específicas (por ejemplo, `[\"NET_BIND_SERVICE" +"\"]`), no genéricas." #: src/container/security.md:21 msgid "" @@ -51456,8 +51219,8 @@ msgstr "// Ambos producen ContainerInfo con la misma semántica de campo:" #: src/container/determinism.md:301 msgid "" -"User code reading `info.status` sees `\"running\"` from any backend — not " -"`\"Up 5 seconds\"` from docker vs `\"running\"` from apple." +"User code reading `info.status` sees `\"running\"` from any backend — not `" +"\"Up 5 seconds\"` from docker vs `\"running\"` from apple." msgstr "" "El código de usuario que lee `info.status` ve `\"running\"` desde cualquier " "motor, no `\"Up 5 seconds\"` desde Docker frente a `\"running\"` desde Apple." @@ -52862,9 +52625,9 @@ msgid "" "code determines the **symbol** (`USD` → `$`, `EUR` → `€`, `GBP` → `£`, …; " "codes without a well-known symbol render as the code itself), while the " "locale keeps controlling the **layout** — separators and symbol placement. " -"With `en = \"GBP\"`, English output becomes `£1,234.50`; with `de = " -"\"USD\"`, German output becomes `1.234,50 $`. Locales without an entry " -"(exact locale first, then base language) use their built-in default symbol." +"With `en = \"GBP\"`, English output becomes `£1,234.50`; with `de = \"USD" +"\"`, German output becomes `1.234,50 $`. Locales without an entry (exact " +"locale first, then base language) use their built-in default symbol." msgstr "" "Cuando se llama `Currency(value)`, el código ISO 4217 configurado de la " "localización activa determina la **símbolo** (`USD` → `$`, `EUR` → `€`, " @@ -52939,7 +52702,7 @@ msgstr "" msgid "Example Output" msgstr "Ejemplo de Salida" -#: src/i18n/cli.md:32 src/testing/ci-tiers.md:267 +#: src/i18n/cli.md:32 src/testing/ci-tiers.md:281 msgid "Workflow" msgstr "Flujo de Trabajo" @@ -53155,13 +52918,13 @@ msgstr "" #: src/updater/overview.md:28 msgid "" "```typescript\n" -"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/" -"updater\"\n" +"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/updater" +"\"\n" "```" msgstr "" "```typescript\n" -"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/" -"updater\"\n" +"// import { checkForUpdate, initUpdater, markHealthy } from \"@perry/updater" +"\"\n" "```" #: src/updater/overview.md:32 @@ -53178,8 +52941,8 @@ msgstr "" #: src/updater/overview.md:36 msgid "" "```typescript\n" -"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for " -"updates\"\n" +"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for updates" +"\"\n" "// menu item or a periodic timer. The manifest URL must serve over HTTPS.\n" "//\n" "// const update = await checkForUpdate({\n" @@ -53203,8 +52966,8 @@ msgid "" "```" msgstr "" "```typescript\n" -"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for " -"updates\"\n" +"// Pseudocode using @perry/updater's wrapper. Drop into a \"Check for updates" +"\"\n" "// menu item or a periodic timer. The manifest URL must serve over HTTPS.\n" "//\n" "// const update = await checkForUpdate({\n" @@ -53303,8 +53066,8 @@ msgid "" "// \"notes\": \"Bug fixes and performance improvements\",\n" "// \"platforms\": {\n" "// \"darwin-aarch64\": {\n" -"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64." -"bin\",\n" +"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64.bin" +"\",\n" "// \"sha256\": \"0123456789abcdef...\",\n" "// \"signature\": \"base64sig==\",\n" "// \"size\": 12345678\n" @@ -53327,8 +53090,8 @@ msgstr "" "// \"notes\": \"Bug fixes and performance improvements\",\n" "// \"platforms\": {\n" "// \"darwin-aarch64\": {\n" -"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64." -"bin\",\n" +"// \"url\": \"https://example.com/app-1.4.0-darwin-aarch64.bin" +"\",\n" "// \"sha256\": \"0123456789abcdef...\",\n" "// \"signature\": \"base64sig==\",\n" "// \"size\": 12345678\n" @@ -53443,7 +53206,7 @@ msgstr "`darwin-x86_64`" msgid "Windows 10/11 64-bit" msgstr "Windows 10/11 64 bits" -#: src/updater/overview.md:123 src/cli/flags.md:122 +#: src/updater/overview.md:123 src/cli/flags.md:144 msgid "`windows-x86_64`" msgstr "`windows-x86_64`" @@ -53451,7 +53214,7 @@ msgstr "`windows-x86_64`" msgid "Windows 11 ARM64" msgstr "Windows 11 ARM64" -#: src/updater/overview.md:124 src/cli/flags.md:123 +#: src/updater/overview.md:124 src/cli/flags.md:145 msgid "`windows-aarch64`" msgstr "`windows-aarch64`" @@ -53459,7 +53222,7 @@ msgstr "`windows-aarch64`" msgid "Linux 64-bit" msgstr "Linux 64 bits" -#: src/updater/overview.md:125 src/cli/flags.md:126 +#: src/updater/overview.md:125 src/cli/flags.md:148 msgid "`linux-x86_64`" msgstr "`linux-x86_64`" @@ -53678,10 +53441,9 @@ msgid "" msgstr "" "Componga un manifiesto final con la salida `sign` a través de `jq` para cada " "activo, luego fusionándose en el diseño por plataforma mostrado " -"anteriormente. Consejo de CI: pase `--secret-key-b64 " -"\"$ED25519_SECRET_KEY\"` en lugar de `--secret-key file` para que el " -"secreto pueda venir de un secreto de repositorio sin golpear el sistema de " -"archivos del trabajador." +"anteriormente. Consejo de CI: pase `--secret-key-b64 \"$ED25519_SECRET_KEY" +"\"` en lugar de `--secret-key file` para que el secreto pueda venir de un " +"secreto de repositorio sin golpear el sistema de archivos del trabajador." #: src/updater/overview.md:237 msgid "Install + rollback flow" @@ -54289,11 +54051,11 @@ msgstr "`getDeviceIdiom()`" #: src/system/overview.md:25 msgid "" -"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, " -"`\"desktop\"`" +"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, `" +"\"desktop\"`" msgstr "" -"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, " -"`\"desktop\"`" +"`\"phone\"`, `\"pad\"`, `\"mac\"`, `\"tv\"`, `\"watch\"`, `\"vision\"`, `" +"\"desktop\"`" #: src/system/overview.md:26 msgid "`getDeviceModel()`" @@ -55711,7 +55473,7 @@ msgstr "" "ts) para la documentación completa de parámetros. Resumen:" #: src/system/audio_module.md:152 src/plugins/native-extensions.md:202 -#: src/cli/commands.md:8 src/contributing/architecture.md:24 +#: src/cli/commands.md:8 src/contributing/architecture.md:25 msgid "Purpose" msgstr "Propósito" @@ -56197,8 +55959,8 @@ msgstr "" msgid "" "Stub platforms link cleanly against the same FFI surface — code that imports " "`perry/media` compiles on every target. `createPlayer` returns `0` on a stub " -"backend so `if (player === 0)` is the canonical \"feature not available " -"here\" check." +"backend so `if (player === 0)` is the canonical \"feature not available here" +"\" check." msgstr "" "Las plataformas stub enlazan limpiamente contra la misma superficie FFI — el " "código que importa `perry/media` compila en todos los targets. " @@ -56516,8 +56278,8 @@ msgid "" " return `${serverUrl}/rest/stream?${params.toString()}`;\n" "}\n" "\n" -"const player = createPlayer(streamUrl(\"https://music.example.com\", " -"\"alice\", \"secret\", \"12345\"));\n" +"const player = createPlayer(streamUrl(\"https://music.example.com\", \"alice" +"\", \"secret\", \"12345\"));\n" "setNowPlaying(player, \"All These Things That I've Done\", \"The Killers\", " "\"Hot Fuss\",\n" " \"https://music.example.com/rest/getCoverArt?" @@ -56543,8 +56305,8 @@ msgstr "" " return `${serverUrl}/rest/stream?${params.toString()}`;\n" "}\n" "\n" -"const player = createPlayer(streamUrl(\"https://music.example.com\", " -"\"alice\", \"secret\", \"12345\"));\n" +"const player = createPlayer(streamUrl(\"https://music.example.com\", \"alice" +"\", \"secret\", \"12345\"));\n" "setNowPlaying(player, \"All These Things That I've Done\", \"The Killers\", " "\"Hot Fuss\",\n" " \"https://music.example.com/rest/getCoverArt?" @@ -56651,9 +56413,9 @@ msgid "" "`\"no-provider-available\"`, `\"unsupported-platform\"`." msgstr "" "`onError(message)` — se dispara al denegar permisos, por tiempo de espera " -"agotado o por indisponibilidad de la plataforma. Mensajes comunes: " -"`\"permission-denied\"`, `\"no-location\"`, `\"no-provider-available\"`, " -"`\"unsupported-platform\"`." +"agotado o por indisponibilidad de la plataforma. Mensajes comunes: `" +"\"permission-denied\"`, `\"no-location\"`, `\"no-provider-available\"`, `" +"\"unsupported-platform\"`." #: src/system/geolocation.md:42 msgid "`geolocationWatch(callback): number`" @@ -56687,8 +56449,8 @@ msgstr "`geolocationRequestPermission(callback)`" #: src/system/geolocation.md:54 msgid "" "Request location permission. Calls `callback(status)` where status is one of " -"`\"granted\"`, `\"denied\"`, `\"restricted\"`, or `\"unsupported-" -"platform\"`. Safe to call repeatedly — already-granted permissions return " +"`\"granted\"`, `\"denied\"`, `\"restricted\"`, or `\"unsupported-platform" +"\"`. Safe to call repeatedly — already-granted permissions return " "immediately." msgstr "" "Solicita permiso de ubicación. Llama a `callback(status)` donde el estado es " @@ -56737,8 +56499,8 @@ msgstr "**tvOS / watchOS / visionOS / GTK4 / Windows / Web**" #: src/system/geolocation.md:66 msgid "" -"No-op stub — `geolocationGetCurrent` invokes `onError` immediately with " -"`\"unsupported-platform\"`." +"No-op stub — `geolocationGetCurrent` invokes `onError` immediately with `" +"\"unsupported-platform\"`." msgstr "" "Stub sin efecto — `geolocationGetCurrent` invoca `onError` inmediatamente " "con `\"unsupported-platform\"`." @@ -57464,12 +57226,12 @@ msgid "" "identifier (`\"iPhone15,2\"`, `\"MacBookPro18,3\"`, etc. — on macOS this is " "sysctl `hw.model`)." msgstr "" -"`getDeviceIdiom()` devuelve el factor de forma amplia como cadena: " -"`\"phone\"` o `\"pad\"` en iOS (y `\"phone\"` en Android), `\"mac\"` en " -"macOS, `\"tv\"` en tvOS, `\"watch\"` en watchOS, `\"vision\"` en visionOS, y " -"`\"desktop\"` en Windows y Linux. `getDeviceModel()` devuelve el " -"identificador de modelo específico de la plataforma (`\"iPhone15,2\"`, " -"`\"MacBookPro18,3\"`, etc. — en macOS esto es sysctl `hw.model`)." +"`getDeviceIdiom()` devuelve el factor de forma amplia como cadena: `\"phone" +"\"` o `\"pad\"` en iOS (y `\"phone\"` en Android), `\"mac\"` en macOS, `\"tv" +"\"` en tvOS, `\"watch\"` en watchOS, `\"vision\"` en visionOS, y `\"desktop" +"\"` en Windows y Linux. `getDeviceModel()` devuelve el identificador de " +"modelo específico de la plataforma (`\"iPhone15,2\"`, `\"MacBookPro18,3\"`, " +"etc. — en macOS esto es sysctl `hw.model`)." #: src/system/other.md:74 msgid "[UI Overview](../ui/overview.md) — Building UIs" @@ -57939,8 +57701,8 @@ msgid "" "Widget({\n" " kind: \"TemplateLiteralWidget\",\n" " displayName: \"Template Literal\",\n" -" description: \"Template literals compile to Swift string " -"interpolation\",\n" +" description: \"Template literals compile to Swift string interpolation" +"\",\n" " entryFields: {\n" " name: \"string\",\n" " score: \"number\",\n" @@ -57956,8 +57718,8 @@ msgstr "" "Widget({\n" " kind: \"TemplateLiteralWidget\",\n" " displayName: \"Template Literal\",\n" -" description: \"Template literals compile to Swift string " -"interpolation\",\n" +" description: \"Template literals compile to Swift string interpolation" +"\",\n" " entryFields: {\n" " name: \"string\",\n" " score: \"number\",\n" @@ -57989,8 +57751,8 @@ msgid "" " displayName: \"City Weather\",\n" " description: \"Weather for a chosen city\",\n" " config: {\n" -" city: { type: \"string\", displayName: \"City\", default: \"New " -"York\" },\n" +" city: { type: \"string\", displayName: \"City\", default: \"New York" +"\" },\n" " units: {\n" " type: \"enum\",\n" " displayName: \"Units\",\n" @@ -58009,8 +57771,8 @@ msgstr "" " displayName: \"City Weather\",\n" " description: \"Weather for a chosen city\",\n" " config: {\n" -" city: { type: \"string\", displayName: \"City\", default: \"New " -"York\" },\n" +" city: { type: \"string\", displayName: \"City\", default: \"New York" +"\" },\n" " units: {\n" " type: \"enum\",\n" " displayName: \"Units\",\n" @@ -58043,8 +57805,8 @@ msgid "" " displayName: \"Stock Price\",\n" " description: \"Shows current stock price\",\n" " config: {\n" -" symbol: { type: \"string\", displayName: \"Symbol\", default: " -"\"AAPL\" },\n" +" symbol: { type: \"string\", displayName: \"Symbol\", default: \"AAPL" +"\" },\n" " },\n" " entryFields: { price: \"number\", change: \"string\" },\n" " provider: async (config) => {\n" @@ -58070,8 +57832,8 @@ msgstr "" " displayName: \"Stock Price\",\n" " description: \"Shows current stock price\",\n" " config: {\n" -" symbol: { type: \"string\", displayName: \"Symbol\", default: " -"\"AAPL\" },\n" +" symbol: { type: \"string\", displayName: \"Symbol\", default: \"AAPL" +"\" },\n" " },\n" " entryFields: { price: \"number\", change: \"string\" },\n" " provider: async (config) => {\n" @@ -58099,12 +57861,12 @@ msgid "" "issues/195). The verified extract uses the supported inline-options form " "`Text(\"...\", { font: \"title\" })`." msgstr "" -"Nota: los modificadores de estilo de cadena (`.font(\"title\")." -"color(\"green\")`) son rechazados con un diagnóstico de tiempo de " -"compilación para que el estilo no pueda desaparecer silenciosamente; este " -"comportamiento cerrado [\\#195](https://github.com/PerryTS/perry/" -"issues/195). El extracto verificado utiliza las opciones en línea " -"compatibles forma `Text(\"...\", { font: \"title\" })`." +"Nota: los modificadores de estilo de cadena (`.font(\"title\").color(\"green" +"\")`) son rechazados con un diagnóstico de tiempo de compilación para que el " +"estilo no pueda desaparecer silenciosamente; este comportamiento cerrado " +"[\\#195](https://github.com/PerryTS/perry/issues/195). El extracto " +"verificado utiliza las opciones en línea compatibles forma `Text(\"...\", " +"{ font: \"title\" })`." #: src/widgets/creating-widgets.md:192 msgid "Placeholder Data" @@ -58180,13 +57942,13 @@ msgstr "" #: src/widgets/creating-widgets.md:222 msgid "" -"Supported families: `\"systemSmall\"`, `\"systemMedium\"`, " -"`\"systemLarge\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, " -"`\"accessoryInline\"`." +"Supported families: `\"systemSmall\"`, `\"systemMedium\"`, `\"systemLarge" +"\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, `\"accessoryInline" +"\"`." msgstr "" -"Familias compatibles: `\"systemSmall\"`, `\"systemMedium\"`, " -"`\"systemLarge\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, " -"`\"accessoryInline\"`." +"Familias compatibles: `\"systemSmall\"`, `\"systemMedium\"`, `\"systemLarge" +"\"`, `\"accessoryCircular\"`, `\"accessoryRectangular\"`, `\"accessoryInline" +"\"`." #: src/widgets/creating-widgets.md:224 msgid "App Group" @@ -58303,10 +58065,10 @@ msgstr "" "un banner `// widget-bundle-id:` ([\\#194](https://github.com/PerryTS/perry/" "issues/194)); los objetivos de extremo a extremo también requieren su " "plataforma SDK. La sintaxis del modificador se limita deliberadamente a " -"**argumentos de opción-objeto en línea** — e.g. `Text(\"hi\", { font: " -"\"title\", color: \"red\" })` y `VStack([...], { padding: 16 })`. Las " -"cadenas tipo método como `Text(\"hi\").font(\"title\")` son rechazadas con " -"un diagnóstico de tiempo de compilación accionable en lugar de ser ignoradas " +"**argumentos de opción-objeto en línea** — e.g. `Text(\"hi\", { font: \"title" +"\", color: \"red\" })` y `VStack([...], { padding: 16 })`. Las cadenas tipo " +"método como `Text(\"hi\").font(\"title\")` son rechazadas con un diagnóstico " +"de tiempo de compilación accionable en lugar de ser ignoradas " "silenciosamente; ese comportamiento cerró [\\#195](https://github.com/" "PerryTS/perry/issues/195). La referencia de extremo a extremo es [`examples/" "widget_demo.ts`](https://github.com/PerryTS/perry/blob/main/examples/" @@ -58688,8 +58450,8 @@ msgid "" " VStack([\n" " Text(`${entry.steps}`, { font: \"title\", fontWeight: " "\"bold\" }),\n" -" Text(\"steps\", { font: \"caption\", color: " -"\"gray\" }),\n" +" Text(\"steps\", { font: \"caption\", color: \"gray" +"\" }),\n" " ]),\n" " Spacer(),\n" " VStack([\n" @@ -58732,8 +58494,8 @@ msgstr "" " VStack([\n" " Text(`${entry.steps}`, { font: \"title\", fontWeight: " "\"bold\" }),\n" -" Text(\"steps\", { font: \"caption\", color: " -"\"gray\" }),\n" +" Text(\"steps\", { font: \"caption\", color: \"gray" +"\" }),\n" " ]),\n" " Spacer(),\n" " VStack([\n" @@ -58843,8 +58605,8 @@ msgid "" "\n" " provider: async (config: { sortBy: string; dateRange: string }) => {\n" " const res = await fetch(\n" -" `https://api.example.com/stats?sort=${config.sortBy}" -"&range=${config.dateRange}`,\n" +" `https://api.example.com/stats?sort=${config.sortBy}&range=" +"${config.dateRange}`,\n" " )\n" " const data = await res.json()\n" " return {\n" @@ -58855,8 +58617,8 @@ msgid "" "\n" " render: (entry) =>\n" " VStack([\n" -" Text(`${entry.total}`, { font: \"title\", fontWeight: " -"\"bold\" }),\n" +" Text(`${entry.total}`, { font: \"title\", fontWeight: \"bold" +"\" }),\n" " Text(entry.label, { font: \"caption\", color: \"secondary\" }),\n" " ]),\n" "})\n" @@ -58892,8 +58654,8 @@ msgstr "" "\n" " provider: async (config: { sortBy: string; dateRange: string }) => {\n" " const res = await fetch(\n" -" `https://api.example.com/stats?sort=${config.sortBy}" -"&range=${config.dateRange}`,\n" +" `https://api.example.com/stats?sort=${config.sortBy}&range=" +"${config.dateRange}`,\n" " )\n" " const data = await res.json()\n" " return {\n" @@ -58904,8 +58666,8 @@ msgstr "" "\n" " render: (entry) =>\n" " VStack([\n" -" Text(`${entry.total}`, { font: \"title\", fontWeight: " -"\"bold\" }),\n" +" Text(`${entry.total}`, { font: \"title\", fontWeight: \"bold" +"\" }),\n" " Text(entry.label, { font: \"caption\", color: \"secondary\" }),\n" " ]),\n" "})\n" @@ -58943,7 +58705,7 @@ msgstr "`{ type: \"bool\", default: true, title: \"...\" }`" msgid "Toggle switch" msgstr "Interruptor de alternancia" -#: src/widgets/configuration.md:74 src/internals/memory-model.md:26 +#: src/widgets/configuration.md:74 src/internals/memory-model.md:29 msgid "String" msgstr "String" @@ -58972,8 +58734,8 @@ msgstr "" msgid "" "```text\n" "provider: async (config: { sortBy: string; dateRange: string }) => {\n" -" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | " -"\"position\"\n" +" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | \"position" +"\"\n" " // config.dateRange === \"7d\" | \"28d\" | \"90d\"\n" " const url = `https://api.example.com/data?sort=${config.sortBy}`;\n" " const res = await fetch(url);\n" @@ -58984,8 +58746,8 @@ msgid "" msgstr "" "```text\n" "provider: async (config: { sortBy: string; dateRange: string }) => {\n" -" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | " -"\"position\"\n" +" // config.sortBy === \"clicks\" | \"impressions\" | \"ctr\" | \"position" +"\"\n" " // config.dateRange === \"7d\" | \"28d\" | \"90d\"\n" " const url = `https://api.example.com/data?sort=${config.sortBy}`;\n" " const res = await fetch(url);\n" @@ -59267,8 +59029,8 @@ msgid "" " },\n" "\n" " provider: async () => {\n" -" const res = await fetch(\"https://api.weather.example.com/" -"current\")\n" +" const res = await fetch(\"https://api.weather.example.com/current" +"\")\n" " const data = await res.json()\n" " return {\n" " entries: [\n" @@ -59299,8 +59061,8 @@ msgstr "" " },\n" "\n" " provider: async () => {\n" -" const res = await fetch(\"https://api.weather.example.com/" -"current\")\n" +" const res = await fetch(\"https://api.weather.example.com/current" +"\")\n" " const data = await res.json()\n" " return {\n" " entries: [\n" @@ -59490,14 +59252,13 @@ msgstr "`{ after: { minutes: N } }`" #: src/widgets/data-fetching.md:136 msgid "" "Re-fetch after N minutes. Compiles to `.after(Date()." -"addingTimeInterval(N*60))` on iOS/watchOS, `android:" -"updatePeriodMillis=\"N*60000\"` on Android, and " -"`setFreshnessIntervalMillis(N*60000)` on Wear OS." +"addingTimeInterval(N*60))` on iOS/watchOS, `android:updatePeriodMillis=" +"\"N*60000\"` on Android, and `setFreshnessIntervalMillis(N*60000)` on Wear " +"OS." msgstr "" "Vuelve a buscar después de N minutos. Compila a `.after(Date()." -"addingTimeInterval(N*60))` en iOS/watchOS, `android:" -"updatePeriodMillis=\"N*60000\"` en Android, y " -"`setFreshnessIntervalMillis(N*60000)` en Wear OS." +"addingTimeInterval(N*60))` en iOS/watchOS, `android:updatePeriodMillis=" +"\"N*60000\"` en Android, y `setFreshnessIntervalMillis(N*60000)` en Wear OS." #: src/widgets/data-fetching.md:137 msgid "_(omitted)_" @@ -59814,7 +59575,7 @@ msgid "SwiftUI `.swift` + Info.plist" msgstr "SwiftUI `.swift` + Info.plist" #: src/widgets/platforms.md:17 src/plugins/native-extensions.md:301 -#: src/testing/geisterhand.md:341 src/cli/flags.md:99 +#: src/testing/geisterhand.md:341 src/cli/flags.md:121 msgid "iOS Simulator" msgstr "Simulador de iOS" @@ -59842,7 +59603,7 @@ msgstr "`--target watchos-widget`" msgid "SwiftUI `.swift` (accessory families)" msgstr "SwiftUI `.swift` (familias de accesorio)" -#: src/widgets/platforms.md:20 src/cli/flags.md:107 +#: src/widgets/platforms.md:20 src/cli/flags.md:129 msgid "watchOS Simulator" msgstr "Simulador de watchOS" @@ -61635,8 +61396,8 @@ msgstr "" #: src/plugins/native-extensions.md:95 msgid "" -"Array of LLVM types: `\"i64\"` for pointers/strings, `\"f64\"` for numbers, " -"`\"i32\"` for integers" +"Array of LLVM types: `\"i64\"` for pointers/strings, `\"f64\"` for numbers, `" +"\"i32\"` for integers" msgstr "" "Array de tipos LLVM: `\"i64\"` para punteros/cadenas, `\"f64\"` para " "números, `\"i32\"` para enteros" @@ -63855,7 +63616,7 @@ msgstr "" "El simulador de iOS comparte la pila de red del host — accede al servidor " "directamente en `localhost`:" -#: src/testing/geisterhand.md:352 src/cli/flags.md:100 +#: src/testing/geisterhand.md:352 src/cli/flags.md:122 msgid "iOS Device" msgstr "Dispositivo iOS" @@ -64416,8 +64177,8 @@ msgstr "\"Incremento\"" #: src/testing/geisterhand.md:657 #, fuzzy msgid "" -"# {\"handle\":4,\"widget_type\":0,\"callback_kind\":0,\"label\":" -"\"Reset\"},\n" +"# {\"handle\":4,\"widget_type\":0,\"callback_kind\":0,\"label\":\"Reset" +"\"},\n" msgstr "" "# {\"handle\":4,\"widget_type\":0,\"callback_kind\":0,\"label\":\"Reset\"}," @@ -64504,8 +64265,8 @@ msgid "" "**Widget Registration**: As UI widgets are created (Button, TextField, " "Slider, etc.), each one calls `perry_geisterhand_register(handle, " "widget_type, callback_kind, closure_f64, label)` to register its callback in " -"the global registry. This is gated behind `#[cfg(feature = " -"\"geisterhand\")]` so normal builds have zero overhead." +"the global registry. This is gated behind `#[cfg(feature = \"geisterhand" +"\")]` so normal builds have zero overhead." msgstr "" "**Registro de widgets**: A medida que se crean widgets de UI (Button, " "TextField, Slider, etc.), cada uno llama a " @@ -64647,7 +64408,7 @@ msgstr "" msgid "Platform crate selection is automatic based on `--target`:" msgstr "La selección del crate de plataforma es automática según `--target`:" -#: src/testing/geisterhand.md:774 src/cli/flags.md:96 +#: src/testing/geisterhand.md:774 src/cli/flags.md:118 msgid "Target" msgstr "Target" @@ -64659,7 +64420,7 @@ msgstr "Crate de UI" msgid "(default/macOS)" msgstr "(predeterminado/macOS)" -#: src/testing/geisterhand.md:776 src/contributing/architecture.md:39 +#: src/testing/geisterhand.md:776 src/contributing/architecture.md:42 msgid "`perry-ui-macos`" msgstr "`perry-ui-macos`" @@ -64667,12 +64428,12 @@ msgstr "`perry-ui-macos`" msgid "`ios` / `ios-simulator`" msgstr "`ios` / `ios-simulator`" -#: src/testing/geisterhand.md:777 src/contributing/architecture.md:40 +#: src/testing/geisterhand.md:777 src/contributing/architecture.md:43 msgid "`perry-ui-ios`" msgstr "`perry-ui-ios`" #: src/testing/geisterhand.md:778 src/cli/commands.md:98 -#: src/cli/commands.md:273 src/cli/flags.md:108 +#: src/cli/commands.md:273 src/cli/flags.md:130 msgid "`android`" msgstr "`android`" @@ -64680,7 +64441,7 @@ msgstr "`android`" msgid "`perry-ui-android`" msgstr "`perry-ui-android`" -#: src/testing/geisterhand.md:779 src/cli/commands.md:274 src/cli/flags.md:125 +#: src/testing/geisterhand.md:779 src/cli/commands.md:274 src/cli/flags.md:147 msgid "`linux`" msgstr "`linux`" @@ -65967,8 +65728,8 @@ msgstr "" #: src/testing/http2-conformance.md:232 msgid "" -"`RST_STREAM stream=5 code=7`, `RST_STREAM stream=7 code=7` — " -"REFUSED\\_STREAM, session survives" +"`RST_STREAM stream=5 code=7`, `RST_STREAM stream=7 code=7` — REFUSED" +"\\_STREAM, session survives" msgstr "" #: src/testing/http2-conformance.md:233 @@ -65977,8 +65738,8 @@ msgstr "" #: src/testing/http2-conformance.md:233 msgid "" -"`RST_STREAM stream=1 code=2`, `GOAWAY stream=0 last=3 code=2` — " -"INTERNAL\\_ERROR, connection torn down, `ERR_HTTP2_ERROR errno=-505`" +"`RST_STREAM stream=1 code=2`, `GOAWAY stream=0 last=3 code=2` — INTERNAL" +"\\_ERROR, connection torn down, `ERR_HTTP2_ERROR errno=-505`" msgstr "" #: src/testing/http2-conformance.md:235 @@ -66276,8 +66037,8 @@ msgstr "" #: src/testing/http2-conformance.md:325 msgid "" -"trailers: event order is exactly `[\"response\",\"data\",\"trailers\"," -"\"end\"]`;" +"trailers: event order is exactly `[\"response\",\"data\",\"trailers\",\"end" +"\"]`;" msgstr "" #: src/testing/http2-conformance.md:326 @@ -66592,12 +66353,17 @@ msgstr "" "liberación" #: src/testing/ci-tiers.md:3 +#, fuzzy msgid "" "Perry's CI is one workflow — `.github/workflows/test.yml` — with **three " "tiers**. The tier a run belongs to, and the exact set of jobs it executes, " "is decided by one script, `scripts/ci_plan.py`, in the workflow's first job " -"(`plan`). Every other job is `needs: plan` and gated on `fromJSON(needs.plan." -"outputs.plan).jobs.`." +"(`plan`). Planned jobs use `needs: plan` and `fromJSON(needs.plan.outputs." +"plan).jobs.`. The `tokio-ban` preflight runs independently on every PR " +"and core CI run. It checks repository Cargo manifests, lockfiles and Rust " +"source without a Rust toolchain. The gate requires its result to be " +"`success`; a missing, skipped, failed or cancelled preflight cannot pass `pr-" +"gate` or a release gate." msgstr "" "El CI de Perry es un flujo de trabajo — `.github/workflows/test.yml` — con " "**tres niveles**. El nivel al que pertenece una ejecución, y el conjunto " @@ -66606,48 +66372,48 @@ msgstr "" "es `needs: plan` y cerrado en `fromJSON(needs.plan.outputs.plan).jobs." "`." -#: src/testing/ci-tiers.md:13 src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:17 src/testing/ci-tiers.md:194 msgid "tier" msgstr "tier" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "trigger" msgstr "trigger" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "what it is for" msgstr "¿Para qué es" -#: src/testing/ci-tiers.md:13 +#: src/testing/ci-tiers.md:17 msgid "fan-in job" msgstr "trabajo de fan-in" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "**pr**" msgstr "**pr**" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "every `pull_request` push" msgstr "cada empuje `pull_request`" -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "the required gate. Small, fast, must be green on `main`." msgstr "la puerta requerida. Pequeño, rápido, debe ser verde en `main`." -#: src/testing/ci-tiers.md:15 +#: src/testing/ci-tiers.md:19 msgid "`pr-gate` — **the only required status context**" msgstr "`pr-gate` — **el único contexto de estado requerido**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "**sweep**" msgstr "**barre**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "every `push` to `main` (coalesced) **+ a two-hourly cron backstop**" msgstr "" "cada `push` a `main` (coalimentado) **+ un soporte de cron de dos horas**" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 #, fuzzy msgid "" "post-merge truth for `main`: unscoped unit/package tests plus medium-weight " @@ -66657,15 +66423,15 @@ msgstr "" "verdad post-fusiones para `main`: el nivel de PR sin alcance más los " "trabajos de peso medio que no se ajustan al presupuesto de PR" -#: src/testing/ci-tiers.md:16 +#: src/testing/ci-tiers.md:20 msgid "`main-gate`" msgstr "`main-gate`" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "**full**" msgstr "**lleno**" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "" "nightly `schedule`, `v*` tags, `workflow_dispatch`, PRs labelled `run-" "extended-tests`" @@ -66673,7 +66439,7 @@ msgstr "" "por la noche `schedule`, `v*` etiquetas, `workflow_dispatch`, PRs " "etiquetados `run-extended-tests`" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 #, fuzzy msgid "" "everything, incl. parity, compile-smoke, doc-tests, package smokes, the 24-" @@ -66682,15 +66448,15 @@ msgstr "" "todo, incl. paridad, compile-smoke, doc-tests, humos de paquetes, la suite " "de 8-shard auto-optimizar brecha" -#: src/testing/ci-tiers.md:17 +#: src/testing/ci-tiers.md:21 msgid "`full-suite-gate` — what `release-packages.yml` waits for" msgstr "`full-suite-gate` — lo que `release-packages.yml` espera" -#: src/testing/ci-tiers.md:19 +#: src/testing/ci-tiers.md:23 msgid "The job × tier matrix" msgstr "El trabajo × matriz de nivel" -#: src/testing/ci-tiers.md:21 +#: src/testing/ci-tiers.md:25 msgid "" "Generated by `python3 scripts/ci_plan.py --table`; the `lint` job checks " "that this copy is current." @@ -66698,7 +66464,7 @@ msgstr "" "Generado por `python3 scripts/ci_plan.py --table`; el trabajo de `lint` " "comprueba que esta copia es actual." -#: src/testing/ci-tiers.md:24 +#: src/testing/ci-tiers.md:28 msgid "" "The PR gap tier uses twelve fast-mode shards after a six-way shard exhausted " "the 110-minute job bound. The full gap tier uses twenty-four shards with " @@ -66714,151 +66480,161 @@ msgid "" "CI durations must still establish the new margin." msgstr "" -#: src/testing/ci-tiers.md:37 +#: src/testing/ci-tiers.md:41 msgid "job" msgstr "job" -#: src/testing/ci-tiers.md:37 +#: src/testing/ci-tiers.md:41 msgid "pr" msgstr "pr" -#: src/testing/ci-tiers.md:37 src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:198 msgid "sweep" msgstr "sweep" -#: src/testing/ci-tiers.md:37 src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:199 msgid "full" msgstr "full" -#: src/testing/ci-tiers.md:39 +#: src/testing/ci-tiers.md:43 msgid "`lint`" msgstr "`lint`" -#: src/testing/ci-tiers.md:39 +#: src/testing/ci-tiers.md:43 msgid "always" msgstr "always" -#: src/testing/ci-tiers.md:40 src/cli/commands.md:11 +#: src/testing/ci-tiers.md:44 src/cli/commands.md:11 msgid "`check`" msgstr "`check`" -#: src/testing/ci-tiers.md:41 +#: src/testing/ci-tiers.md:45 msgid "`warnings`" msgstr "`warnings`" -#: src/testing/ci-tiers.md:42 +#: src/testing/ci-tiers.md:46 msgid "`cargo-test`" msgstr "`cargo-test`" -#: src/testing/ci-tiers.md:43 +#: src/testing/ci-tiers.md:47 #, fuzzy msgid "`cargo-test-perry`" msgstr "`cargo-test`" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 msgid "`gap-suite`" msgstr "`gap-suite`" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 #, fuzzy msgid "12x fast" msgstr "6 veces rápido" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 msgid "3x fast" msgstr "3 veces rápido" -#: src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:48 #, fuzzy msgid "24x full" msgstr "8x lleno" -#: src/testing/ci-tiers.md:45 +#: src/testing/ci-tiers.md:49 #, fuzzy msgid "`wasi-execution`" msgstr "`flexDirection`" -#: src/testing/ci-tiers.md:46 +#: src/testing/ci-tiers.md:50 msgid "`gc-call-effects`" msgstr "" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "`gc-stress`" msgstr "`gc-stress`" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "1x pr" msgstr "" -#: src/testing/ci-tiers.md:47 +#: src/testing/ci-tiers.md:51 msgid "4x all" msgstr "" -#: src/testing/ci-tiers.md:48 +#: src/testing/ci-tiers.md:52 msgid "`e2e-scoped`" msgstr "`e2e-scoped`" -#: src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:53 msgid "`security-audit`" msgstr "`security-audit`" -#: src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:53 msgid "deps only" msgstr "sólo deps" -#: src/testing/ci-tiers.md:50 +#: src/testing/ci-tiers.md:54 msgid "`windows-build`" msgstr "`windows-build`" -#: src/testing/ci-tiers.md:51 +#: src/testing/ci-tiers.md:55 msgid "`windows-arm64-build`" msgstr "`windows-arm64-build`" -#: src/testing/ci-tiers.md:52 +#: src/testing/ci-tiers.md:56 msgid "`compiler-output-regression`" msgstr "`compiler-output-regression`" -#: src/testing/ci-tiers.md:53 +#: src/testing/ci-tiers.md:57 msgid "`repsel-census`" msgstr "`repsel-census`" -#: src/testing/ci-tiers.md:54 +#: src/testing/ci-tiers.md:58 msgid "`harmonyos-smoke`" msgstr "`harmonyos-smoke`" -#: src/testing/ci-tiers.md:55 +#: src/testing/ci-tiers.md:59 msgid "`binary-size`" msgstr "`binary-size`" -#: src/testing/ci-tiers.md:56 +#: src/testing/ci-tiers.md:60 msgid "`parity`" msgstr "`parity`" -#: src/testing/ci-tiers.md:57 +#: src/testing/ci-tiers.md:61 +#, fuzzy +msgid "`node-suite-guard`" +msgstr "`nodemailer`" + +#: src/testing/ci-tiers.md:62 +#, fuzzy +msgid "`test262`" +msgstr "`test`" + +#: src/testing/ci-tiers.md:63 msgid "`compile-smoke`" msgstr "`compile-smoke`" -#: src/testing/ci-tiers.md:58 +#: src/testing/ci-tiers.md:64 msgid "`native-abi-evidence-packet`" msgstr "`native-abi-evidence-packet`" -#: src/testing/ci-tiers.md:59 +#: src/testing/ci-tiers.md:65 msgid "`drizzle-mysql-smoke`" msgstr "`drizzle-mysql-smoke`" -#: src/testing/ci-tiers.md:60 +#: src/testing/ci-tiers.md:66 msgid "`ink-link-smoke`" msgstr "`ink-link-smoke`" -#: src/testing/ci-tiers.md:61 +#: src/testing/ci-tiers.md:67 msgid "`effect-basic-smoke`" msgstr "`effect-basic-smoke`" -#: src/testing/ci-tiers.md:62 +#: src/testing/ci-tiers.md:68 msgid "`doc-tests`" msgstr "`doc-tests`" -#: src/testing/ci-tiers.md:64 +#: src/testing/ci-tiers.md:70 msgid "" "`windows-build` also runs `python scripts/run_windows_runtime_regressions." "py` after building the compiler and both static archives. It compares the " @@ -66871,22 +66647,24 @@ msgid "" "tier (`run-extended-tests`) when reviewing changes to this behavior." msgstr "" -#: src/testing/ci-tiers.md:74 +#: src/testing/ci-tiers.md:80 msgid "Within the **pr** tier the changed-file list narrows the plan further:" msgstr "" "Dentro de la **pr** nivel la lista de archivos cambiados reduce el plan aún " "más:" -#: src/testing/ci-tiers.md:76 +#: src/testing/ci-tiers.md:82 +#, fuzzy msgid "" "**docs-only** (only `docs/`, `*.md`, `benchmarks/`, `npm/`, `packaging/`, `." -"claude/`, … — see `NON_CORE_GLOBS` in `ci_plan.py`) → only `lint` runs." +"claude/`, … — see `NON_CORE_GLOBS` in `ci_plan.py`) → `lint` and the " +"mandatory `tokio-ban` preflight run." msgstr "" "**sólo docs** (sólo `docs/`, `*.md`, `benchmarks/`, `npm/`, `packaging/`, `." "claude/`, ... — véase `NON_CORE_GLOBS` en `ci_plan.py`) → sólo `lint` se " "ejecuta." -#: src/testing/ci-tiers.md:78 +#: src/testing/ci-tiers.md:85 msgid "" "**core** (anything that can change the compiler, the runtime, or a test " "outcome) → the whole PR tier." @@ -66894,7 +66672,7 @@ msgstr "" "**core** (cualquier cosa que pueda cambiar el compilador, el tiempo de " "ejecución o un resultado de prueba) → todo el nivel PR." -#: src/testing/ci-tiers.md:80 +#: src/testing/ci-tiers.md:87 #, fuzzy msgid "" "**deps** (a lockfile, manifest, `deny.toml`, `package.json`, `.claude/`, " @@ -66904,7 +66682,7 @@ msgstr "" "claude/`, `skills/`, ...) → adicionalmente el flujo de trabajo reutilizable " "`security-audit`." -#: src/testing/ci-tiers.md:83 +#: src/testing/ci-tiers.md:90 msgid "" "Within `cargo-test`, runtime changes also select `perry-stdlib` unit tests: " "those tests exercise runtime GC, arenas, closures, promises and the event " @@ -66913,11 +66691,11 @@ msgid "" "regression tests in `lint`." msgstr "" -#: src/testing/ci-tiers.md:89 +#: src/testing/ci-tiers.md:96 msgid "Rust test lanes and timeboxes" msgstr "" -#: src/testing/ci-tiers.md:91 +#: src/testing/ci-tiers.md:98 msgid "" "Perry's Cargo suites use the same fast / mid / slow idea as socket-" "wheelhouse, but their wall times are measured in minutes because Rust " @@ -66926,84 +66704,84 @@ msgid "" "should not be copied onto Cargo." msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Lane" msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 #, fuzzy msgid "What runs" msgstr "Qué correr" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Expected time" msgstr "" -#: src/testing/ci-tiers.md:96 +#: src/testing/ci-tiers.md:103 msgid "Enforced timebox" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "**Fast**" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "" "PR-scoped crate unit tests (`--lib --bins`), plus the always-on FFI ABI unit " "check" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "`<10 min` for the scoped Cargo loop" msgstr "" -#: src/testing/ci-tiers.md:98 +#: src/testing/ci-tiers.md:105 msgid "" "`30 min` Cargo job cap; the PR gate targets `≤30 min` wall clock once queued." msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 #, fuzzy msgid "**Mid**" msgstr "**Android**" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "" "Full workspace package tests on main sweeps, including Perry bin/unit tests; " "Perry integration tests are left to the slow lane" msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "" "`<60 min` target; the prior full Cargo job measured `50–60 min` warm and `90–" "103 min` cold before removing Perry's serial integration inventory" msgstr "" -#: src/testing/ci-tiers.md:99 +#: src/testing/ci-tiers.md:106 msgid "`120 min` Cargo job cap." msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "**Slow**" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "" "Complete Perry integration inventory on nightly, release tags, and opt-in " "full runs; changed suites run through `e2e-scoped` on PRs" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "Eight round-robin shards" msgstr "" -#: src/testing/ci-tiers.md:100 +#: src/testing/ci-tiers.md:107 msgid "" "`120 min` per shard. The separate scoped e2e job applies `25 min` subprocess " "bounds where configured." msgstr "" -#: src/testing/ci-tiers.md:102 +#: src/testing/ci-tiers.md:109 msgid "" "The hard caps are failure cutoffs, not performance goals. Although fast and " "mid share the `cargo-test` job, its timeout comes from the planner tier: 30 " @@ -67025,7 +66803,7 @@ msgid "" "crates while sharing unchanged compilations." msgstr "" -#: src/testing/ci-tiers.md:121 +#: src/testing/ci-tiers.md:128 msgid "" "`e2e-scoped` runs integration suites selected by the diff, but its " "`SUITE_EXCLUSIONS` check is deliberately not scoped: whenever that list is " @@ -67040,7 +66818,7 @@ msgstr "" "hechas en HIR, transformar, u otra dependencia sin añadir la configuración " "Rust a PRs sólo docs." -#: src/testing/ci-tiers.md:127 +#: src/testing/ci-tiers.md:134 msgid "" "An empty or failed file listing is treated as **core** and a failed `plan` " "job fails the gate outright — a broken planner must never turn into " @@ -67050,11 +66828,11 @@ msgstr "" "fallido de `plan` falla en la puerta, un planificador roto nunca debe " "convertirse en \"todo salteado, por lo tanto verde\"." -#: src/testing/ci-tiers.md:131 +#: src/testing/ci-tiers.md:138 msgid "Why it looks like this (measured 2026-08-16)" msgstr "¿Por qué se ve así (medido 2026-08-16)" -#: src/testing/ci-tiers.md:133 +#: src/testing/ci-tiers.md:140 msgid "" "The organisation runs on GitHub's Free plan: **20 concurrent hosted jobs, 5 " "of them macOS**, for the whole org. Before this shape, every PR push fanned " @@ -67067,13 +66845,13 @@ msgstr "" "trabajos / ~650 segundos**, y `main` vio ~66 PR empuja y ~58 fusiona al día. " "Es decir, 1.5–2× capacidad total, así que:" -#: src/testing/ci-tiers.md:138 +#: src/testing/ci-tiers.md:145 msgid "job queue waits were 3–7 h (`conformance-smoke` shards: median 4 h);" msgstr "" "las esperas de la cola de trabajo fueron de 3-7 h (los fragmentos de " "`conformance-smoke`: mediana 4 h);" -#: src/testing/ci-tiers.md:139 +#: src/testing/ci-tiers.md:146 msgid "" "**0 of 66** PR runs of the `Tests` workflow reached a conclusion in the " "sample window — 8 failed, 56 were cancelled by the next push;" @@ -67082,7 +66860,7 @@ msgstr "" "una conclusión en la ventana de muestra: 8 fallaron, 56 fueron canceladas " "por el siguiente empujón;" -#: src/testing/ci-tiers.md:141 +#: src/testing/ci-tiers.md:148 msgid "" "the required contexts included two jobs (`parity`, `compile-smoke`) that " "never ran on a PR and one (`conformance-smoke-complete`) that had been red " @@ -67094,7 +66872,7 @@ msgstr "" "había sido rojo en `main` durante días, así que **cada una de las últimas 12 " "fusiones fue un bypass administrador** con `lint` y `cargo-test` en cola." -#: src/testing/ci-tiers.md:146 +#: src/testing/ci-tiers.md:153 msgid "" "The always-red required-gate incident was tracked in [\\#8092](https://" "github.com/PerryTS/perry/issues/8092). #8087 restored `lint`, \\#8095 made " @@ -67109,11 +66887,11 @@ msgstr "" "Linux, y #8187 reemplazó la fragmentada lista de contexto requerido con el " "solo ventilador `pr-gate` descrito anteriormente." -#: src/testing/ci-tiers.md:152 +#: src/testing/ci-tiers.md:159 msgid "Two specific costs dominated:" msgstr "Dos costes específicos predominaron:" -#: src/testing/ci-tiers.md:154 +#: src/testing/ci-tiers.md:161 #, fuzzy msgid "" "`conformance-smoke` was 8 shards × ~60 min = **480 job-minutes per push**, " @@ -67136,7 +66914,7 @@ msgstr "" "automática de 8-shard se mantiene en el nivel completo porque es el único " "brazo que ve errores de enlace de sólo auto-optimizar." -#: src/testing/ci-tiers.md:161 +#: src/testing/ci-tiers.md:168 msgid "" "Every job saved a fresh ~0.5–1.3 GB sccache tarball on every PR push (~200 " "GB/day into a 10 GB repo cache budget), evicting every useful entry within " @@ -67151,7 +66929,7 @@ msgstr "" "línea principal; PRs restauran la nueva blob de línea principal. `cache-warm." "yml` se ha ido: el barrido es la compilación productora de caché en `main`." -#: src/testing/ci-tiers.md:167 +#: src/testing/ci-tiers.md:174 msgid "" "A later release run exposed a separate serial bottleneck: its 246 `perry` " "integration-test binaries had not reached their midpoint when `cargo-test` " @@ -67163,7 +66941,7 @@ msgid "" "result is a direct dependency of `full-suite-gate`." msgstr "" -#: src/testing/ci-tiers.md:175 +#: src/testing/ci-tiers.md:182 msgid "" "The **satellite gates** (`gc-ratchet`, `gc-root-dominance`, `gc-native-" "roots`, `gc-moving-witnesses`, `gc-parse-churn-gate`, `gc-ptr-shape-off-" @@ -67187,75 +66965,75 @@ msgstr "" "de cada uno — con cada trabajo omitido, que no cuesta ninguna ranura de " "corredor." -#: src/testing/ci-tiers.md:185 +#: src/testing/ci-tiers.md:192 msgid "Budgets" msgstr "Budgets" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "typical jobs" msgstr "trabajos típicos" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "typical runner-minutes" msgstr "Los típicos minutos de corredor" -#: src/testing/ci-tiers.md:187 +#: src/testing/ci-tiers.md:194 msgid "target wall clock" msgstr "reloj de pared objetivo" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "pr (core)" msgstr "pr (básico)" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "~13 (6 gap shards)" msgstr "~13 (6 fragmentos de espacio)" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "~200" msgstr "~200" -#: src/testing/ci-tiers.md:189 +#: src/testing/ci-tiers.md:196 msgid "≤ 30 min once queued" msgstr "≤ 30 min una vez en cola" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "pr (docs-only)" msgstr "pr (solo docs)" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "~5" msgstr "~5" -#: src/testing/ci-tiers.md:190 +#: src/testing/ci-tiers.md:197 msgid "≤ 5 min" msgstr "≤ 5 min" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "~19" msgstr "~19" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "~300" msgstr "~300" -#: src/testing/ci-tiers.md:191 +#: src/testing/ci-tiers.md:198 msgid "not a target — it coalesces" msgstr "no un objetivo — se funde" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "~30" msgstr "~30" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "~1500" msgstr "~1500" -#: src/testing/ci-tiers.md:192 +#: src/testing/ci-tiers.md:199 msgid "not a target" msgstr "no es un objetivo" -#: src/testing/ci-tiers.md:194 +#: src/testing/ci-tiers.md:201 msgid "" "`push` to `main` uses ONE constant concurrency group with `cancel-in-" "progress: false`: GitHub keeps at most one running + one pending run per " @@ -67286,11 +67064,11 @@ msgstr "" "de barrido es por ventana (`previous sweep SHA .. this sweep SHA`), " "exactamente como para las puertas de seis horas." -#: src/testing/ci-tiers.md:205 +#: src/testing/ci-tiers.md:212 msgid "Opting a PR into more" msgstr "Optar por una RRPP en más" -#: src/testing/ci-tiers.md:207 +#: src/testing/ci-tiers.md:214 msgid "" "**`run-cc-parity` label** — runs the [Claude Code bundle parity gate](cc-" "parity.md) on changes to crates, build inputs, or the gate. This " @@ -67299,7 +67077,7 @@ msgid "" "goldens on one macOS runner." msgstr "" -#: src/testing/ci-tiers.md:211 +#: src/testing/ci-tiers.md:218 msgid "" "**`run-extended-tests` label** — promotes the PR's `test.yml` run to the " "`full` tier AND enables the PR arm of every satellite gate. Use it for GC / " @@ -67313,7 +67091,7 @@ msgstr "" "para cualquier cosa que toque una suite de nivel completo. La aplicación de " "la etiqueta reinicia las ejecuciones (`labeled` trigger)." -#: src/testing/ci-tiers.md:215 +#: src/testing/ci-tiers.md:222 msgid "" "**`skip-changelog` label** — skips the changeset step in `lint` (a `crates/` " "change must otherwise add a `changelog.d/-.md` fragment)." @@ -67322,7 +67100,7 @@ msgstr "" "`lint` (un cambio de `crates/` debe añadir de otro modo un fragmento de " "`changelog.d/-.md`)." -#: src/testing/ci-tiers.md:217 +#: src/testing/ci-tiers.md:224 msgid "" "**`workflow_dispatch`** on any branch: `tier` (`pr` / `sweep` / `full`) and " "`update_gap_snapshot`." @@ -67330,11 +67108,11 @@ msgstr "" "**`workflow_dispatch`** en cualquier rama: `tier` (`pr` / `sweep` / `full`) " "y `update_gap_snapshot`." -#: src/testing/ci-tiers.md:220 +#: src/testing/ci-tiers.md:227 msgid "Re-baselining the gap snapshot" msgstr "Rebasado de la instantánea de la brecha" -#: src/testing/ci-tiers.md:222 +#: src/testing/ci-tiers.md:229 msgid "" "`test-parity/gap_snapshot.json` is a both-direction snapshot: any divergence " "— a test that started failing OR started passing — is red. When `main` has " @@ -67347,17 +67125,17 @@ msgstr "" "cambio de oráculo), regenerarlo **de CI**, no desde una computadora portátil " "(la línea de base requerida es Linux, modo `fast`):" -#: src/testing/ci-tiers.md:228 +#: src/testing/ci-tiers.md:235 #, fuzzy msgid "# wait for the run, then:\n" msgstr "# Espera a que corra, entonces:" -#: src/testing/ci-tiers.md:231 +#: src/testing/ci-tiers.md:238 #, fuzzy msgid "# fill in `issue` / `reason` for every new entry, then commit\n" msgstr "# llenar `issue` / `reason` para cada nueva entrada, a continuación," -#: src/testing/ci-tiers.md:235 +#: src/testing/ci-tiers.md:242 msgid "" "New entries land as `category: untriaged`; give each an issue and a reason " "before merging. A crash may never be parked in the snapshot (`run_gap_tests." @@ -67367,11 +67145,11 @@ msgstr "" "problema y una razón antes de fusionarse. Un accidente nunca se puede " "estacionar en la instantánea (`run_gap_tests.sh` se niega)." -#: src/testing/ci-tiers.md:238 +#: src/testing/ci-tiers.md:245 msgid "Branch protection" msgstr "Protección de las subdivisiones" -#: src/testing/ci-tiers.md:240 +#: src/testing/ci-tiers.md:247 msgid "" "Required status checks on `main`: **`pr-gate`** only. Adding, removing or " "renaming a job in `test.yml` never needs a branch-protection edit again; the " @@ -67388,11 +67166,11 @@ msgstr "" "su presupuesto; la propia ejecución de la puerta lleva si ese resultado pasó " "o falló." -#: src/testing/ci-tiers.md:246 +#: src/testing/ci-tiers.md:253 msgid "Release" msgstr "Release" -#: src/testing/ci-tiers.md:248 +#: src/testing/ci-tiers.md:255 msgid "" "`release-packages.yml`'s `await-tests` dispatches `test.yml` with " "`tier=full` on the pinned release branch and polls the SHA for a run whose " @@ -67404,7 +67182,7 @@ msgstr "" "suite-gate`** trabajo succeeded — un barrido verde o PR-tier ejecutar en el " "mismo SHA no cuenta. Véase [Releasing](../contributing/releasing.md)." -#: src/testing/ci-tiers.md:253 +#: src/testing/ci-tiers.md:260 msgid "" "`parity` runs as 8 shards (`run_parity_tests.sh --shard N/8`) after the " "unsharded job was killed by GitHub's 6-hour job cap on 2026-08-16. " @@ -67421,11 +67199,22 @@ msgstr "" "aggregate` sobre el informe fusionado (`scripts/parity_report_merge.py`, que " "rechaza un fragmento faltante en lugar de reducir la suite)." -#: src/testing/ci-tiers.md:261 +#: src/testing/ci-tiers.md:267 +msgid "" +"`test262` runs every Test262 case (`scripts/test262_subset.py --full-" +"corpus`) as 16 shards at the SHA in `test-compat/test262/pinned-sha.txt`. " +"`test262-aggregate` merges them with `scripts/test262_ratchet.py`, prints " +"the pass rate over the whole corpus in the run summary, and fails on a new " +"failure, on a known failure that now passes (remove it from `test-compat/" +"test262/baseline.json`), or on a pass count below the baseline. Re-baseline " +"with `scripts/test262_ratchet.py --update `." +msgstr "" + +#: src/testing/ci-tiers.md:275 msgid "Workflow suite selection" msgstr "" -#: src/testing/ci-tiers.md:263 +#: src/testing/ci-tiers.md:277 msgid "" "The **CI** Actions workflow defaults to `suite=core`, preserving the tiered " "`pr-gate`, `main-gate`, and `full-suite-gate`. Select `suite=coverage`, " @@ -67434,76 +67223,76 @@ msgid "" "`tier` selector continues to control core CI." msgstr "" -#: src/testing/ci-tiers.md:265 +#: src/testing/ci-tiers.md:279 msgid "" "The Actions sidebar has six repository workflows. GitHub also registers four " "independent entries for Dependabot Updates, Dependency Graph, Copilot, and " "Pages, making ten entries once retired workflow history is cleaned up." msgstr "" -#: src/testing/ci-tiers.md:267 +#: src/testing/ci-tiers.md:281 #, fuzzy msgid "Categories and suite selection" msgstr "Categorías y puertas de migración" -#: src/testing/ci-tiers.md:269 +#: src/testing/ci-tiers.md:283 msgid "**CI**" msgstr "" -#: src/testing/ci-tiers.md:269 +#: src/testing/ci-tiers.md:283 msgid "" "Required core CI and auxiliary security/coverage checks; defaults to " "`suite=core`" msgstr "" -#: src/testing/ci-tiers.md:270 +#: src/testing/ci-tiers.md:284 msgid "**Extended Tests** (`compiler-runtime.yml`)" msgstr "" -#: src/testing/ci-tiers.md:270 +#: src/testing/ci-tiers.md:284 msgid "" "Compiler/runtime, compatibility, integration, and performance; choose a " "category such as `suite=compatibility`, one suite such as `suite=simctl-" "tests`, or `suite=all`" msgstr "" -#: src/testing/ci-tiers.md:271 +#: src/testing/ci-tiers.md:285 msgid "**GC**" msgstr "" -#: src/testing/ci-tiers.md:271 +#: src/testing/ci-tiers.md:285 msgid "Six collector suites; choose an individual suite or `suite=all`" msgstr "" -#: src/testing/ci-tiers.md:272 +#: src/testing/ci-tiers.md:286 msgid "**Repository** (`maintenance.yml`)" msgstr "" -#: src/testing/ci-tiers.md:272 +#: src/testing/ci-tiers.md:286 msgid "" "Documentation and repository maintenance; defaults to offline " "`suite=validate`; select `suite=docs-check` for docs checks or `suite=docs` " "for deployment" msgstr "" -#: src/testing/ci-tiers.md:273 +#: src/testing/ci-tiers.md:287 msgid "**Publish to npm / GitHub Release**" msgstr "" -#: src/testing/ci-tiers.md:273 +#: src/testing/ci-tiers.md:287 msgid "Compiler packages, platform archives, and GitHub releases" msgstr "" -#: src/testing/ci-tiers.md:274 +#: src/testing/ci-tiers.md:288 #, fuzzy msgid "**Publish to npm / @perryts/hono-server**" msgstr "`@perryts/hono-server`" -#: src/testing/ci-tiers.md:274 +#: src/testing/ci-tiers.md:288 msgid "Independent adapter package publication" msgstr "" -#: src/testing/ci-tiers.md:276 +#: src/testing/ci-tiers.md:290 msgid "" "All 38 original suites remain represented. Schedules select their original " "suites even when several categories share a parent. Category and `all` " @@ -67514,7 +67303,7 @@ msgid "" "filenames for npm trusted publisher authorization." msgstr "" -#: src/testing/ci-tiers.md:278 +#: src/testing/ci-tiers.md:292 msgid "" "Job display names use lowercase. Nested suite jobs use the stable suite ID " "as their prefix, for example `docs-check / build-and-validation` and `docs-" @@ -67524,7 +67313,7 @@ msgid "" "check_actions_topology.py` enforces lowercase labels and suite prefixes." msgstr "" -#: src/testing/ci-tiers.md:285 +#: src/testing/ci-tiers.md:299 msgid "" "For example, `gh workflow run compiler-runtime.yml -f suite=performance` " "runs the performance category; `gh workflow run compiler-runtime.yml -f " @@ -67533,7 +67322,7 @@ msgid "" "actual job result, independently of other Extended Tests jobs." msgstr "" -#: src/testing/ci-tiers.md:287 +#: src/testing/ci-tiers.md:301 msgid "" "After a consolidation lands, Repository automatically deletes completed runs " "whose repository workflow file no longer exists. It allows queued/running " @@ -67542,11 +67331,11 @@ msgid "" "last runs are cleaned up." msgstr "" -#: src/testing/ci-tiers.md:289 +#: src/testing/ci-tiers.md:303 msgid "Required WASI command and component execution" msgstr "" -#: src/testing/ci-tiers.md:291 +#: src/testing/ci-tiers.md:305 msgid "" "`wasi-execution` joins the core PR, sweep and full tiers. On Linux it builds " "Perry with `target-wasi` and a matching wasm32-wasip2 runtime, checks the IR " @@ -67981,8 +67770,8 @@ msgid "" "The `github.sha` arm is guarded on `github.event_name == 'push'`. #7856 — " "the change this very document describes — moved the main-line arm from " "`push: branches: [main]` to `schedule:`. The guard stopped matching, the " -"expression fell through to `github.ref` (constant `refs/heads/main`), and " -"**\\#7205 came straight back on the arm \\#7856 created.** Measured " +"expression fell through to `github.ref` (constant `refs/heads/main`), and **" +"\\#7205 came straight back on the arm \\#7856 created.** Measured " "2026-08-12, identically on all ten gates: oldest run `queued` holding the " "group, the next two `cancelled` with `jobs: 0`, newest `pending`. `gate-" "freshness` itself was cancelled the same way." @@ -67990,8 +67779,8 @@ msgstr "" "El brazo `github.sha` está vigilado en `github.event_name == 'push'`. #7856 —" "el cambio que describe este mismo documento— movió el brazo principal de " "`push: branches: [main]` a `schedule:`. El guardia dejó de coincidir, la " -"expresión cayó a través de `github.ref` (constante `refs/heads/main`), y " -"**\\#7205 volvió directamente al brazo creado por \\#7856.** Medido " +"expresión cayó a través de `github.ref` (constante `refs/heads/main`), y **" +"\\#7205 volvió directamente al brazo creado por \\#7856.** Medido " "2026-08-12, idénticamente en las diez puertas: la ejecución más antigua " "`queued` sosteniendo el grupo, las dos siguientes `cancelled` con `jobs: 0`, " "la más reciente `pending`. `gate-freshness` en sí mismo fue cancelado de la " @@ -68014,16 +67803,16 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:85 msgid "" -"**The lesson is about the sentence, not the YAML.** \"Do not fix this " -"again\" is a claim about the future, and a scheduling change three days " -"later invalidated it. A repaired invariant should be written down as an " +"**The lesson is about the sentence, not the YAML.** \"Do not fix this again" +"\" is a claim about the future, and a scheduling change three days later " +"invalidated it. A repaired invariant should be written down as an " "_executable check_, not as an instruction to the next human to stop looking." msgstr "" -"**La lección es sobre la frase, no sobre el YAML.** \"No lo arregles otra " -"vez\" es una afirmación sobre el futuro, y un cambio de programación tres " -"días después lo invalida. Un invariante reparado debe ser escrito como un " -"check_ _ejecutable, no como una instrucción para el próximo humano para " -"dejar de mirar." +"**La lección es sobre la frase, no sobre el YAML.** \"No lo arregles otra vez" +"\" es una afirmación sobre el futuro, y un cambio de programación tres días " +"después lo invalida. Un invariante reparado debe ser escrito como un check_ " +"_ejecutable, no como una instrucción para el próximo humano para dejar de " +"mirar." #: src/testing/ci-gate-scheduling.md:90 msgid "" @@ -68614,8 +68403,8 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:275 msgid "" -"Why \"the gate was dark\" is not the same as \"the gate would have caught " -"it\"" +"Why \"the gate was dark\" is not the same as \"the gate would have caught it" +"\"" msgstr "" "¿Por qué \"la puerta estaba oscura\" no es lo mismo que \"la puerta lo " "habría atrapado\"" @@ -68647,8 +68436,8 @@ msgstr "" #: src/testing/ci-gate-scheduling.md:285 msgid "" -"The two dimensions that did move — wall time and RSS — are explicitly " -"`\"gating\": false` in the `shared_ci` profile CI runs, with the rationale " +"The two dimensions that did move — wall time and RSS — are explicitly `" +"\"gating\": false` in the `shared_ci` profile CI runs, with the rationale " "recorded in `tolerances.json`. They gate only under `pinned_host`, which CI " "never uses." msgstr "" @@ -69009,14 +68798,14 @@ msgstr "# O taquigrafía (detecta automáticamente la compilación):" #: src/cli/commands.md:48 src/cli/commands.md:142 src/cli/commands.md:183 #: src/cli/commands.md:216 src/cli/commands.md:230 src/cli/commands.md:283 -#: src/cli/commands.md:295 src/cli/flags.md:80 src/cli/flags.md:143 -#: src/cli/flags.md:191 src/cli/flags.md:291 src/cli/flags.md:379 -#: src/cli/flags.md:412 src/cli/flags.md:419 +#: src/cli/commands.md:295 src/cli/flags.md:102 src/cli/flags.md:165 +#: src/cli/flags.md:213 src/cli/flags.md:313 src/cli/flags.md:401 +#: src/cli/flags.md:434 src/cli/flags.md:441 msgid "Flag" msgstr "Flag" #: src/cli/commands.md:50 src/cli/commands.md:144 src/cli/commands.md:278 -#: src/cli/flags.md:145 +#: src/cli/flags.md:167 msgid "`-o, --output `" msgstr "`-o, --output `" @@ -69040,7 +68829,7 @@ msgstr "`--output-type `" msgid "`executable` (default) or `dylib` (plugin)" msgstr "`executable` (predeterminado) o `dylib` (plugin)" -#: src/cli/commands.md:53 src/cli/flags.md:293 +#: src/cli/commands.md:53 src/cli/flags.md:315 msgid "`--print-hir`" msgstr "`--print-hir`" @@ -69048,7 +68837,7 @@ msgstr "`--print-hir`" msgid "Print HIR intermediate representation" msgstr "Imprime la representación intermedia HIR" -#: src/cli/commands.md:54 src/cli/flags.md:296 +#: src/cli/commands.md:54 src/cli/flags.md:318 msgid "`--no-link`" msgstr "`--no-link`" @@ -69060,7 +68849,7 @@ msgstr "" "Producir solo el objeto file, saltar el enlace; escrito en `-o` (véase " "[Banderas de compilador](flags.md))" -#: src/cli/commands.md:55 src/cli/flags.md:298 +#: src/cli/commands.md:55 src/cli/flags.md:320 msgid "`--keep-intermediates`" msgstr "`--keep-intermediates`" @@ -69068,7 +68857,7 @@ msgstr "`--keep-intermediates`" msgid "Keep `.o` and `.asm` files" msgstr "Conserva los archivos `.o` y `.asm`" -#: src/cli/commands.md:56 src/cli/commands.md:103 src/cli/flags.md:421 +#: src/cli/commands.md:56 src/cli/commands.md:103 src/cli/flags.md:443 msgid "`--enable-js-runtime`" msgstr "`--enable-js-runtime`" @@ -69076,7 +68865,7 @@ msgstr "`--enable-js-runtime`" msgid "Enable V8 JavaScript runtime fallback" msgstr "Habilita el fallback al runtime JavaScript de V8" -#: src/cli/commands.md:57 src/cli/commands.md:104 src/cli/flags.md:422 +#: src/cli/commands.md:57 src/cli/commands.md:104 src/cli/flags.md:444 msgid "`--enable-wasm-runtime`" msgstr "`--enable-wasm-runtime`" @@ -69088,8 +68877,8 @@ msgstr "" "Fuerza el enlazado del runtime host de WebAssembly wasmi (detectado " "automáticamente con el uso de `WebAssembly.*`)" -#: src/cli/commands.md:58 src/cli/commands.md:105 src/cli/flags.md:150 -#: src/cli/flags.md:423 +#: src/cli/commands.md:58 src/cli/commands.md:105 src/cli/flags.md:172 +#: src/cli/flags.md:445 msgid "`--type-check`" msgstr "`--type-check`" @@ -69097,16 +68886,16 @@ msgstr "`--type-check`" msgid "Enable type checking via tsgo" msgstr "Habilita la verificación de tipos mediante tsgo" -#: src/cli/commands.md:59 src/cli/flags.md:381 +#: src/cli/commands.md:59 src/cli/flags.md:403 msgid "`--minify`" msgstr "`--minify`" -#: src/cli/commands.md:59 src/cli/flags.md:381 +#: src/cli/commands.md:59 src/cli/flags.md:403 msgid "Minify and obfuscate output (auto-enabled for `--target web`)" msgstr "" "Minimiza y ofusca la salida (habilitado automáticamente para `--target web`)" -#: src/cli/commands.md:60 src/cli/flags.md:148 +#: src/cli/commands.md:60 src/cli/flags.md:170 msgid "`--app-bundle-id `" msgstr "`--app-bundle-id `" @@ -69114,7 +68903,7 @@ msgstr "`--app-bundle-id `" msgid "Bundle ID (required for widget targets)" msgstr "Bundle ID (requerido para targets de widget)" -#: src/cli/commands.md:61 src/cli/flags.md:149 +#: src/cli/commands.md:61 src/cli/flags.md:171 msgid "`--bundle-extensions `" msgstr "`--bundle-extensions `" @@ -69180,7 +68969,7 @@ msgstr "# Reenviar args a su programa" msgid "Argument / Flag" msgstr "Argumento / Flag" -#: src/cli/commands.md:94 src/cli/commands.md:271 src/cli/flags.md:100 +#: src/cli/commands.md:94 src/cli/commands.md:271 src/cli/flags.md:122 msgid "`ios`" msgstr "`ios`" @@ -69188,7 +68977,7 @@ msgstr "`ios`" msgid "Target iOS (device or simulator)" msgstr "Target iOS (dispositivo o simulador)" -#: src/cli/commands.md:95 src/cli/commands.md:272 src/cli/flags.md:102 +#: src/cli/commands.md:95 src/cli/commands.md:272 src/cli/flags.md:124 msgid "`visionos`" msgstr "`visionos`" @@ -69196,7 +68985,7 @@ msgstr "`visionos`" msgid "Target visionOS (device or simulator)" msgstr "Target visionOS (dispositivo o simulador)" -#: src/cli/commands.md:96 src/cli/commands.md:270 src/cli/flags.md:103 +#: src/cli/commands.md:96 src/cli/commands.md:270 src/cli/flags.md:125 msgid "`macos`" msgstr "`macos`" @@ -69204,7 +68993,7 @@ msgstr "`macos`" msgid "Target macOS (default on macOS host)" msgstr "Destino macOS (predeterminado en host macOS)" -#: src/cli/commands.md:97 src/cli/flags.md:120 +#: src/cli/commands.md:97 src/cli/flags.md:142 msgid "`web`" msgstr "`web`" @@ -69398,8 +69187,8 @@ msgstr "" #: src/cli/commands.md:152 msgid "" "Watches the project root (plus any `--watch` dirs) recursively using the " -"`notify` crate. A 300 ms **debounce** window collapses editor \"save " -"storms\" into one rebuild." +"`notify` crate. A 300 ms **debounce** window collapses editor \"save storms" +"\" into one rebuild." msgstr "" "Observa la raíz del proyecto (más cualquier directorio `--watch`) de forma " "recursiva usando el crate `notify`. Una ventana de **debounce** de 300 ms " @@ -69494,7 +69283,7 @@ msgstr "" "Preservación de estado entre recompilaciones / HMR — \"reinicio rápido\" es " "el objetivo honesto." -#: src/cli/commands.md:175 src/internals/garbage-collector.md:448 +#: src/cli/commands.md:175 src/internals/garbage-collector.md:444 msgid "check" msgstr "check" @@ -70216,10 +70005,41 @@ msgstr "" "para inspeccionar esa compilación exacta." #: src/cli/flags.md:7 +#, fuzzy +msgid "Build-location independent paths" +msgstr "Múltiples Cómputos Independientes" + +#: src/cli/flags.md:9 +msgid "" +"`--source-root ` (or `PERRY_SOURCE_ROOT=`) bakes module source " +"paths relative to `` instead of the directory the program was built in. " +"A module at `/a/b.js` sees `/a/b.js` in `__dirname`, `__filename` and " +"`import.meta.{url,dirname,filename,resolve}`, as the base of `require` / " +"`createRequire`, in `require.cache` keys, `require.resolve` results, stack-" +"frame labels and the text of runtime error messages. `--source-root-target " +"` (or `PERRY_SOURCE_ROOT_TARGET`) replaces the default `/` with " +"another absolute path, so `--source-root /build/app --source-root-target /" +"srv/app` bakes `/srv/app/a/b.js`." +msgstr "" + +#: src/cli/flags.md:20 +msgid "\"$PWD\"" +msgstr "" + +#: src/cli/flags.md:23 +msgid "" +"Compile-time file access, diagnostics and `--debug-symbols` are unchanged; " +"only values the program can observe are rebased. A module outside `` (a " +"linked package, say) keeps its real path and the compiler warns once per " +"such module. Without the option the output is byte-identical to before. Both " +"build and object caches include the effective root and target." +msgstr "" + +#: src/cli/flags.md:29 msgid "Build-time defines" msgstr "" -#: src/cli/flags.md:9 +#: src/cli/flags.md:31 msgid "" "`--define NAME=EXPR` is repeatable. It replaces unbound identifier reads and " "dotted names before lowering, including `typeof` guards, object shorthand, " @@ -70228,44 +70048,44 @@ msgid "" "quote a JavaScript string inside the shell argument:" msgstr "" -#: src/cli/flags.md:16 +#: src/cli/flags.md:38 msgid "'OPENCODE_VERSION=\"1.18.30\"'" msgstr "" -#: src/cli/flags.md:20 +#: src/cli/flags.md:42 msgid "The nearest `perry.json` above the entry can hold the same expressions:" msgstr "" -#: src/cli/flags.md:24 +#: src/cli/flags.md:46 #, fuzzy msgid "\"define\"" msgstr "`undefined`" -#: src/cli/flags.md:25 +#: src/cli/flags.md:47 msgid "\"OPENCODE_VERSION\"" msgstr "" -#: src/cli/flags.md:25 +#: src/cli/flags.md:47 msgid "\"\\\"1.18.30\\\"\"" msgstr "" -#: src/cli/flags.md:26 +#: src/cli/flags.md:48 msgid "\"OPENCODE_CHANNEL\"" msgstr "" -#: src/cli/flags.md:26 +#: src/cli/flags.md:48 msgid "\"\\\"latest\\\"\"" msgstr "" -#: src/cli/flags.md:27 +#: src/cli/flags.md:49 msgid "\"OPENCODE_MODELS_DEV\"" msgstr "" -#: src/cli/flags.md:27 +#: src/cli/flags.md:49 msgid "\"{\\\"provider\\\":{\\\"models\\\":{}}}\"" msgstr "" -#: src/cli/flags.md:32 +#: src/cli/flags.md:54 msgid "" "Strings in `perry.json` are JavaScript expressions, as in Bun's `define` " "map. Existing `package.json` `perry.define` strings keep their literal-" @@ -70274,7 +70094,7 @@ msgid "" "effective map." msgstr "" -#: src/cli/flags.md:37 +#: src/cli/flags.md:59 msgid "" "Large JSON-compatible literals (including defines) can stay serialized in " "the executable and use the built-in JSON parser when evaluated. Two " @@ -70282,19 +70102,19 @@ msgid "" "budgets:" msgstr "" -#: src/cli/flags.md:41 +#: src/cli/flags.md:63 msgid "" "Flat arrays of numbers, strings, booleans and null use this path at 1,024 " "value nodes or 64 KiB of string data." msgstr "" -#: src/cli/flags.md:43 +#: src/cli/flags.md:65 msgid "" "All other JSON-compatible object/array literals use it at 24,576 value nodes " "or 1 MiB of key/string data, to avoid excessive LLVM code generation." msgstr "" -#: src/cli/flags.md:46 +#: src/cli/flags.md:68 msgid "" "Nodes include the root and nested values; bytes count UTF-8 key/string " "content, excluding whitespace. Mid-size records keep ordinary lowering and " @@ -70304,7 +70124,7 @@ msgid "" "expression lowering." msgstr "" -#: src/cli/flags.md:52 +#: src/cli/flags.md:74 msgid "" "`import.meta.resolve(specifier[, parent])` returns a `file://` URL for " "filesystem targets and a `node:` specifier for supported builtins. Literal " @@ -70317,15 +70137,15 @@ msgid "" "their contents." msgstr "" -#: src/cli/flags.md:61 +#: src/cli/flags.md:83 msgid "For OpenCode v1.18.30, after installing its dependencies, run:" msgstr "" -#: src/cli/flags.md:64 +#: src/cli/flags.md:86 msgid "/path/to/models-snapshot.json" msgstr "" -#: src/cli/flags.md:69 +#: src/cli/flags.md:91 msgid "" "The harness merges release defines into `packages/opencode/perry.json`, " "reads the version from OpenCode's package, uses channel `latest`, imports " @@ -70336,47 +70156,47 @@ msgid "" "in issue #10107." msgstr "" -#: src/cli/flags.md:76 +#: src/cli/flags.md:98 msgid "Global Flags" msgstr "Flags globales" -#: src/cli/flags.md:78 +#: src/cli/flags.md:100 msgid "Available on all commands:" msgstr "Disponibles en todos los comandos:" -#: src/cli/flags.md:82 +#: src/cli/flags.md:104 msgid "`--format text\\|json`" msgstr "`--format text\\|json`" -#: src/cli/flags.md:82 +#: src/cli/flags.md:104 msgid "Output format (default: `text`)" msgstr "Formato de salida (por defecto: `text`)" -#: src/cli/flags.md:83 +#: src/cli/flags.md:105 msgid "`-v, --verbose`" msgstr "`-v, --verbose`" -#: src/cli/flags.md:83 +#: src/cli/flags.md:105 msgid "Increase verbosity (repeatable: `-v`, `-vv`, `-vvv`)" msgstr "Aumentar la verbosidad (repetible: `-v`, `-vv`, `-vvv`)" -#: src/cli/flags.md:84 +#: src/cli/flags.md:106 msgid "`-q, --quiet`" msgstr "`-q, --quiet`" -#: src/cli/flags.md:84 +#: src/cli/flags.md:106 msgid "Suppress non-error output" msgstr "Suprimir la salida que no sea de errores" -#: src/cli/flags.md:85 +#: src/cli/flags.md:107 msgid "`--no-color`" msgstr "`--no-color`" -#: src/cli/flags.md:85 +#: src/cli/flags.md:107 msgid "Disable ANSI color codes" msgstr "Deshabilitar los códigos de color ANSI" -#: src/cli/flags.md:87 +#: src/cli/flags.md:109 msgid "" "Successful compilation keeps Cargo, clang, Swift, and linker implementation " "details out of the default output. Pass `--verbose` to stream those " @@ -70389,235 +70209,235 @@ msgstr "" "herramienta interna falla, Perry muestra su salida capturada incluso sin `--" "verbose`." -#: src/cli/flags.md:92 +#: src/cli/flags.md:114 msgid "Compilation Targets" msgstr "Plataformas de compilación" -#: src/cli/flags.md:94 +#: src/cli/flags.md:116 msgid "Use `--target` to cross-compile:" msgstr "Usa `--target` para compilar de forma cruzada:" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "_(none)_" msgstr "_(ninguno)_" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "Current platform" msgstr "Plataforma actual" -#: src/cli/flags.md:98 +#: src/cli/flags.md:120 msgid "Default behavior" msgstr "Comportamiento predeterminado" -#: src/cli/flags.md:99 +#: src/cli/flags.md:121 msgid "`ios-simulator`" msgstr "`ios-simulator`" -#: src/cli/flags.md:99 +#: src/cli/flags.md:121 msgid "ARM64 simulator binary" msgstr "Binario del simulador ARM64" -#: src/cli/flags.md:100 +#: src/cli/flags.md:122 msgid "ARM64 device binary" msgstr "Binario de dispositivo ARM64" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "`visionos-simulator`" msgstr "`visionos-simulator`" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "visionOS Simulator" msgstr "Simulador de visionOS" -#: src/cli/flags.md:101 +#: src/cli/flags.md:123 msgid "Apple Vision Pro simulator build" msgstr "Compilación para el simulador de Apple Vision Pro" -#: src/cli/flags.md:102 +#: src/cli/flags.md:124 msgid "visionOS Device" msgstr "Dispositivo visionOS" -#: src/cli/flags.md:102 +#: src/cli/flags.md:124 msgid "Apple Vision Pro device build" msgstr "Compilación para el dispositivo Apple Vision Pro" -#: src/cli/flags.md:103 +#: src/cli/flags.md:125 msgid "Native macOS app bundle/build" msgstr "macOS app bundle/build" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "`tvos`" msgstr "`tvos`" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "tvOS Device" msgstr "Dispositivo tvOS" -#: src/cli/flags.md:104 +#: src/cli/flags.md:126 msgid "ARM64 Apple TV app" msgstr "ARM64 App Apple TV" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "`tvos-simulator`" msgstr "`tvos-simulator`" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "tvOS Simulator" msgstr "Simulador tvOS" -#: src/cli/flags.md:105 +#: src/cli/flags.md:127 msgid "Apple TV simulator app" msgstr "Aplicación simulador de Apple TV" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "`watchos`" msgstr "`watchos`" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "watchOS Device" msgstr "Dispositivo watchOS" -#: src/cli/flags.md:106 +#: src/cli/flags.md:128 msgid "Apple Watch app" msgstr "Aplicación Apple Watch" -#: src/cli/flags.md:107 +#: src/cli/flags.md:129 msgid "`watchos-simulator`" msgstr "`watchos-simulator`" -#: src/cli/flags.md:107 +#: src/cli/flags.md:129 msgid "Apple Watch simulator app" msgstr "App simulador de Apple Watch" -#: src/cli/flags.md:108 +#: src/cli/flags.md:130 msgid "ARM64 device build" msgstr "Construcción de dispositivos ARM64" -#: src/cli/flags.md:109 +#: src/cli/flags.md:131 msgid "`android-x86_64`" msgstr "`android-x86_64`" -#: src/cli/flags.md:109 +#: src/cli/flags.md:131 msgid "x86_64 emulator/device build" msgstr "Construcción x86_64 emulator/device" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "`ios-widget`" msgstr "`ios-widget`" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "iOS Widget" msgstr "Widget de iOS" -#: src/cli/flags.md:110 +#: src/cli/flags.md:132 msgid "WidgetKit extension (requires `--app-bundle-id`)" msgstr "Extensión WidgetKit (requiere `--app-bundle-id`)" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "`ios-widget-simulator`" msgstr "`ios-widget-simulator`" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "iOS Widget (Sim)" msgstr "Widget de iOS (Sim)" -#: src/cli/flags.md:111 +#: src/cli/flags.md:133 msgid "Widget for simulator" msgstr "Widget para el simulador" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "`watchos-widget`" msgstr "`watchos-widget`" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "watchOS Complication" msgstr "Complicación de watchOS" -#: src/cli/flags.md:112 +#: src/cli/flags.md:134 msgid "WidgetKit extension for Apple Watch" msgstr "Extensión WidgetKit para Apple Watch" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "`watchos-widget-simulator`" msgstr "`watchos-widget-simulator`" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "watchOS Widget (Sim)" msgstr "Widget de watchOS (Sim)" -#: src/cli/flags.md:113 +#: src/cli/flags.md:135 msgid "Widget for watchOS simulator" msgstr "Widget para el simulador de watchOS" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "`android-widget`" msgstr "`android-widget`" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "Android Widget" msgstr "Widget de Android" -#: src/cli/flags.md:114 +#: src/cli/flags.md:136 msgid "Android App Widget (AppWidgetProvider)" msgstr "Widget de aplicación Android (AppWidgetProvider)" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "`wearos-tile`" msgstr "`wearos-tile`" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "Wear OS Tile" msgstr "Tile de Wear OS" -#: src/cli/flags.md:115 +#: src/cli/flags.md:137 msgid "Wear OS Tile (TileService)" msgstr "Tile de Wear OS (TileService)" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "`wearos`" msgstr "`wearos`" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "Wear OS App" msgstr "Aplicación Wear OS" -#: src/cli/flags.md:116 +#: src/cli/flags.md:138 msgid "ARM64 Wear OS application shared library" msgstr "Biblioteca compartida de aplicaciones ARM64 Wear OS" -#: src/cli/flags.md:117 +#: src/cli/flags.md:139 msgid "`harmonyos`" msgstr "`harmonyos`" -#: src/cli/flags.md:117 +#: src/cli/flags.md:139 msgid "ARM64 OHOS HAP/native library" msgstr "Biblioteca ARM64 OHOS HAP/native" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "`harmonyos-simulator`" msgstr "`harmonyos-simulator`" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "HarmonyOS Simulator" msgstr "Simulador HarmonyOS" -#: src/cli/flags.md:118 +#: src/cli/flags.md:140 msgid "x86_64 OHOS simulator output" msgstr "Salida del simulador x86_64 OHOS" -#: src/cli/flags.md:119 +#: src/cli/flags.md:141 msgid "`wasm`" msgstr "`wasm`" -#: src/cli/flags.md:119 +#: src/cli/flags.md:141 msgid "Self-contained HTML with WASM or raw `.wasm` binary" msgstr "HTML autónomo con WASM o binario `.wasm` puro" -#: src/cli/flags.md:120 +#: src/cli/flags.md:142 msgid "Outputs HTML file with JS" msgstr "Genera un archivo HTML con JS" -#: src/cli/flags.md:121 +#: src/cli/flags.md:143 msgid "" "Win32/GDI executable for the native Windows host architecture; x64 when " "cross-compiling from another OS" @@ -70625,28 +70445,28 @@ msgstr "" "Win32/GDI ejecutable para la arquitectura de hosts nativa Windows; x64 " "cuando se combina desde otro sistema operativo" -#: src/cli/flags.md:122 +#: src/cli/flags.md:144 msgid "Explicit x64 MSVC target" msgstr "Objetivo explícito x64 MSVC" -#: src/cli/flags.md:123 +#: src/cli/flags.md:145 msgid "Windows ARM64" msgstr "Windows ARM64" -#: src/cli/flags.md:123 +#: src/cli/flags.md:145 msgid "Explicit ARM64 MSVC target (`windows-arm64` is accepted as an alias)" msgstr "" "Objetivo explícito de ARM64 MSVC (`windows-arm64` se acepta como alias)" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "`windows-winui`" msgstr "`windows-winui`" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "Windows (Fluent)" msgstr "Windows (Fluent)" -#: src/cli/flags.md:124 +#: src/cli/flags.md:146 msgid "" "Opt-in WinUI 3 / Fluent backend for core Perry controls. Requires the " "Windows App SDK 2.0 runtime; see [Windows](../platforms/windows.md#winui-3-" @@ -70656,103 +70476,103 @@ msgstr "" "Requiere el tiempo de ejecución de Windows App SDK 2.0; véase [Windows](../" "platforms/windows.md#winui-3-fluent-target)." -#: src/cli/flags.md:125 +#: src/cli/flags.md:147 msgid "GTK4 executable" msgstr "Ejecutable GTK4" -#: src/cli/flags.md:126 +#: src/cli/flags.md:148 msgid "Explicit x86_64 GNU target" msgstr "Objetivo explícito de GNU x86_64" -#: src/cli/flags.md:127 +#: src/cli/flags.md:149 msgid "`linux-aarch64` (`linux-arm64`)" msgstr "`linux-aarch64` (`linux-arm64`)" -#: src/cli/flags.md:127 +#: src/cli/flags.md:149 msgid "Explicit aarch64 GNU target" msgstr "Objetivo aarch64 GNU explícito" -#: src/cli/flags.md:128 +#: src/cli/flags.md:150 msgid "`linux-musl` (`linux-x86_64-musl`)" msgstr "`linux-musl` (`linux-x86_64-musl`)" -#: src/cli/flags.md:128 +#: src/cli/flags.md:150 msgid "Linux x64 (musl)" msgstr "Linux x64 (muso)" -#: src/cli/flags.md:128 src/cli/flags.md:129 +#: src/cli/flags.md:150 src/cli/flags.md:151 msgid "Fully static headless/server target" msgstr "Objetivo headless/server totalmente estático" -#: src/cli/flags.md:129 +#: src/cli/flags.md:151 msgid "`linux-aarch64-musl`" msgstr "`linux-aarch64-musl`" -#: src/cli/flags.md:129 +#: src/cli/flags.md:151 msgid "Linux arm64 (musl)" msgstr "Linux brazo64 (muso)" -#: src/cli/flags.md:131 +#: src/cli/flags.md:153 msgid "Output Types" msgstr "Tipos de salida" -#: src/cli/flags.md:133 +#: src/cli/flags.md:155 msgid "Use `--output-type` to change what's produced:" msgstr "Usa `--output-type` para cambiar lo que se produce:" -#: src/cli/flags.md:137 +#: src/cli/flags.md:159 msgid "`executable`" msgstr "`executable`" -#: src/cli/flags.md:137 +#: src/cli/flags.md:159 msgid "Standalone binary (default)" msgstr "Binario independiente (predeterminado)" -#: src/cli/flags.md:138 +#: src/cli/flags.md:160 msgid "`dylib`" msgstr "`dylib`" -#: src/cli/flags.md:138 +#: src/cli/flags.md:160 msgid "Shared library (`.dylib`/`.so`) for [plugins](../plugins/overview.md)" msgstr "" "Biblioteca compartida (`.dylib`/`.so`) para [plugins](../plugins/overview.md)" -#: src/cli/flags.md:139 +#: src/cli/flags.md:161 msgid "`staticlib`" msgstr "`staticlib`" -#: src/cli/flags.md:139 +#: src/cli/flags.md:161 msgid "" "Static archive (`.a`/`.lib`) exposing `perry_module_init` for a native host" msgstr "" "Archivo estático (`.a`/`.lib`) exponiendo `perry_module_init` para un " "anfitrión nativo" -#: src/cli/flags.md:141 +#: src/cli/flags.md:163 msgid "General Build and Platform Flags" msgstr "Banderas generales de construcción y plataforma" -#: src/cli/flags.md:145 +#: src/cli/flags.md:167 msgid "Output executable, bundle, shared library, archive, or object path." msgstr "" "Salida ejecutable, paquete, biblioteca compartida, archivo u ruta de objetos." -#: src/cli/flags.md:146 +#: src/cli/flags.md:168 #, fuzzy msgid "`--platform node\\|bun`" msgstr "`platform-core`" -#: src/cli/flags.md:146 +#: src/cli/flags.md:168 msgid "" "Select the JavaScript host global surface. `node` is the default; `bun` " "installs a real, stable `Bun === globalThis.Bun` namespace." msgstr "" -#: src/cli/flags.md:147 +#: src/cli/flags.md:169 msgid "`--libc glibc\\|musl`" msgstr "`--libc glibc\\|musl`" -#: src/cli/flags.md:147 +#: src/cli/flags.md:169 msgid "" "Select Linux libc/linkage; `musl` upgrades the corresponding Linux target to " "a fully static headless build." @@ -70760,20 +70580,20 @@ msgstr "" "Seleccione Linux libc/linkage; `musl` actualiza el objetivo Linux " "correspondiente a una construcción totalmente estática sin cabeza." -#: src/cli/flags.md:148 +#: src/cli/flags.md:170 msgid "Bundle identifier required by home-screen widget targets." msgstr "" "Identificador de paquetes requerido por los objetivos del widget de pantalla " "doméstica." -#: src/cli/flags.md:149 +#: src/cli/flags.md:171 msgid "" "Discover and statically bundle native extension packages from a directory." msgstr "" "Descubra y empaquete estáticamente paquetes de extensiones nativas desde un " "directorio." -#: src/cli/flags.md:150 +#: src/cli/flags.md:172 msgid "" "Run the native TypeScript checker (`@typescript/native-preview`) before " "codegen." @@ -70781,22 +70601,22 @@ msgstr "" "Ejecute el verificador nativo TypeScript (`@typescript/native-preview`) " "antes del códice." -#: src/cli/flags.md:151 +#: src/cli/flags.md:173 msgid "`--min-windows-version 7\\|8\\|10`" msgstr "`--min-windows-version 7\\|8\\|10`" -#: src/cli/flags.md:151 +#: src/cli/flags.md:173 msgid "" "Set the Windows PE compatibility floor; ignored for non-Windows targets." msgstr "" "Establecer el piso de compatibilidad Windows PE; ignorado para objetivos no " "Windows." -#: src/cli/flags.md:152 +#: src/cli/flags.md:174 msgid "`--windows-subsystem auto\\|console\\|windows`" msgstr "`--windows-subsystem auto\\|console\\|windows`" -#: src/cli/flags.md:152 +#: src/cli/flags.md:174 msgid "" "Select the Windows PE subsystem instead of relying on import-based auto " "detection." @@ -70804,11 +70624,11 @@ msgstr "" "Seleccione el subsistema Windows PE en lugar de confiar en la autodetección " "basada en la importación." -#: src/cli/flags.md:153 +#: src/cli/flags.md:175 msgid "`--skip-swift-build`" msgstr "`--skip-swift-build`" -#: src/cli/flags.md:153 +#: src/cli/flags.md:175 msgid "" "For Apple widget targets, emit SwiftUI source and metadata without invoking " "`swiftc`." @@ -70816,11 +70636,11 @@ msgstr "" "Para los objetivos del widget de Apple, emita fuente y metadatos SwiftUI sin " "invocar `swiftc`." -#: src/cli/flags.md:154 +#: src/cli/flags.md:176 msgid "`--p12-keystore ` / `--p12-password `" msgstr "`--p12-keystore ` / `--p12-password `" -#: src/cli/flags.md:154 +#: src/cli/flags.md:176 msgid "" "Select HarmonyOS HAP signing material. Prefer the corresponding environment/" "config sources for secrets." @@ -70828,36 +70648,36 @@ msgstr "" "Seleccione el material de firma HarmonyOS HAP. Prefiere las fuentes " "environment/config correspondientes para secretos." -#: src/cli/flags.md:155 +#: src/cli/flags.md:177 msgid "`--harmonyos-cert `" msgstr "`--harmonyos-cert `" -#: src/cli/flags.md:155 +#: src/cli/flags.md:177 msgid "HarmonyOS application certificate chain." msgstr "Cadena de certificados de solicitud HarmonyOS." -#: src/cli/flags.md:156 +#: src/cli/flags.md:178 msgid "`--harmonyos-profile `" msgstr "`--harmonyos-profile `" -#: src/cli/flags.md:156 +#: src/cli/flags.md:178 msgid "HarmonyOS signed provisioning profile." msgstr "Perfil de aprovisionamiento firmado por HarmonyOS." -#: src/cli/flags.md:157 +#: src/cli/flags.md:179 msgid "`--harmonyos-key-alias `" msgstr "`--harmonyos-key-alias `" -#: src/cli/flags.md:157 +#: src/cli/flags.md:179 msgid "HarmonyOS keystore alias; defaults to `debugKey`." msgstr "HarmonyOS keystore alias; por defecto a `debugKey`." -#: src/cli/flags.md:159 +#: src/cli/flags.md:181 #, fuzzy msgid "Bun platform mode" msgstr "`platform` — módulo" -#: src/cli/flags.md:161 +#: src/cli/flags.md:183 msgid "" "`perry compile app.ts --platform bun` exposes Perry's implemented Bun APIs " "through one real global namespace. Direct calls, extracted or destructured " @@ -70867,7 +70687,7 @@ msgid "" "direct Bun shims and imports from `\"bun\"` continue to work." msgstr "" -#: src/cli/flags.md:168 +#: src/cli/flags.md:190 msgid "" "In this mode, `Bun.version` is Perry's runtime crate version, not a claimed " "Bun release number. `Bun.isStandaloneExecutable` is always `true`, because " @@ -70876,7 +70696,7 @@ msgid "" "compatibility error." msgstr "" -#: src/cli/flags.md:173 +#: src/cli/flags.md:195 msgid "" "`Bun.spawn` supports the command-array and `{ cmd, ...options }` forms, " "piped output consumers such as `child.stdout.text()`, lifecycle controls, " @@ -70885,7 +70705,7 @@ msgid "" "are not yet available on Windows." msgstr "" -#: src/cli/flags.md:179 +#: src/cli/flags.md:201 msgid "" "Web APIs otherwise keep Node's behavior; the one runtime difference in this " "mode is that a `Response` may carry a body under a null-body status (204, " @@ -70894,11 +70714,11 @@ msgid "" "Node's `TypeError: Response constructor: Invalid response status code 204`." msgstr "" -#: src/cli/flags.md:186 +#: src/cli/flags.md:208 msgid "Embedding Assets" msgstr "Activo de inserción" -#: src/cli/flags.md:188 +#: src/cli/flags.md:210 msgid "" "Bake static files (an SPA `dist/`, images, JSON, fonts, …) into the " "standalone executable so it runs with no external files on disk (#5731)." @@ -70907,11 +70727,11 @@ msgstr "" "el ejecutable independiente para que se ejecute sin archivos externos en el " "disco (#5731)." -#: src/cli/flags.md:193 +#: src/cli/flags.md:215 msgid "`--embed `" msgstr "`--embed `" -#: src/cli/flags.md:193 +#: src/cli/flags.md:215 msgid "" "Embed a file, directory, or `*`/`**` glob (relative to the project root). " "Repeatable. Merged with `perry.embed` (package.json) and `[compile] embed` " @@ -70921,23 +70741,23 @@ msgstr "" "proyecto). Repeatable. Combinado con `perry.embed` (package.json) y " "`[compile] embed` (perry.toml)." -#: src/cli/flags.md:194 +#: src/cli/flags.md:216 #, fuzzy msgid "`--bunfs-root `" msgstr "`unrooted`" -#: src/cli/flags.md:194 +#: src/cli/flags.md:216 msgid "" "Map an extracted Bun standalone root to `/$bunfs/root/`; mapped module paths " "resolve natively and literal file paths remain readable from the relocated " "executable." msgstr "" -#: src/cli/flags.md:195 +#: src/cli/flags.md:217 msgid "`--asset-module `" msgstr "`--asset-module `" -#: src/cli/flags.md:195 +#: src/cli/flags.md:217 msgid "" "Generate a virtual module whose default export maps every file below `dir` " "to a stable `$perryfs` handle. Repeatable; source maps are excluded." @@ -70946,26 +70766,26 @@ msgstr "" "por debajo de `dir` a un mango estable de `$perryfs`. Repetible; se excluyen " "los mapas de origen." -#: src/cli/flags.md:199 +#: src/cli/flags.md:221 msgid "\"./dist/**\"" msgstr "\"./dist/**\"" -#: src/cli/flags.md:200 +#: src/cli/flags.md:222 #, fuzzy msgid "# serves dist/ from memory — no dist/ folder needed\n" msgstr "# sirve dist/ de memoria — no se necesita dist/ carpeta" -#: src/cli/flags.md:203 +#: src/cli/flags.md:225 msgid "Embedded files are reachable at runtime three ways:" msgstr "" "Los archivos empotrados se pueden alcanzar en el tiempo de ejecución de tres " "maneras:" -#: src/cli/flags.md:205 +#: src/cli/flags.md:227 msgid "" "```typescript,no-test\n" -"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from " -"\"perry\";\n" +"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from \"perry" +"\";\n" "import { readFileSync } from \"fs\";\n" "\n" "for (const f of embeddedFiles()) {\n" @@ -70979,8 +70799,8 @@ msgid "" "```" msgstr "" "```typescript,no-test\n" -"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from " -"\"perry\";\n" +"import { embeddedFiles, readEmbedded, isStandaloneExecutable } from \"perry" +"\";\n" "import { readFileSync } from \"fs\";\n" "\n" "for (const f of embeddedFiles()) {\n" @@ -70993,7 +70813,7 @@ msgstr "" "const html = readFileSync(\"$perryfs/dist/index.html\", \"utf8\");\n" "```" -#: src/cli/flags.md:218 +#: src/cli/flags.md:240 msgid "" "`embeddedFiles()` is a function (not a bare value like Bun's " "`embeddedFiles`) so that array methods dispatch on its result. " @@ -71005,7 +70825,7 @@ msgstr "" "`readEmbedded(path)` y `node:fs` aceptan la ruta virtual `$perryfs/` o " "la clave relativa a la integración." -#: src/cli/flags.md:222 +#: src/cli/flags.md:244 msgid "" "Bun-compatible file-loader imports embed the referenced binary automatically " "and bind the default import to its `$perryfs` path:" @@ -71013,7 +70833,7 @@ msgstr "" "Bun-compatible file-loader imports embed the referenced binary automatically " "and bind the default import to its `$perryfs` path:" -#: src/cli/flags.md:225 +#: src/cli/flags.md:247 msgid "" "```typescript,no-test\n" "import sound from \"./sound.mp3\" with { type: \"file\" };\n" @@ -71023,13 +70843,13 @@ msgstr "" "import sound from \"./sound.mp3\" with { type: \"file\" };\n" "```" -#: src/cli/flags.md:229 +#: src/cli/flags.md:251 msgid "" "For source extracted from a Bun standalone executable, mount its extracted " "`root/` directory at Bun's original virtual prefix:" msgstr "" -#: src/cli/flags.md:236 +#: src/cli/flags.md:258 msgid "" "Static imports, re-exports, literal dynamic imports, literal `require()`, " "and `import.meta.require()` calls below `/$bunfs/root/` resolve against that " @@ -71041,7 +70861,7 @@ msgid "" "symlink is needed." msgstr "" -#: src/cli/flags.md:245 +#: src/cli/flags.md:267 msgid "" "Bun bundles can load compiled chunks synchronously with `import.meta." "require(\"./chunk.js\")` or `import.meta[\"require\"](\"./chunk.js\")`. Both " @@ -71051,7 +70871,7 @@ msgid "" "call." msgstr "" -#: src/cli/flags.md:251 +#: src/cli/flags.md:273 msgid "" "Some build pipelines inject a generated module rather than writing it into " "the source checkout. Reproduce that file-map step with `--asset-module`. " @@ -71065,7 +70885,7 @@ msgstr "" "borde `{ type: \"file\" }` y mantiene la fuente generada en su caché en " "lugar de modificar la caja." -#: src/cli/flags.md:256 +#: src/cli/flags.md:278 msgid "" "For the pinned OpenCode source graph, build the upstream web UI first, then " "compile from `packages/opencode`'s package root:" @@ -71074,7 +70894,7 @@ msgstr "" "usuario de la web de origen y luego compile a partir de la raíz de paquetes " "de `packages/opencode`:" -#: src/cli/flags.md:266 +#: src/cli/flags.md:288 msgid "" "The first command is the upstream preparation step; Perry reproduces only " "the deterministic `opencode-web-ui.gen.ts` file map. A missing/empty `dist` " @@ -71089,7 +70909,7 @@ msgstr "" "resolver. Ejecute el comando de preparación para cada revisión de origen " "para que las entradas generadas no puedan ser rancias." -#: src/cli/flags.md:272 +#: src/cli/flags.md:294 msgid "" "Every compile also writes `/assets.json`, a deterministic report " "of text, JSON, file-loader, WASM, and explicitly embedded assets. It records " @@ -71102,7 +70922,7 @@ msgstr "" "relacionada con el proyecto, el tamaño de los bytes, el digest SHA-256 y el " "módulo de activos generadores, cuando proceda." -#: src/cli/flags.md:277 +#: src/cli/flags.md:299 msgid "" "**Note** `node:fs` consults the embedded registry _before_ disk, and a bare " "embed-relative key matches too — so `readFileSync(\"dist/index.html\")` " @@ -71117,7 +70937,7 @@ msgstr "" "real en el disco por ruta absoluta, y utilice el formulario `$perryfs/" "` explícito cuando se refiere específicamente a la copia incrustada." -#: src/cli/flags.md:284 +#: src/cli/flags.md:306 msgid "" "Embedding supports host builds on macOS, Linux, and Windows. Windows uses " "LLVM clang to emit a COFF registration object; set `PERRY_LLVM_CLANG` when " @@ -71129,19 +70949,19 @@ msgstr "" "`PERRY_LLVM_CLANG` cuando el clang correspondiente no es el primero en " "`PATH`. Actualmente no se admite la integración de objetivos cruzados." -#: src/cli/flags.md:289 +#: src/cli/flags.md:311 msgid "Debug Flags" msgstr "Flags de depuración" -#: src/cli/flags.md:293 +#: src/cli/flags.md:315 msgid "Print HIR (intermediate representation) to stdout" msgstr "Imprimir el HIR (representación intermedia) en stdout" -#: src/cli/flags.md:294 +#: src/cli/flags.md:316 msgid "`--trace `" msgstr "`--trace `" -#: src/cli/flags.md:294 +#: src/cli/flags.md:316 msgid "" "Dump IR at one or more pipeline stages. Comma-separated: `hir` (post-" "transform HIR), `llvm` (per-module `.ll` into `.perry-trace/llvm/`), or `all`" @@ -71150,11 +70970,11 @@ msgstr "" "(HIR post-transformación), `llvm` (`.ll` por módulo en `.perry-trace/llvm/" "`), o `all`" -#: src/cli/flags.md:295 +#: src/cli/flags.md:317 msgid "`--focus `" msgstr "`--focus `" -#: src/cli/flags.md:295 +#: src/cli/flags.md:317 msgid "" "Restrict `--trace hir` to functions/methods/classes whose name contains " "`NAME`, suppressing import/export/init noise. Implies `--trace hir` if no " @@ -71164,7 +70984,7 @@ msgstr "" "`NAME`, suprimiendo el ruido de imports/exports/init. Implica `--trace hir` " "si no se especifica ninguna etapa" -#: src/cli/flags.md:296 +#: src/cli/flags.md:318 msgid "" "Produce `.o` object file(s) only, skip linking. The objects are written to `-" "o` — verbatim for a single-module program, otherwise into `-o`'s directory " @@ -71178,11 +70998,11 @@ msgstr "" "que un `-o` no puede nombrar varios archivos. Sin `-o` que aterrizan en el " "directorio actual. Cada ruta se imprime como `Wrote object file: `" -#: src/cli/flags.md:297 +#: src/cli/flags.md:319 msgid "`--no-codegen`" msgstr "`--no-codegen`" -#: src/cli/flags.md:297 +#: src/cli/flags.md:319 msgid "" "Skip the `package.json` `perry.codegen` build-time steps (also " "`PERRY_SKIP_CODEGEN=1`). See [Project Configuration](../getting-started/" @@ -71192,15 +71012,15 @@ msgstr "" "`PERRY_SKIP_CODEGEN=1`). Ver [Configuración del proyecto](../getting-started/" "project-config.md)" -#: src/cli/flags.md:298 +#: src/cli/flags.md:320 msgid "Keep `.o` and `.asm` intermediate files" msgstr "Conservar los archivos intermedios `.o` y `.asm`" -#: src/cli/flags.md:299 +#: src/cli/flags.md:321 msgid "`--debug-symbols`" msgstr "`--debug-symbols`" -#: src/cli/flags.md:299 +#: src/cli/flags.md:321 msgid "" "Retain symbols/DWARF (and emit a Windows PDB) instead of stripping the " "result, and emit a DWARF **line table** mapping machine code back to your `." @@ -71208,11 +71028,11 @@ msgid "" "(debugging.md)." msgstr "" -#: src/cli/flags.md:300 +#: src/cli/flags.md:322 msgid "`--function-source `" msgstr "" -#: src/cli/flags.md:300 +#: src/cli/flags.md:322 msgid "" "How much `Function.prototype.toString` source to keep. `full` (default) " "stores interned original source (nested functions share one module blob). " @@ -71221,33 +71041,33 @@ msgid "" "`PERRY_FUNCTION_SOURCE`." msgstr "" -#: src/cli/flags.md:301 +#: src/cli/flags.md:323 msgid "`--no-cache`" msgstr "`--no-cache`" -#: src/cli/flags.md:301 +#: src/cli/flags.md:323 msgid "" "Disable the per-module object cache for this build; also `PERRY_NO_CACHE=1`." msgstr "" "Desactivar la caché de objetos por módulo para esta compilación; también " "`PERRY_NO_CACHE=1`." -#: src/cli/flags.md:302 +#: src/cli/flags.md:324 msgid "`--cache-dir `" msgstr "`--cache-dir `" -#: src/cli/flags.md:302 +#: src/cli/flags.md:324 msgid "" "Override the machine-local cache root; see [Cache Directory](cache-dir.md)." msgstr "" "Anular la raíz de caché local de la máquina; véase [Directorio de caché]" "(cache-dir.md)." -#: src/cli/flags.md:303 +#: src/cli/flags.md:325 msgid "`--verify-native-regions`" msgstr "`--verify-native-regions`" -#: src/cli/flags.md:303 +#: src/cli/flags.md:325 msgid "" "Run native-representation lowering invariants and force codegen instead of " "cache reuse." @@ -71255,30 +71075,30 @@ msgstr "" "Ejecute representación nativa reduciendo invariantes y forzar la " "reutilización del códice en lugar de la caché." -#: src/cli/flags.md:304 +#: src/cli/flags.md:326 msgid "`--disable-buffer-fast-path`" msgstr "`--disable-buffer-fast-path`" -#: src/cli/flags.md:304 +#: src/cli/flags.md:326 msgid "Disable native Buffer/Uint8Array load/store lowering for A/B diagnosis." msgstr "Desactivar Buffer/Uint8Array load/store para el diagnóstico de A/B." -#: src/cli/flags.md:305 +#: src/cli/flags.md:327 msgid "`--explain-lowering`" msgstr "`--explain-lowering`" -#: src/cli/flags.md:305 +#: src/cli/flags.md:327 msgid "" "Emit a type-lowering evidence report; implies native-region verification." msgstr "" "Emitir un informe de evidencia tipo-reducción; implica la verificación de la " "región nativa." -#: src/cli/flags.md:306 +#: src/cli/flags.md:328 msgid "`--opt-report[=json]`" msgstr "`--opt-report[=json]`" -#: src/cli/flags.md:306 +#: src/cli/flags.md:328 msgid "" "Report which values Perry could **not** statically type, why, and whether " "you can fix it. Text by default; `--opt-report=json` emits a stable schema " @@ -71289,11 +71109,11 @@ msgstr "" "estable para herramientas. También configurable a través de " "`PERRY_OPT_REPORT=1`" -#: src/cli/flags.md:307 +#: src/cli/flags.md:329 msgid "`--statepoint-report[=json]`" msgstr "`--statepoint-report[=json]`" -#: src/cli/flags.md:307 +#: src/cli/flags.md:329 msgid "" "Report native-stack GC root pressure: calls with live roots, audited non-" "collecting calls omitted, relocations, plain-map fallbacks, and live-root " @@ -71301,7 +71121,7 @@ msgid "" "plain stack-map and explicit-bridge modes it also named are gone)" msgstr "" -#: src/cli/flags.md:309 +#: src/cli/flags.md:331 msgid "" "The `--trace`/`--focus` pair localizes \"compiled to the wrong thing\" bugs: " "`perry compile foo.ts --trace hir,llvm --focus parseRow` dumps just the " @@ -71310,20 +71130,20 @@ msgid "" "llvm` forces a full recompile (the object cache otherwise skips codegen for " "unchanged modules, leaving the trace dir empty)." msgstr "" -"El par `--trace`/`--focus` localiza los errores de \"compiló algo " -"incorrecto\": `perry compile foo.ts --trace hir,llvm --focus parseRow` " -"vuelca únicamente el HIR generado de la función `parseRow` y el IR de LLVM " -"del módulo, para que puedas ver en qué etapa se corrompió sin necesidad de " +"El par `--trace`/`--focus` localiza los errores de \"compiló algo incorrecto" +"\": `perry compile foo.ts --trace hir,llvm --focus parseRow` vuelca " +"únicamente el HIR generado de la función `parseRow` y el IR de LLVM del " +"módulo, para que puedas ver en qué etapa se corrompió sin necesidad de " "recorrer un volcado del módulo completo. `--trace llvm` fuerza una " "recompilación completa (la caché de objetos de lo contrario omite la " "generación de código para módulos sin cambios, dejando el directorio de " "trazas vacío)." -#: src/cli/flags.md:316 +#: src/cli/flags.md:338 msgid "`--opt-report` — why a value stayed boxed" msgstr "`--opt-report` — Por qué se mantuvo en caja un valor" -#: src/cli/flags.md:318 +#: src/cli/flags.md:340 msgid "" "Perry's speed comes from proving static types and selecting unboxed " "representations (see the [representation-selection RFC](https://github.com/" @@ -71341,11 +71161,11 @@ msgstr "" "representación-selección de los comentarios LLVM de `-Rpass-missed`: imprime " "lo que fue probado, lo que no lo fue, y qué regla hizo la llamada." -#: src/cli/flags.md:345 +#: src/cli/flags.md:367 msgid "Each denied value carries:" msgstr "Cada valor negado lleva:" -#: src/cli/flags.md:347 +#: src/cli/flags.md:369 msgid "" "**Position** — `local`, `param`, `return`, `field`, or `alloc-site` (an " "object literal that is never bound to a local, the `.map(x => ({...}))` " @@ -71355,7 +71175,7 @@ msgstr "" "objeto literal que nunca está ligado a un local, el idioma `.map(x => " "({...}))`)." -#: src/cli/flags.md:350 +#: src/cli/flags.md:372 msgid "" "**The rule that denied it**, in the collector's own numbering, so you can " "check it against the named source file." @@ -71363,7 +71183,7 @@ msgstr "" "**La regla que lo negó**, en la propia numeración del coleccionista, para " "que pueda comprobarlo en el archivo fuente nombrado." -#: src/cli/flags.md:352 +#: src/cli/flags.md:374 msgid "" "**An actionability tier**: _Fixable in your source_ (stop reassigning it, " "don't capture it in a closure), _Inherently polymorphic_ (correctly boxed — " @@ -71375,7 +71195,7 @@ msgstr "" "(correctamente boxeado — sin acción), o _Perry limitation_ (no su código; " "nombre el problema de seguimiento donde existe)." -#: src/cli/flags.md:356 +#: src/cli/flags.md:378 msgid "" "**A hotness proxy.** Loop-nesting depth **and**, separately, whether the " "enclosing region is an iterating builtin's callback. The two are reported as " @@ -71390,7 +71210,7 @@ msgstr "" "vez por elemento, por lo que la profundidad del bucle solo lo clasifica como " "último. Tampoco lo es un perfil, son proxies estáticos." -#: src/cli/flags.md:362 +#: src/cli/flags.md:384 msgid "" "Wins are reported alongside the misses, so the ratio is visible rather than " "just the complaints." @@ -71398,7 +71218,7 @@ msgstr "" "Las ganancias se reportan junto a las fallas, por lo que la proporción es " "visible en lugar de sólo las quejas." -#: src/cli/flags.md:365 +#: src/cli/flags.md:387 msgid "" "**It is observational only** — emitted code is byte-identical with the flag " "on and off. Like `--trace llvm`, it disables build and object cache reuse " @@ -71413,7 +71233,7 @@ msgstr "" "nada que informar. El informe va a **stderr**, por lo que nunca se mezcla en " "una carga útil `--format json` o la salida canalizada de su programa." -#: src/cli/flags.md:371 +#: src/cli/flags.md:393 msgid "" "Values are identified by **function and binding name**: Perry's HIR keeps " "names through lowering but drops source positions, so there is no `file:" @@ -71428,15 +71248,15 @@ msgstr "" "un cheque CI barato contra una representación que retrocede silenciosamente " "a cero." -#: src/cli/flags.md:377 +#: src/cli/flags.md:399 msgid "Output Optimization" msgstr "Optimización de salida" -#: src/cli/flags.md:382 +#: src/cli/flags.md:404 msgid "`--march `" msgstr "`--march `" -#: src/cli/flags.md:382 +#: src/cli/flags.md:404 msgid "" "CPU baseline for the generated machine code: an LLVM CPU name (`x86-64-v2`, " "`x86-64-v3`, `znver2`, `apple-m1`, …), `native` (tune to the build machine — " @@ -71459,11 +71279,11 @@ msgstr "" "para `generic`. Aplica al código de aplicación y la reconstrucción de " "runtime/stdlib optimizada automáticamente." -#: src/cli/flags.md:383 +#: src/cli/flags.md:405 msgid "`--features `" msgstr "`--features `" -#: src/cli/flags.md:383 +#: src/cli/flags.md:405 msgid "" "Define comma-separated compile-time `__feature_NAME__` constants for dead-" "code elimination." @@ -71471,11 +71291,11 @@ msgstr "" "Definir las constantes `__feature_NAME__` de tiempo de compilación separadas " "por comas para la eliminación de código muerto." -#: src/cli/flags.md:384 +#: src/cli/flags.md:406 msgid "`--no-auto-optimize`" msgstr "`--no-auto-optimize`" -#: src/cli/flags.md:384 +#: src/cli/flags.md:406 msgid "" "Use the prebuilt full runtime/stdlib instead of rebuilding the smallest " "reachable feature set." @@ -71483,50 +71303,50 @@ msgstr "" "Utilice el runtime/stdlib completo preconstruido en lugar de reconstruir el " "conjunto de características más pequeño accesible." -#: src/cli/flags.md:385 src/cli/fast-math.md:13 src/cli/fast-math.md:108 +#: src/cli/flags.md:407 src/cli/fast-math.md:13 src/cli/fast-math.md:108 #: src/cli/fast-math.md:132 msgid "`--fast-math`" msgstr "`--fast-math`" -#: src/cli/flags.md:385 +#: src/cli/flags.md:407 msgid "Permit floating-point reassociation; see [Fast-math](fast-math.md)." msgstr "" "Permitir la reasociación de puntos flotantes; véase [Fast-math](fast-math." "md)." -#: src/cli/flags.md:386 +#: src/cli/flags.md:408 msgid "`--fp-contract off\\|on\\|fast`" msgstr "`--fp-contract off\\|on\\|fast`" -#: src/cli/flags.md:386 +#: src/cli/flags.md:408 msgid "Control fused multiply-add contraction separately from reassociation." msgstr "" "Control fusionado multi-añadir la contracción por separado de la " "reasignación." -#: src/cli/flags.md:387 +#: src/cli/flags.md:409 msgid "`--record-function-order`" msgstr "" -#: src/cli/flags.md:387 +#: src/cli/flags.md:409 msgid "" -"Build a binary that appends each compiled function's name to " -"`$PERRY_FUNCTION_ORDER_OUT` the first time it runs; see [Function Order]" +"Build a binary that appends each compiled function's name to `" +"$PERRY_FUNCTION_ORDER_OUT` the first time it runs; see [Function Order]" "(function-order.md)." msgstr "" -#: src/cli/flags.md:388 +#: src/cli/flags.md:410 msgid "`--function-order `" msgstr "" -#: src/cli/flags.md:388 +#: src/cli/flags.md:410 msgid "" "Place the functions a recording listed first and together, in list order, so " "startup code shares pages; unknown names are ignored. See [Function Order]" "(function-order.md)." msgstr "" -#: src/cli/flags.md:390 +#: src/cli/flags.md:412 msgid "" "Minification strips comments, collapses whitespace, and mangles local " "variable/parameter/non-exported function names for smaller output." @@ -71535,11 +71355,11 @@ msgstr "" "nombres de variables locales, parámetros y funciones no exportadas para " "obtener una salida más pequeña." -#: src/cli/flags.md:392 +#: src/cli/flags.md:414 msgid "Size-optimized builds" msgstr "Estructuras optimizadas para el tamaño" -#: src/cli/flags.md:394 +#: src/cli/flags.md:416 msgid "" "For the smallest possible native binaries, two opt-in environment variables " "rebuild the auto-optimized runtime/stdlib archives tuned for size instead of " @@ -71552,11 +71372,11 @@ msgstr "" "(requieren una compra de espacio de trabajo Perry, como el resto de auto-" "optimizar):" -#: src/cli/flags.md:401 +#: src/cli/flags.md:423 msgid "`PERRY_SIZE_OPT=z` (or `s`)" msgstr "`PERRY_SIZE_OPT=z` (or `s`)" -#: src/cli/flags.md:401 +#: src/cli/flags.md:423 msgid "" "Rebuild the runtime/stdlib at `-C opt-level=z`/`s` instead of `3`. Roughly " "halves a small program's binary at some runtime-speed cost (compute-heavy " @@ -71569,11 +71389,11 @@ msgstr "" "pueden correr ~2-3× más lento). Los archivos normales y optimizados en " "tamaño se guardan en caché de forma independiente." -#: src/cli/flags.md:402 +#: src/cli/flags.md:424 msgid "`PERRY_SIZE_LTO=fat`" msgstr "`PERRY_SIZE_LTO=fat`" -#: src/cli/flags.md:402 +#: src/cli/flags.md:424 msgid "" "Additionally run whole-archive fat LTO over the rebuilt archives (slower " "rebuild, smaller binary). Only meaningful together with `PERRY_SIZE_OPT`." @@ -71582,11 +71402,11 @@ msgstr "" "reconstruidos (reconstrucción lenta, binario más pequeño). Sólo significante " "junto con `PERRY_SIZE_OPT`." -#: src/cli/flags.md:403 +#: src/cli/flags.md:425 msgid "`PERRY_SIZE_PANIC=abort-immediate`" msgstr "`PERRY_SIZE_PANIC=abort-immediate`" -#: src/cli/flags.md:403 +#: src/cli/flags.md:425 msgid "" "Additionally rebuild the Rust standard library with the `immediate-abort` " "panic strategy when auto-optimize proves that no UI/thread/plugin callback " @@ -71605,7 +71425,7 @@ msgstr "" "luego se aborta sin una retrotraza simbolizada — JS `throw`/`catch`, " "`finally`, las pilas de error y `uncaughtException` no se ven afectadas." -#: src/cli/flags.md:405 +#: src/cli/flags.md:427 msgid "" "A `console.log` hello world on macOS arm64: 4.03 MB default → 2.17 MB with " "`PERRY_SIZE_OPT=z PERRY_SIZE_LTO=fat` → 1.87 MB adding " @@ -71617,15 +71437,15 @@ msgstr "" "`PERRY_SIZE_PANIC=abort-immediate`. Los programas que usan más tiempo de " "ejecución se reducen menos, proporcionalmente." -#: src/cli/flags.md:410 +#: src/cli/flags.md:432 msgid "Testing Flags" msgstr "Flags de pruebas" -#: src/cli/flags.md:414 +#: src/cli/flags.md:436 msgid "`--enable-geisterhand`" msgstr "`--enable-geisterhand`" -#: src/cli/flags.md:414 +#: src/cli/flags.md:436 msgid "" "Embed the [Geisterhand](../testing/geisterhand.md) HTTP server for " "programmatic UI testing (default port 7676)" @@ -71633,26 +71453,26 @@ msgstr "" "Incrustar el servidor HTTP [Geisterhand](../testing/geisterhand.md) para " "pruebas de UI programáticas (puerto predeterminado 7676)" -#: src/cli/flags.md:415 +#: src/cli/flags.md:437 msgid "`--geisterhand-port `" msgstr "`--geisterhand-port `" -#: src/cli/flags.md:415 +#: src/cli/flags.md:437 msgid "" "Set a custom port for the Geisterhand server (implies `--enable-geisterhand`)" msgstr "" "Establecer un puerto personalizado para el servidor Geisterhand (implica `--" "enable-geisterhand`)" -#: src/cli/flags.md:417 +#: src/cli/flags.md:439 msgid "Runtime Flags" msgstr "Flags de runtime" -#: src/cli/flags.md:421 +#: src/cli/flags.md:443 msgid "Enable V8 JavaScript runtime for unsupported npm packages" msgstr "Habilitar el runtime de JavaScript V8 para paquetes npm no compatibles" -#: src/cli/flags.md:422 +#: src/cli/flags.md:444 msgid "" "Force-link the wasmi WebAssembly host runtime (auto-detected when " "`WebAssembly.*` is referenced; needed only when loading via dlopen / FFI " @@ -71662,64 +71482,43 @@ msgstr "" "cuando se referencia `WebAssembly.*`; solo es necesario cuando se carga " "mediante dlopen / FFI sin una referencia estática)" -#: src/cli/flags.md:423 +#: src/cli/flags.md:445 msgid "Enable type checking via tsgo IPC" msgstr "Habilitar la verificación de tipos mediante IPC de tsgo" -#: src/cli/flags.md:424 +#: src/cli/flags.md:446 msgid "`--strict-eval`" msgstr "`--strict-eval`" -#: src/cli/flags.md:424 +#: src/cli/flags.md:446 msgid "" -"Fail the build if any runtime-unknown `eval(...)` / `new Function()` site is reachable. By default such a site is compiled to a deferred " -"runtime error (throws only if reached) and a compile-time notice is printed. " -"Also settable via `perry.eval = \"error\"` / `perry.strict = true` (package." -"json or perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. See [Limitations]" -"(../language/limitations.md#no-eval-or-dynamic-code)." -msgstr "" -"Falla en la compilación si se puede acceder a cualquier sitio desconocido de " -"`eval(...)` / `new Function()`. Por defecto, tal sitio se " -"compila a un error de tiempo de ejecución diferido (sólo se lanza si se " -"alcanza) y se imprime un aviso de tiempo de compilación. También se puede " -"configurar mediante `perry.eval = \"error\"` / `perry.strict = true` " -"(package.json o perry.toml). `PERRY_ALLOW_EVAL=1` lo obliga a retirarse. " -"Véase [Limitations](../language/limitations.md#no-eval-or-dynamic-code)." +"Legacy compatibility option; current dynamic-code lowering does not enforce " +"a general rejection gate. See [dynamic code " +"limits](../language/limitations.md#dynamic-code-and-module-loading)." +msgstr "" +"Opción de compatibilidad heredada; la bajada actual de código dinámico no " +"impone rechazo general. Véanse los [límites del código " +"dinámico](../language/limitations.md#dynamic-code-and-module-loading)." -#: src/cli/flags.md:425 +#: src/cli/flags.md:447 msgid "`--strict-dynamic-import`" msgstr "`--strict-dynamic-import`" -#: src/cli/flags.md:425 +#: src/cli/flags.md:447 msgid "" -"Fail the build if a dynamic `import(...)` has a runtime-computed (non-" -"resolvable) specifier. By default such a site is compiled to a rejected " -"`Promise` that throws a descriptive `Error` only if reached, and is listed " -"in the same end-of-build notice as deferred eval sites. Also settable via " -"`perry.dynamicImport = \"error\"` / `perry.strict = true` (package.json or " -"perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. Resolvable forms (string " -"literals, ternaries of resolvable arms, template literals over const locals, " -"finite union-typed params, glob) are unaffected. See [Limitations](../" -"language/limitations.md#no-eval-or-dynamic-code)." -msgstr "" -"Fallar la construcción si un `import(...)` dinámico tiene un especificador " -"de tiempo de ejecución (no resoluble). Por defecto, este sitio se compila a " -"un `Promise` rechazado que lanza un `Error` descriptivo sólo si se alcanza, " -"y está listado en el mismo aviso de fin de construcción que sitios de " -"valores diferidos. También se puede configurar mediante `perry.dynamicImport " -"= \"error\"` / `perry.strict = true` (package.json o perry.toml). " -"`PERRY_ALLOW_EVAL=1` lo obliga a retirarse. Las formas resolubles " -"(encadenados literales, ternarios de brazos resolubles, plantillas literales " -"sobre locales de const, params finitos de tipo unión, glob) no se ven " -"afectadas. Véase [Limitations](../language/limitations.md#no-eval-or-dynamic-" -"code)." - -#: src/cli/flags.md:426 +"Reject unresolved asynchronous import specifiers during module collection. " +"Supported resolvable forms and synchronous `require()` are unaffected. " +"`PERRY_ALLOW_EVAL=1` disables this check." +msgstr "" +"Rechaza especificadores asíncronos no resueltos al recopilar módulos. Las " +"formas resolubles admitidas y `require()` síncrono no cambian. " +"`PERRY_ALLOW_EVAL=1` desactiva la comprobación." + +#: src/cli/flags.md:448 msgid "`--strict-unimplemented`" msgstr "`--strict-unimplemented`" -#: src/cli/flags.md:426 +#: src/cli/flags.md:448 msgid "" "Fail at compile time when a recognized but unimplemented Node/stdlib API is " "referenced instead of emitting a deferred runtime error." @@ -71728,23 +71527,23 @@ msgstr "" "stdlib reconocida pero no implementada en lugar de emitir un error de tiempo " "de ejecución diferido." -#: src/cli/flags.md:427 +#: src/cli/flags.md:449 msgid "`--emit-attest`" msgstr "`--emit-attest`" -#: src/cli/flags.md:427 +#: src/cli/flags.md:449 msgid "Emit `.attest.json`; see [Binary attestation](emit-attest.md)." msgstr "" "Emite `.attest.json`; véase [Declaración binaria](emit-attest.md)." -#: src/cli/flags.md:428 +#: src/cli/flags.md:450 msgid "" "Emit the platform sandbox sidecar; see [Sandbox profiles](emit-sandbox.md)." msgstr "" "Emite el sidecar de la plataforma Sandbox; véase [Perfiles de caja de arena]" "(emit-sandbox.md)." -#: src/cli/flags.md:429 +#: src/cli/flags.md:451 msgid "" "Refuse reachable arbitrary-code-execution surfaces; see [Lockdown](lockdown." "md)." @@ -71752,7 +71551,7 @@ msgstr "" "Rechazar superficies de ejecución de código arbitrario accesibles; véase " "[Lockdown](lockdown.md)." -#: src/cli/flags.md:431 +#: src/cli/flags.md:453 msgid "" "`--type-check` requires a project `tsconfig.json` and the verified preview " "build: install with `npm install --save-dev @typescript/native-preview@7.0.0-" @@ -71762,31 +71561,31 @@ msgid "" "handshake, so other builds are explicitly refused before IPC." msgstr "" -#: src/cli/flags.md:433 src/cli/perry-toml.md:512 +#: src/cli/flags.md:455 src/cli/perry-toml.md:512 msgid "Environment Variables" msgstr "Variables de entorno" -#: src/cli/flags.md:437 src/cli/perry-toml.md:520 +#: src/cli/flags.md:459 src/cli/perry-toml.md:520 msgid "`PERRY_LICENSE_KEY`" msgstr "`PERRY_LICENSE_KEY`" -#: src/cli/flags.md:437 +#: src/cli/flags.md:459 msgid "Perry Hub license key for `perry publish`" msgstr "Clave de licencia de Perry Hub para `perry publish`" -#: src/cli/flags.md:438 src/cli/perry-toml.md:522 +#: src/cli/flags.md:460 src/cli/perry-toml.md:522 msgid "`PERRY_APPLE_CERTIFICATE_PASSWORD`" msgstr "`PERRY_APPLE_CERTIFICATE_PASSWORD`" -#: src/cli/flags.md:438 +#: src/cli/flags.md:460 msgid "Password for .p12 certificate" msgstr "Contraseña para el certificado .p12" -#: src/cli/flags.md:439 +#: src/cli/flags.md:461 msgid "`PERRY_TARGET_CPU`" msgstr "`PERRY_TARGET_CPU`" -#: src/cli/flags.md:439 +#: src/cli/flags.md:461 msgid "" "CPU baseline for generated machine code (same values as `--march`; the flag " "and perry.toml `[build] march` win over the env var)" @@ -71795,62 +71594,62 @@ msgstr "" "valores que `--march`; el indicador y perry.toml `[build] march` ganan sobre " "el var env)" -#: src/cli/flags.md:440 src/cli/updates.md:146 +#: src/cli/flags.md:462 src/cli/updates.md:146 msgid "`PERRY_NO_UPDATE_CHECK=1`" msgstr "`PERRY_NO_UPDATE_CHECK=1`" -#: src/cli/flags.md:440 +#: src/cli/flags.md:462 msgid "Disable automatic update checks" msgstr "Deshabilitar las comprobaciones automáticas de actualizaciones" -#: src/cli/flags.md:441 src/cli/updates.md:147 +#: src/cli/flags.md:463 src/cli/updates.md:147 msgid "`NO_UPDATE_NOTIFIER`" msgstr "`NO_UPDATE_NOTIFIER`" -#: src/cli/flags.md:441 +#: src/cli/flags.md:463 msgid "The same, using the ecosystem-wide spelling" msgstr "Lo mismo, usando la ortografía de todo el ecosistema" -#: src/cli/flags.md:442 src/cli/updates.md:148 +#: src/cli/flags.md:464 src/cli/updates.md:148 msgid "`PERRY_UPDATE_MODE`" msgstr "`PERRY_UPDATE_MODE`" -#: src/cli/flags.md:442 +#: src/cli/flags.md:464 msgid "`off`/`notify`/`prompt`/`auto` for one run — see [Updates](updates.md)" msgstr "" "`off`/`notify`/`prompt`/`auto` para una carrera — véase [Updates](updates.md)" -#: src/cli/flags.md:443 src/cli/updates.md:149 +#: src/cli/flags.md:465 src/cli/updates.md:149 msgid "`PERRY_UPDATE_SERVER`" msgstr "`PERRY_UPDATE_SERVER`" -#: src/cli/flags.md:443 +#: src/cli/flags.md:465 msgid "Custom update server URL" msgstr "URL de servidor de actualizaciones personalizado" -#: src/cli/flags.md:444 +#: src/cli/flags.md:466 msgid "`CI=true`" msgstr "`CI=true`" -#: src/cli/flags.md:444 +#: src/cli/flags.md:466 msgid "Auto-skip update checks (set by most CI systems)" msgstr "" "Omitir automáticamente las comprobaciones de actualizaciones (establecido " "por la mayoría de los sistemas CI)" -#: src/cli/flags.md:445 +#: src/cli/flags.md:467 msgid "`RUST_LOG`" msgstr "`RUST_LOG`" -#: src/cli/flags.md:445 +#: src/cli/flags.md:467 msgid "Debug logging level (`debug`, `info`, `trace`)" msgstr "Nivel de registro de depuración (`debug`, `info`, `trace`)" -#: src/cli/flags.md:446 +#: src/cli/flags.md:468 msgid "`PERRY_OPT_REPORT`" msgstr "`PERRY_OPT_REPORT`" -#: src/cli/flags.md:446 +#: src/cli/flags.md:468 msgid "" "`1`/`text` or `json` — same as `--opt-report[=json]`, for driving the report " "from an environment where adding a flag is awkward" @@ -71858,7 +71657,7 @@ msgstr "" "`1`/`text` o `json` — lo mismo que `--opt-report[=json]`, para conducir el " "informe desde un entorno donde añadir un indicador es incómodo" -#: src/cli/flags.md:448 +#: src/cli/flags.md:470 msgid "" "The native-stack GC root-pressure report has **no** environment spelling: " "use `--statepoint-report[=json]`. The `PERRY_STATEPOINT_REPORT` variable is " @@ -71873,15 +71672,15 @@ msgstr "" "usuario — era un quinto botón de env GC sin brazo CI, y fue eliminado bajo " "la política de matar del pomo GC de CLAUDE.md." -#: src/cli/flags.md:454 +#: src/cli/flags.md:476 msgid "Configuration Files" msgstr "Archivos de configuración" -#: src/cli/flags.md:456 +#: src/cli/flags.md:478 msgid "perry.toml (project)" msgstr "perry.toml (proyecto)" -#: src/cli/flags.md:458 +#: src/cli/flags.md:480 msgid "" "```toml\n" "[project]\n" @@ -71961,11 +71760,11 @@ msgstr "" "category = \"Development\"\n" "```" -#: src/cli/flags.md:496 +#: src/cli/flags.md:518 msgid "~/.perry/config.toml (global)" msgstr "~/.perry/config.toml (global)" -#: src/cli/flags.md:498 +#: src/cli/flags.md:520 msgid "" "```toml\n" "[apple]\n" @@ -71987,76 +71786,76 @@ msgstr "" "key_alias = \"my-key\"\n" "```" -#: src/cli/flags.md:511 +#: src/cli/flags.md:533 #, fuzzy msgid "# Simple CLI program\n" msgstr "# Programa CLI simple" -#: src/cli/flags.md:513 +#: src/cli/flags.md:535 #, fuzzy msgid "# iOS app for simulator\n" msgstr "# iOS aplicación para simulador" -#: src/cli/flags.md:516 +#: src/cli/flags.md:538 #, fuzzy msgid "# visionOS app for simulator\n" msgstr "# visionOS aplicación para simulador" -#: src/cli/flags.md:519 +#: src/cli/flags.md:541 #, fuzzy msgid "# Web app (WASM with DOM bridge — alias: --target wasm)\n" msgstr "# Aplicación web (WASM con puente DOM — alias: --target wasm)" -#: src/cli/flags.md:522 +#: src/cli/flags.md:544 #, fuzzy msgid "# Plugin shared library\n" msgstr "# Librería compartida de complementos" -#: src/cli/flags.md:525 +#: src/cli/flags.md:547 #, fuzzy msgid "# iOS widget with bundle ID\n" msgstr "# iOS widget con ID de paquete" -#: src/cli/flags.md:528 +#: src/cli/flags.md:550 #, fuzzy msgid "# Debug compilation\n" msgstr "# Compilación de depuración" -#: src/cli/flags.md:531 +#: src/cli/flags.md:553 #, fuzzy msgid "# Verbose compilation\n" msgstr "# Compilación de verbos" -#: src/cli/flags.md:534 +#: src/cli/flags.md:556 #, fuzzy msgid "# Type-checked compilation\n" msgstr "# Compilación verificada por el tipo" -#: src/cli/flags.md:537 +#: src/cli/flags.md:559 #, fuzzy msgid "# Raw WASM binary (no HTML wrapper)\n" msgstr "# Raw WASM binario (sin envoltorio HTML)" -#: src/cli/flags.md:540 +#: src/cli/flags.md:562 #, fuzzy msgid "# Minified web output (compresses embedded JS bridge)\n" msgstr "# Salida web minimizada (comprime el puente JS integrado)" -#: src/cli/flags.md:547 +#: src/cli/flags.md:569 msgid "[Commands](commands.md) — All CLI commands" msgstr "[Comandos](commands.md) — Todos los comandos de la CLI" -#: src/cli/flags.md:548 +#: src/cli/flags.md:570 msgid "[Platform Overview](../platforms/overview.md) — Platform targets" msgstr "" "[Descripción general de plataformas](../platforms/overview.md) — Plataformas " "disponibles" -#: src/cli/flags.md:550 +#: src/cli/flags.md:572 msgid "Typed-feedback profile replay" msgstr "" -#: src/cli/flags.md:552 +#: src/cli/flags.md:574 msgid "" "`--typed-feedback-profile ` supplies an **advisory** profile to native " "LLVM lowering. Default builds do not read a profile. The first supported " @@ -72066,13 +71865,13 @@ msgid "" "explained." msgstr "" -#: src/cli/flags.md:558 +#: src/cli/flags.md:580 msgid "" "Capture a workload, then replay it with the **same compiler executable, " "source, target and lowering options**:" msgstr "" -#: src/cli/flags.md:572 +#: src/cli/flags.md:594 msgid "" "The conversion utility is in the Perry source checkout. Pair the catalog " "with the trace from that exact capture build. It retains only observed " @@ -72083,120 +71882,120 @@ msgid "" "normal binary cannot restore omitted instrumentation." msgstr "" -#: src/cli/flags.md:579 +#: src/cli/flags.md:601 msgid "The JSON replay schema is version 1:" msgstr "" -#: src/cli/flags.md:583 +#: src/cli/flags.md:605 #, fuzzy msgid "\"schema_version\"" msgstr "`schemaVersion`" -#: src/cli/flags.md:584 +#: src/cli/flags.md:606 #, fuzzy msgid "\"compiler\"" msgstr "compile" -#: src/cli/flags.md:584 +#: src/cli/flags.md:606 msgid "\"sha256:\"" msgstr "" -#: src/cli/flags.md:585 src/cli/perry-audit-sbom.md:48 +#: src/cli/flags.md:607 src/cli/perry-audit-sbom.md:48 msgid "\"modules\"" msgstr "\"módulos\"" -#: src/cli/flags.md:586 +#: src/cli/flags.md:608 #, fuzzy msgid "\"identity\"" msgstr "Identidad caché" -#: src/cli/flags.md:587 +#: src/cli/flags.md:609 #, fuzzy msgid "\"module\"" msgstr "\"módulos\"" -#: src/cli/flags.md:587 +#: src/cli/flags.md:609 msgid "\"app.ts\"" msgstr "" -#: src/cli/flags.md:588 +#: src/cli/flags.md:610 #, fuzzy msgid "\"source_hash\"" msgstr "\"fuente\"" -#: src/cli/flags.md:588 +#: src/cli/flags.md:610 msgid "\"sha256:\"" msgstr "" -#: src/cli/flags.md:589 +#: src/cli/flags.md:611 #, fuzzy msgid "\"hir_hash\"" msgstr "\"snippet_hash\"" -#: src/cli/flags.md:589 +#: src/cli/flags.md:611 msgid "\"\"" msgstr "" -#: src/cli/flags.md:590 +#: src/cli/flags.md:612 #, fuzzy msgid "\"lowering_hash\"" msgstr "\"lodash\"" -#: src/cli/flags.md:590 +#: src/cli/flags.md:612 msgid "\"\"" msgstr "" -#: src/cli/flags.md:591 +#: src/cli/flags.md:613 #, fuzzy msgid "\"target\"" msgstr "\"objetivos\"" -#: src/cli/flags.md:591 +#: src/cli/flags.md:613 msgid "\"x86_64-unknown-linux-gnu\"" msgstr "" -#: src/cli/flags.md:593 +#: src/cli/flags.md:615 msgid "\"sites\"" msgstr "" -#: src/cli/flags.md:594 +#: src/cli/flags.md:616 #, fuzzy msgid "\"site_id\"" msgstr "\"client_id\"" -#: src/cli/flags.md:595 +#: src/cli/flags.md:617 #, fuzzy msgid "\"function\"" msgstr "\"funciones\"" -#: src/cli/flags.md:595 +#: src/cli/flags.md:617 #, fuzzy msgid "\"perry_fn_app_ts__read\"" msgstr "\"libperry_appreview.a\"" -#: src/cli/flags.md:596 +#: src/cli/flags.md:618 #, fuzzy msgid "\"array_element\"" msgstr "\"Incremento\"" -#: src/cli/flags.md:597 +#: src/cli/flags.md:619 #, fuzzy msgid "\"operation\"" msgstr "\"caption\"" -#: src/cli/flags.md:597 +#: src/cli/flags.md:619 msgid "\"array[index]\"" msgstr "" -#: src/cli/flags.md:598 +#: src/cli/flags.md:620 msgid "\"observation_kind\"" msgstr "" -#: src/cli/flags.md:598 +#: src/cli/flags.md:620 msgid "\"numeric_array_element\"" msgstr "" -#: src/cli/flags.md:604 +#: src/cli/flags.md:626 msgid "" "Use generated identities, rather than copying this illustrative site ID. " "Site IDs identify deterministic lowering sites within an exact module/" @@ -72210,7 +72009,7 @@ msgid "" "replay is attempted." msgstr "" -#: src/cli/flags.md:614 +#: src/cli/flags.md:636 msgid "" "Malformed or unreadable explicit input is a compilation error naming the " "profile and how to create it. Well-formed schema/compiler/target/source/HIR/" @@ -72224,7 +72023,7 @@ msgid "" "lowering are deterministic for identical inputs." msgstr "" -#: src/cli/flags.md:624 +#: src/cli/flags.md:646 msgid "" "Profiles and TypeScript annotations never authorize an unchecked operation. " "Every replay-selected read rechecks the live receiver, array representation, " @@ -72237,11 +72036,11 @@ msgid "" "materialization record for every claimed profile selection." msgstr "" -#: src/cli/flags.md:634 +#: src/cli/flags.md:656 msgid "Unused re-export collection" msgstr "" -#: src/cli/flags.md:636 +#: src/cli/flags.md:658 msgid "" "Collection prunes unused `export { name } from`, `export * from`, and " "namespace re-export edges by default when the exporting file and every " @@ -72267,14 +72066,14 @@ msgid "" "deferred." msgstr "" -#: src/cli/flags.md:658 +#: src/cli/flags.md:680 msgid "" "Set `PERRY_NO_REEXPORT_PRUNE=1` to disable this collection pass for an A/B " "build. The compile summary reports unique omitted modules in the proven " "static dependency trees, excluding modules retained by another importer." msgstr "" -#: src/cli/flags.md:662 +#: src/cli/flags.md:684 msgid "" "Set `PERRY_COLLECT_ONLY=1` to stop after collection and write `/" "audit.json` and `module-graph.json` (paths, eager/deferred initialization, " @@ -72283,18 +72082,18 @@ msgid "" "checks. For example:" msgstr "" -#: src/cli/flags.md:673 +#: src/cli/flags.md:695 msgid "Linux hot text, including the Rust runtime" msgstr "" -#: src/cli/flags.md:675 +#: src/cli/flags.md:697 msgid "" "Ordering changes layout only: every unlisted function remains available at " "the same optimization level. Normal builds without an order keep the " "original link arguments and startup code." msgstr "" -#: src/cli/flags.md:679 +#: src/cli/flags.md:701 msgid "" "For a hot runtime profile, compile with `PERRY_KEEP_SYMBOLS=1`, then run " "`scripts/record_function_order.py -o app.order -- ./app representative-" @@ -72311,7 +72110,7 @@ msgid "" "avoids instrumentation in the sample run." msgstr "" -#: src/cli/flags.md:692 +#: src/cli/flags.md:714 msgid "" "Apply either list with `--function-order app.order`. On Linux the linker " "places listed generated and Rust function sections together at the beginning " @@ -72754,8 +72553,8 @@ msgstr "# Mantenga la reasociación de --fast-math pero bloque FMA contracción. #: src/cli/fast-math.md:55 msgid "" -"The same setting is available through `PERRY_FP_CONTRACT=off|on|fast` or " -"`\"perry\": { \"fpContract\": \"on\" }` in package.json. Explicit package, " +"The same setting is available through `PERRY_FP_CONTRACT=off|on|fast` or `" +"\"perry\": { \"fpContract\": \"on\" }` in package.json. Explicit package, " "env, or CLI `fpContract` values override the `--fast-math` implied default." msgstr "" "La misma configuración está disponible a través de `PERRY_FP_CONTRACT=off|on|" @@ -73237,9 +73036,8 @@ msgstr "" #: src/cli/dynamic-dispatch.md:11 msgid "" "is the standard string-based obfuscation pattern used by malicious npm " -"packages: `process[\"bind\" + \"ing\"](\"dns\")`, " -"`globalThis[atob(\"ZXZhbA==\")]()`, `fs[methodName]()` where `methodName` is " -"computed at runtime." +"packages: `process[\"bind\" + \"ing\"](\"dns\")`, `globalThis[atob(\"ZXZhbA==" +"\")]()`, `fs[methodName]()` where `methodName` is computed at runtime." msgstr "" "es el patrón estándar de obfuscación basado en cadenas utilizado por " "paquetes npm maliciosos: `process[\"bind\" + \"ing\"](\"dns\")`, " @@ -73534,14 +73332,14 @@ msgstr "" #: src/cli/dynamic-dispatch.md:143 msgid "" "See [`#503`](https://github.com/PerryTS/perry/issues/503) for design " -"discussion and the broader supply-chain hardening series (\\[`#495`–" -"`#506`\\] (https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security))." +"discussion and the broader supply-chain hardening series (\\[`#495`–`#506`" +"\\] (https://github.com/PerryTS/perry/issues?q=is%3Aissue+label%3Aenhancement" +"+security))." msgstr "" "Ver [`#503`](https://github.com/PerryTS/perry/issues/503) para la discusión " "de diseño y la serie más amplia de refuerzo de la cadena de suministro " -"(\\[`#495`–`#506`\\] (https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security))." +"(\\[`#495`–`#506`\\] (https://github.com/PerryTS/perry/issues?q=is%3Aissue" +"+label%3Aenhancement+security))." #: src/cli/allow-js-runtime.md:1 msgid "JS Runtime Opt-In (`perry.allowJsRuntime`)" @@ -73685,8 +73483,8 @@ msgid "" "PerryTS/perry/issues?q=is%3Aissue+label%3Aenhancement+security))." msgstr "" "La serie más amplia de refuerzo de la cadena de suministro ([`#495`–`#506`]" -"(https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security))." +"(https://github.com/PerryTS/perry/issues?q=is%3Aissue+label%3Aenhancement" +"+security))." #: src/cli/sandbox-buildrs.md:3 msgid "" @@ -74486,8 +74284,8 @@ msgstr "Composición con el resto de la serie de seguridad" #: src/cli/lockdown.md:65 msgid "" "Lockdown is the umbrella mode for the wider supply-chain hardening series " -"([`#495`–`#506`](https://github.com/PerryTS/perry/issues?" -"q=is%3Aissue+label%3Aenhancement+security)):" +"([`#495`–`#506`](https://github.com/PerryTS/perry/issues?q=is%3Aissue+label" +"%3Aenhancement+security)):" msgstr "" "Lockdown es el modo paraguas para la serie más amplia de endurecimiento de " "la cadena de suministro ([`#495`–`#506`](https://github.com/PerryTS/perry/" @@ -74913,8 +74711,8 @@ msgstr "\"*\"" #: src/cli/capabilities.md:39 msgid "" -"Keys are exact npm package names (`@scope/pkg` accepted) or the universal " -"`\"*\"` default." +"Keys are exact npm package names (`@scope/pkg` accepted) or the universal `" +"\"*\"` default." msgstr "" "Las claves son nombres exactos de paquetes npm (`@scope/pkg` aceptado) o el " "comodín universal `\"*\"`." @@ -76048,9 +75846,9 @@ msgid "" "`PERRY_TARGET_CPU`." msgstr "" "Línea de base de CPU para el código de máquina generado: un nombre de CPU " -"LLVM (`\"x86-64-v2\"`, `\"x86-64-v3\"`, `\"znver2\"`, `\"apple-m1\"`, ...), " -"`\"native\"` (en función del conjunto completo de instrucciones de la " -"máquina de construcción), o `\"generic\"` (la línea de base portátil de la " +"LLVM (`\"x86-64-v2\"`, `\"x86-64-v3\"`, `\"znver2\"`, `\"apple-m1\"`, ...), `" +"\"native\"` (en función del conjunto completo de instrucciones de la máquina " +"de construcción), o `\"generic\"` (la línea de base portátil de la " "arquitectura de destino). Fijar esto cuando el binario se ejecuta en " "máquinas distintas de la caja de compilación — una construcción de origen-" "host sin fijar en una máquina AVX-512 se bloquea con SIGILL en CPUs x86-64 " @@ -76067,11 +75865,11 @@ msgstr "bool" #: src/cli/perry-toml.md:160 msgid "" -"Boolean shorthand for `march`: `true` → `\"native\"`, `false` → " -"`\"generic\"`. Ignored when `march` is set." +"Boolean shorthand for `march`: `true` → `\"native\"`, `false` → `\"generic" +"\"`. Ignored when `march` is set." msgstr "" -"Taquigrafía booleana para `march`: `true` → `\"native\"`, `false` → " -"`\"generic\"`. Ignorado cuando `march` está configurado." +"Taquigrafía booleana para `march`: `true` → `\"native\"`, `false` → `" +"\"generic\"`. Ignorado cuando `march` está configurado." #: src/cli/perry-toml.md:162 msgid "" @@ -76235,8 +76033,8 @@ msgstr "`notarize_signing_identity`" msgid "" "Signing identity for notarization (only used with `distribute = \"both\"`)" msgstr "" -"Identidad de firma para notarización (solo se usa con `distribute = " -"\"both\"`)" +"Identidad de firma para notarización (solo se usa con `distribute = \"both" +"\"`)" #: src/cli/perry-toml.md:194 msgid "`installer_certificate`" @@ -76777,8 +76575,8 @@ msgstr "`permissions`" #: src/cli/perry-toml.md:340 msgid "" -"Android permissions (e.g., `[\"INTERNET\", \"CAMERA\", " -"\"ACCESS_FINE_LOCATION\"]`)" +"Android permissions (e.g., `[\"INTERNET\", \"CAMERA\", \"ACCESS_FINE_LOCATION" +"\"]`)" msgstr "" "Permisos de Android (p. ej., `[\"INTERNET\", \"CAMERA\", " "\"ACCESS_FINE_LOCATION\"]`)" @@ -76835,11 +76633,11 @@ msgstr "Formato de paquete: `\"appimage\"`, `\"deb\"` o `\"rpm\"`" #: src/cli/perry-toml.md:356 msgid "" -"Desktop application category (e.g., `\"Development\"`, `\"Utility\"`, " -"`\"Game\"`)" +"Desktop application category (e.g., `\"Development\"`, `\"Utility\"`, `\"Game" +"\"`)" msgstr "" -"Categoría de la aplicación de escritorio (p. ej., `\"Development\"`, " -"`\"Utility\"`, `\"Game\"`)" +"Categoría de la aplicación de escritorio (p. ej., `\"Development\"`, `" +"\"Utility\"`, `\"Game\"`)" #: src/cli/perry-toml.md:357 msgid "Application description for package metadata" @@ -77042,11 +76840,11 @@ msgstr "`\"all\"`" #: src/cli/perry-toml.md:417 msgid "" -"Filter findings by severity: `\"all\"`, `\"critical\"`, `\"high\"`, " -"`\"medium\"`, `\"low\"`" +"Filter findings by severity: `\"all\"`, `\"critical\"`, `\"high\"`, `\"medium" +"\"`, `\"low\"`" msgstr "" -"Filtra los hallazgos por severidad: `\"all\"`, `\"critical\"`, `\"high\"`, " -"`\"medium\"`, `\"low\"`" +"Filtra los hallazgos por severidad: `\"all\"`, `\"critical\"`, `\"high\"`, `" +"\"medium\"`, `\"low\"`" #: src/cli/perry-toml.md:418 msgid "`ignore`" @@ -77510,7 +77308,7 @@ msgstr "" msgid "Setting" msgstr "Ajuste" -#: src/cli/perry-toml.md:581 src/internals/memory-model.md:310 +#: src/cli/perry-toml.md:581 src/internals/memory-model.md:294 msgid "File" msgstr "Archivo" @@ -78887,8 +78685,8 @@ msgid "" "`recordEmbeddedResponse` reads the body according to your configured " "`source`, so a registry answering a `gh-releases` request is rejected rather " "than read as version `\"\"`. A version that does not parse is also rejected " -"— one malformed answer would otherwise become a permanent \"update " -"available\" your users cannot dismiss." +"— one malformed answer would otherwise become a permanent \"update available" +"\" your users cannot dismiss." msgstr "" "`recordEmbeddedResponse` lee el cuerpo de acuerdo con su configuración " "`source`, por lo que un registro que responde a una solicitud `gh-releases` " @@ -78910,9 +78708,9 @@ msgid "" "Perry sends no telemetry unless you accept the first-run prompt (or " "explicitly set `telemetry.enabled = true` in `~/.perry/config.toml`). This " "setting is the master consent gate for every telemetry channel. If it is " -"false or missing, nothing is sent, even when `compatibility_reports = " -"\"on\"`. The environment overrides `PERRY_NO_TELEMETRY=1` and `CI=true` " -"always win as well." +"false or missing, nothing is sent, even when `compatibility_reports = \"on" +"\"`. The environment overrides `PERRY_NO_TELEMETRY=1` and `CI=true` always " +"win as well." msgstr "" #: src/cli/telemetry.md:9 @@ -79150,12 +78948,13 @@ msgstr "" #: src/internals/memory-model.md:5 msgid "" -"If you've ever wondered \"does Perry use reference counting?\" — no. There " -"is no `Rc` at runtime. Perry has a real tracing GC, described below." +"JavaScript heap objects use tracing GC. Rust runtime components may " +"separately use reference-counted ownership; that is not the JavaScript heap " +"collector." msgstr "" -"Si alguna vez se preguntó «¿usa Perry reference counting?» — no. No existe " -"ningún `Rc` en tiempo de ejecución. Perry tiene un GC de rastreo real, " -"descrito a continuación." +"Los objetos del heap de JavaScript usan GC por trazado. Los componentes Rust" +" pueden emplear propiedad con recuento de referencias; ese mecanismo no es " +"el recolector del heap de JavaScript." #: src/internals/memory-model.md:7 msgid "" @@ -79173,119 +78972,141 @@ msgstr "" msgid "Value representation: NaN-boxing" msgstr "Representación de valores: NaN-boxing" -#: src/internals/memory-model.md:13 +#: src/internals/memory-model.md:13 src/contributing/architecture.md:49 msgid "" -"Every JavaScript value in Perry is a single 64-bit word. The encoding piggy-" -"backs on IEEE 754: any `f64` whose exponent is all-ones and whose mantissa " -"is non-zero is a NaN, and there are ~2⁵² distinct NaN bit patterns. Perry " -"uses the high 16 bits as a type tag and the low 48 (or 32) bits as the " -"payload." +"The generic JavaScript value ABI is a 64-bit NaN-boxed word. Proven native " +"values and specialized storage can use other representations; boxing is " +"required when crossing a generic value boundary." msgstr "" -"Cada valor JavaScript en Perry es una palabra de 64 bits. La codificación se " -"apoya en IEEE 754: cualquier `f64` cuyo exponente tenga todos los bits en " -"uno y cuya mantisa sea distinta de cero es un NaN, y existen ~2⁵² patrones " -"de bits NaN distintos. Perry usa los 16 bits superiores como etiqueta de " -"tipo y los 48 (o 32) bits inferiores como payload." +"La ABI genérica de valores JavaScript usa una palabra de 64 bits con NaN-" +"boxing. Los valores nativos demostrados y el almacenamiento especializado " +"pueden usar otras representaciones; al cruzar una interfaz de valores " +"genéricos hay que empaquetarlos." #: src/internals/memory-model.md:15 +msgid "" +"The encoding piggy-backs on IEEE 754: any `f64` whose exponent is all-ones " +"and whose mantissa is non-zero is a NaN, and there are ~2⁵² distinct NaN bit" +" patterns. Perry uses the high 16 bits as a type tag and the low 48 (or 32) " +"bits as the payload." +msgstr "" +"La codificación aprovecha IEEE 754: un `f64` con todos los bits del " +"exponente a uno y mantisa no nula es NaN; hay unos 2⁵² patrones NaN. Perry " +"usa los 16 bits superiores como etiqueta y los 48 (o 32) inferiores como " +"carga útil." + +#: src/internals/memory-model.md:17 msgid "Tag (high 16 bits)" msgstr "Etiqueta (16 bits superiores)" -#: src/internals/memory-model.md:15 +#: src/internals/memory-model.md:17 msgid "Payload" msgstr "Payload" -#: src/internals/memory-model.md:17 +#: src/internals/memory-model.md:19 msgid "`0x7FFC…0001`" msgstr "`0x7FFC…0001`" -#: src/internals/memory-model.md:17 src/internals/memory-model.md:18 #: src/internals/memory-model.md:19 src/internals/memory-model.md:20 +#: src/internals/memory-model.md:21 src/internals/memory-model.md:22 msgid "— (singleton)" msgstr "— (singleton)" -#: src/internals/memory-model.md:18 +#: src/internals/memory-model.md:20 msgid "`0x7FFC…0002`" msgstr "`0x7FFC…0002`" -#: src/internals/memory-model.md:18 +#: src/internals/memory-model.md:20 msgid "`null`" msgstr "`null`" -#: src/internals/memory-model.md:19 +#: src/internals/memory-model.md:21 msgid "`0x7FFC…0003`" msgstr "`0x7FFC…0003`" -#: src/internals/memory-model.md:20 +#: src/internals/memory-model.md:22 msgid "`0x7FFC…0004`" msgstr "`0x7FFC…0004`" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 src/contributing/architecture.md:57 msgid "`0x7FF9`" msgstr "`0x7FF9`" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 msgid "Short string (0–5 bytes)" msgstr "Cadena corta (0-5 bytes)" -#: src/internals/memory-model.md:21 +#: src/internals/memory-model.md:23 msgid "length in bits 40–47, the bytes in bits 0–39 — no allocation" msgstr "longitud en bits 40–47, los bytes en bits 0–39 — sin asignación" -#: src/internals/memory-model.md:22 src/contributing/architecture.md:53 +#: src/internals/memory-model.md:24 src/contributing/architecture.md:56 msgid "`0x7FFA`" msgstr "`0x7FFA`" -#: src/internals/memory-model.md:22 src/internals/memory-model.md:24 -#: src/internals/memory-model.md:26 +#: src/internals/memory-model.md:24 src/internals/memory-model.md:27 +#: src/internals/memory-model.md:29 msgid "low 48 bits = heap pointer" msgstr "48 bits inferiores = puntero al heap" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "`0x7FFB`" msgstr "`0x7FFB`" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "JS handle" msgstr "Manija JS" -#: src/internals/memory-model.md:23 +#: src/internals/memory-model.md:25 msgid "low 48 bits = handle id (V8-backed objects)" msgstr "bajo 48 bits = id de mango (objetos con respaldo V8)" -#: src/internals/memory-model.md:24 src/contributing/architecture.md:51 +#: src/internals/memory-model.md:26 src/contributing/architecture.md:58 +#, fuzzy +msgid "`0xFFF9`" +msgstr "`0x7FF9`" + +#: src/internals/memory-model.md:26 src/contributing/architecture.md:58 +msgid "Native handle" +msgstr "Handle nativo" + +#: src/internals/memory-model.md:26 +msgid "low 48 bits = registry id, not a heap pointer" +msgstr "48 bits inferiores = identificador de registro, no puntero al heap" + +#: src/internals/memory-model.md:27 src/contributing/architecture.md:54 msgid "`0x7FFD`" msgstr "`0x7FFD`" -#: src/internals/memory-model.md:24 +#: src/internals/memory-model.md:27 msgid "Object / Array / Closure" msgstr "Object / Array / Closure" -#: src/internals/memory-model.md:25 src/contributing/architecture.md:52 +#: src/internals/memory-model.md:28 src/contributing/architecture.md:55 msgid "`0x7FFE`" msgstr "`0x7FFE`" -#: src/internals/memory-model.md:25 +#: src/internals/memory-model.md:28 msgid "Int32" msgstr "Int32" -#: src/internals/memory-model.md:25 +#: src/internals/memory-model.md:28 msgid "low 32 bits = signed int" msgstr "32 bits inferiores = entero con signo" -#: src/internals/memory-model.md:26 src/contributing/architecture.md:50 +#: src/internals/memory-model.md:29 src/contributing/architecture.md:53 msgid "`0x7FFF`" msgstr "`0x7FFF`" -#: src/internals/memory-model.md:27 +#: src/internals/memory-model.md:30 msgid "anything else" msgstr "cualquier otro valor" -#: src/internals/memory-model.md:27 +#: src/internals/memory-model.md:30 msgid "the full 64 bits are the number" msgstr "los 64 bits completos son el número" -#: src/internals/memory-model.md:36 +#: src/internals/memory-model.md:40 msgid "" "Source: `crates/perry-runtime/src/value/tags.rs` (the singleton and pointer " "tags), with the rest of the `value/` module tree for the encode/decode " @@ -79298,11 +79119,11 @@ msgstr "" "el tiempo de ejecución utiliza internamente; no son valores observables por " "el usuario." -#: src/internals/memory-model.md:41 +#: src/internals/memory-model.md:45 msgid "Three consequences worth noting:" msgstr "Tres consecuencias que vale la pena destacar:" -#: src/internals/memory-model.md:43 +#: src/internals/memory-model.md:47 msgid "" "**Numbers are free.** A plain `f64` value is its own representation — no " "boxing, no header, no allocation. Numeric hot loops cost nothing in memory " @@ -79312,7 +79133,7 @@ msgstr "" "representación — sin boxing, sin cabecera, sin asignación. Los bucles " "intensivos en cómputo numérico no tienen ningún costo en tráfico de memoria." -#: src/internals/memory-model.md:44 +#: src/internals/memory-model.md:48 msgid "" "**The GC can identify pointer values from the tag alone.** When tracing a " "value, the collector masks the high bits, checks for `0x7FFA`/`0x7FFD`/" @@ -79324,7 +79145,7 @@ msgstr "" "`0x7FFA`/`0x7FFD`/`0x7FFF`, y sigue el puntero de 48 bits bajos o lo omite. " "No existe búsqueda de tipo en tiempo de ejecución por valor." -#: src/internals/memory-model.md:45 +#: src/internals/memory-model.md:49 msgid "" "**Type checks are bitwise.** `typeof` and many fast paths in the runtime are " "register-level mask-and-compare operations." @@ -79333,42 +79154,37 @@ msgstr "" "rápidos en el runtime son operaciones de máscara y comparación a nivel de " "registro." -#: src/internals/memory-model.md:47 +#: src/internals/memory-model.md:51 msgid "Heap layout: per-thread arena, nursery + old-gen" msgstr "Diseño del heap: arena por hilo, nursery + old-gen" -#: src/internals/memory-model.md:49 -msgid "" -"Perry is single-threaded by default, and each thread owns its own heap. " -"Sharing across threads happens via deep copy (`SerializedValue`), not shared " -"memory, so the GC never has to synchronize across threads." -msgstr "" -"Perry es monohilo por defecto, y cada hilo posee su propio heap. El " -"intercambio entre hilos se realiza mediante copia profunda " -"(`SerializedValue`), no mediante memoria compartida, por lo que el GC nunca " -"necesita sincronizarse entre hilos." - -#: src/internals/memory-model.md:51 -msgid "Within a thread, the heap is two arenas:" -msgstr "Dentro de un hilo, el heap está formado por dos arenas:" - #: src/internals/memory-model.md:53 msgid "" -"**`ARENA`** — the nursery. New allocations land here. Carved into 1 MB " -"blocks (since v0.5.196)." +"Ordinary values cross agents through `SerializedValue`. SharedArrayBuffer " +"backing is explicitly shared, and supported transfer operations move " +"ownership. Each collector traces its own heap; shared backing and transfer " +"coordination have separate lifetime rules." msgstr "" -"**`ARENA`** — la nursery. Las nuevas asignaciones aterrizan aquí. Dividida " -"en bloques de 1 MB (desde v0.5.196)." +"Los valores ordinarios pasan entre agentes mediante `SerializedValue`. El " +"almacenamiento de SharedArrayBuffer se comparte explícitamente y las " +"transferencias admitidas trasladan la propiedad. Cada recolector recorre su " +"heap; el almacenamiento compartido y las transferencias tienen reglas de " +"vida propias." -#: src/internals/memory-model.md:54 +#: src/internals/memory-model.md:55 msgid "" -"**`OLD_ARENA`** — the old generation. Holds objects that have survived " -"enough minor GCs to be tenured." +"The nursery uses Eden and survivor spaces; `OLD_ARENA` holds old objects. " +"Regular blocks occupy one aligned region (2 MiB on Linux, 1 MiB elsewhere). " +"Dedicated young large-object storage and separately tracked malloc " +"allocations also participate in collection." msgstr "" -"**`OLD_ARENA`** — la generación antigua. Contiene objetos que han " -"sobrevivido suficientes GC menores para ser promovidos." +"La generación joven usa espacios Eden y de supervivientes; `OLD_ARENA` " +"contiene objetos antiguos. Los bloques normales ocupan una región alineada " +"(2 MiB en Linux, 1 MiB en otros sistemas). También se recolectan el " +"almacenamiento específico de objetos jóvenes grandes y las asignaciones " +"malloc registradas por separado." -#: src/internals/memory-model.md:56 +#: src/internals/memory-model.md:57 msgid "" "Every allocation, in either arena, is prefixed by an 8-byte `GcHeader` " "(`crates/perry-runtime/src/gc/types.rs`):" @@ -79376,22 +79192,26 @@ msgstr "" "Cada asignación, en cada ámbito, está prefijado por un `GcHeader` de 8 bytes " "(`crates/perry-runtime/src/gc/types.rs`):" -#: src/internals/memory-model.md:62 +#: src/internals/memory-model.md:63 #, fuzzy msgid "// GC_TYPE_ARRAY, GC_TYPE_STRING, …\n" msgstr "// GC_TYPE_ARRAY, GC_TYPE_STRING, …" -#: src/internals/memory-model.md:63 +#: src/internals/memory-model.md:64 #, fuzzy msgid "// MARKED | ARENA | PINNED | TENURED | HAS_SURVIVED | …\n" msgstr "// MARKED | ARENA | PINNED | TENURED | HAS_SURVIVED | …" #: src/internals/memory-model.md:65 +msgid "// kind-specific facts, including object callability\n" +msgstr "" + +#: src/internals/memory-model.md:66 #, fuzzy msgid "// total alloc size, used for arena block walking\n" msgstr "// tamaño total del aloc, utilizado para caminar en bloque de arena" -#: src/internals/memory-model.md:69 +#: src/internals/memory-model.md:70 msgid "" "Callers receive a pointer **after** the header (`ptr + 8`), so from " "TypeScript code's perspective the header is invisible. The collector finds " @@ -79401,25 +79221,23 @@ msgstr "" "por lo que desde la perspectiva del código TypeScript el encabezado es " "invisible. El recolector lo encuentra restando 8." -#: src/internals/memory-model.md:71 +#: src/internals/memory-model.md:72 msgid "" -"Allocation goes through `gc_malloc(size, obj_type)` in the `gc/` module " -"tree. LLVM-generated code emits calls to this for every object literal, " -"array literal, closure capture, string concat, BigInt operation, etc. Going " -"through the GC allocation funnel is how the collector accounts for memory " -"and decides when to collect." +"Allocation uses runtime helpers and compiler-emitted inline bump allocation." +" Inline births initialize a complete layout and obey the same accounting and" +" GC birth-color contract. `gc_malloc` is one allocation entry point, not a " +"call emitted for every object." msgstr "" -"La asignación pasa por `gc_malloc(size, obj_type)` en el árbol de módulos " -"`gc/`. El código generado por LLVM emite llamadas a esto para cada objeto " -"literal, array literal, captura de cierre, cadena concat, operación BigInt, " -"etc. Ir a través del embudo de asignación GC es cómo el colector cuenta para " -"la memoria y decide cuándo recoger." +"La asignación usa funciones del runtime y asignación incremental inline " +"emitida por el compilador. Las creaciones inline inicializan un layout " +"completo y respetan la misma contabilidad y el contrato de color inicial del" +" GC. `gc_malloc` es una entrada de asignación, no una llamada por objeto." -#: src/internals/memory-model.md:77 +#: src/internals/memory-model.md:74 msgid "How the GC finds roots" msgstr "Cómo el GC encuentra las raíces" -#: src/internals/memory-model.md:79 +#: src/internals/memory-model.md:76 msgid "" "This is the part most people are surprised by: if Perry compiles through " "LLVM, the optimizer is free to keep values in registers, spill them to stack " @@ -79431,32 +79249,30 @@ msgstr "" "slots de pila, rematerializarlos — nada de lo cual el recolector puede " "inspeccionar. ¿Cómo sabe entonces el recolector qué valores JS están vivos?" -#: src/internals/memory-model.md:81 +#: src/internals/memory-model.md:78 msgid "Three mechanisms cover different storage locations:" msgstr "Tres mecanismos cubren diferentes ubicaciones de almacenamiento:" -#: src/internals/memory-model.md:83 +#: src/internals/memory-model.md:80 msgid "1. Target-aware precise roots (codegen-emitted)" msgstr "1. Raíces precisas con conocimiento de destino (emisión de codigo)" -#: src/internals/memory-model.md:85 +#: src/internals/memory-model.md:82 msgid "" -"One pointer-local analysis feeds two correct lowerings. On supported 64-bit " -"AArch64/arm64 and x86-64 targets, native RS4GC statepoints plus Perry's " -"compact stack map are the default. `arm64_32` watchOS, ARM64 Windows, and " -"unsupported architectures use Perry's heap-backed shadow frames. The " -"fallback is a root lowering, not an unrooted mode; see [the current GC page]" -"(garbage-collector.md#roots-by-target)." +"Native AArch64 and x86-64 use LLVM RS4GC statepoints and compact stack maps." +" WASI and ARM64 Windows use shadow frames. watchOS ILP32 currently rejects " +"compiler emission. Managed values must remain in writable roots across " +"collection; generated native homes store boxed values and are reloaded after" +" relocation. See [roots by target](garbage-collector.md#roots-by-target)." msgstr "" -"Un análisis local de punteros alimenta dos descensos correctos. En los " -"objetivos compatibles de 64 bits AArch64/arm64 y x86-64, los puntos de " -"estado nativos RS4GC más el mapa de pila compacto de Perry son los " -"predeterminados. `arm64_32` watchOS, ARM64 Windows y arquitecturas no " -"soportadas utilizan los marcos de sombras de Perry. El retroceso es un " -"descenso de raíz, no un modo sin raíces; véase [la página actual del GC]" -"(garbage-collector.md#roots-by-target)." +"AArch64 y x86-64 nativos usan statepoints RS4GC de LLVM y mapas de pila " +"compactos. WASI y ARM64 Windows usan shadow frames. watchOS ILP32 rechaza " +"actualmente la emisión. Los valores administrados deben permanecer en raíces" +" modificables durante la recolección; sus ubicaciones nativas contienen " +"valores empaquetados y se recargan tras una reubicación. Véanse las [raíces " +"por plataforma](garbage-collector.md#roots-by-target)." -#: src/internals/memory-model.md:91 +#: src/internals/memory-model.md:84 msgid "" "At a safepoint the selected map describes each live managed local regardless " "of whether LLVM kept it in a register, spilled it, or relocated it." @@ -79465,11 +79281,11 @@ msgstr "" "vivo independientemente de si LLVM lo mantuvo en un registro, lo derramó o " "lo reubicó." -#: src/internals/memory-model.md:94 +#: src/internals/memory-model.md:87 msgid "2. Conservative native-stack diagnostic" msgstr "2. Diagnóstico conservador de pila nativa" -#: src/internals/memory-model.md:96 +#: src/internals/memory-model.md:89 msgid "" "The production default does not scan the native stack conservatively: `Auto` " "resolves to `SkipDisabled`. `PERRY_CONSERVATIVE_STACK_SCAN=full` is a " @@ -79484,11 +79300,11 @@ msgstr "" "correspondientes para ese ciclo. Debido a que una raíz ambigua no puede ser " "reescrita con seguridad, este brazo hace que la copia menor no sea elegible." -#: src/internals/memory-model.md:102 +#: src/internals/memory-model.md:95 msgid "3. Registered runtime root scanners" msgstr "3. Escáneres de raíces del runtime registrados" -#: src/internals/memory-model.md:104 +#: src/internals/memory-model.md:97 msgid "" "Some roots live in the runtime itself, not in user code: pending Promises, " "timer callbacks, exception state, async-context stacks, shape caches, " @@ -79505,11 +79321,11 @@ msgstr "" "gc_runtime_root_holders.py` enumera los titulares y rechaza las entradas de " "inventario no clasificadas o rancias." -#: src/internals/memory-model.md:110 +#: src/internals/memory-model.md:103 msgid "Generational behaviour" msgstr "Comportamiento generacional" -#: src/internals/memory-model.md:112 +#: src/internals/memory-model.md:105 msgid "" "Most JS allocations die young — object literals in a loop body, short-lived " "closures, intermediate strings. A generational collector exploits this by " @@ -79520,7 +79336,7 @@ msgstr "" "recolector generacional aprovecha esto recolectando la nursery " "frecuentemente y la generación antigua raramente." -#: src/internals/memory-model.md:114 +#: src/internals/memory-model.md:107 msgid "" "Aging is recorded two different ways, because the two minor paths need " "different things from it." @@ -79528,30 +79344,19 @@ msgstr "" "El envejecimiento se registra de dos maneras diferentes, porque los dos " "caminos menores necesitan cosas diferentes de ella." -#: src/internals/memory-model.md:117 -msgid "" -"**The non-copying minor** uses two flag bits in `gc_flags` (`crates/perry-" -"runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` on the first minor an " -"object survives, `GC_FLAG_TENURED` on the second, at which point the object " -"is logically promoted. The two-bit scheme avoids a counter field in the " -"header, and a tenured object initially stays physically where it is — that " -"promotion is a flag flip, not a copy. Whether such objects are then " -"evacuated into `OLD_ARENA` (with every reference rewritten) is a policy " -"decision taken per cycle from nursery/RSS pressure and measured movable " -"candidates, and it requires generated write barriers to be active." -msgstr "" -"**El menor que no copia** utiliza dos bits de la bandera en `gc_flags` " -"(`crates/perry-runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` en el " -"primer menor un objeto sobrevive, `GC_FLAG_TENURED` en el segundo, en cuyo " -"punto el objeto se promueve lógicamente. El esquema de dos bits evita un " -"campo de contador en la cabecera, y un objeto en posesión inicialmente " -"permanece físicamente donde está - que la promoción es un flip de bandera, " -"no una copia. Si estos objetos son evacuados a `OLD_ARENA` (con cada " -"reescrito de referencia) es una decisión política tomada por ciclo de " -"presión nursery/RSS y candidatos móviles medidos, y requiere barreras de " -"escritura generadas para estar activo." +#: src/internals/memory-model.md:110 +msgid "" +"The non-copying minor records survival in `gc_flags`: " +"`GC_FLAG_HAS_SURVIVED`, then `GC_FLAG_TENURED`. Logical promotion does not " +"itself move the object. Evacuation requires active barriers and rewritable " +"roots; budgeted low-pause cycles remain non-moving." +msgstr "" +"La recolección menor sin copia registra supervivencia en `gc_flags`: " +"`GC_FLAG_HAS_SURVIVED`, después `GC_FLAG_TENURED`. La promoción lógica no " +"mueve el objeto. Evacuar exige barreras activas y raíces reescribibles; los " +"ciclos presupuestados de baja pausa no mueven objetos." -#: src/internals/memory-model.md:127 +#: src/internals/memory-model.md:112 msgid "" "**The copying minor** — the default nursery path — stores an exact short age " "in the header instead, and its promotion threshold is _adaptive_: a survivor-" @@ -79570,7 +79375,7 @@ msgstr "" "fijo y no hay perilla; `crates/perry-runtime/src/gc/tenuring.rs` es la " "definición." -#: src/internals/memory-model.md:135 +#: src/internals/memory-model.md:120 msgid "" "When a copying minor's whole young generation measures (near-)entirely live " "it does not copy at all: the blocks change generation in place. The current " @@ -79584,11 +79389,11 @@ msgstr "" "presupuestos de huella — [Envejecimiento, promoción, y donde nace un objeto]" "(garbage-collector.md#aging-promotion-and-where-an-object-is-born)." -#: src/internals/memory-model.md:140 +#: src/internals/memory-model.md:125 msgid "Write barriers and the remembered set" msgstr "Write barriers y el remembered set" -#: src/internals/memory-model.md:142 +#: src/internals/memory-model.md:127 msgid "" "Generational collectors have one fundamental problem: if an old-gen object " "points to a young-gen object, a minor GC (which only traces the nursery) " @@ -79598,7 +79403,7 @@ msgstr "" "de old-gen apunta a un objeto de young-gen, un GC menor (que solo rastrea la " "nursery) necesita conocer ese puntero o liberará un objeto vivo." -#: src/internals/memory-model.md:144 +#: src/internals/memory-model.md:129 msgid "" "The fix is a **write barrier**: every time a pointer field is written, the " "runtime checks \"is this old → young?\" and, if so, records the parent in a " @@ -79610,7 +79415,7 @@ msgstr "" "el padre en un **remembered set**. Los GC menores tratan las entradas del " "remembered set como raíces adicionales." -#: src/internals/memory-model.md:146 +#: src/internals/memory-model.md:131 msgid "" "In Perry, codegen emits write-barrier calls by default so copied minor GC " "and evacuation can rely on exact remembered-set data. Set " @@ -79627,28 +79432,28 @@ msgstr "" "generacionales luego vuelven a barrer las marcas en vez de confiar en un " "conjunto olvidado vacío." -#: src/internals/memory-model.md:152 +#: src/internals/memory-model.md:137 msgid "Triggers and tuning" msgstr "Disparadores y ajuste" -#: src/internals/memory-model.md:154 +#: src/internals/memory-model.md:139 msgid "`gc_check_trigger` in `gc/policy.rs` responds to four signal families:" msgstr "" "`gc_check_trigger` en `gc/policy.rs` responde a cuatro familias de señales:" -#: src/internals/memory-model.md:156 +#: src/internals/memory-model.md:141 msgid "**Nursery pressure** — allocation growth and the adaptive nursery cap." msgstr "" "**Presión de los viveros** — el crecimiento de la asignación y el límite " "máximo de adaptación de los viveros." -#: src/internals/memory-model.md:157 +#: src/internals/memory-model.md:142 msgid "**Malloc count pressure** — too many separately tracked allocations." msgstr "" "**Presión del recuento de malloc** — demasiadas asignaciones de seguimiento " "por separado." -#: src/internals/memory-model.md:158 +#: src/internals/memory-model.md:143 msgid "" "**Major pacing** — unreclaimed post-collection bytes outgrow the live " "baseline." @@ -79656,7 +79461,7 @@ msgstr "" "**Patinaje importante** — los datos no reclamados después de la recogida " "superan a la línea de base viva." -#: src/internals/memory-model.md:159 +#: src/internals/memory-model.md:144 msgid "" "**Explicit/host requests** — user `gc()` and warning/critical OS memory " "pressure." @@ -79664,7 +79469,7 @@ msgstr "" "**Solicitudes Explicit/host** — la presión de memoria del usuario `gc()` y " "warning/critical OS." -#: src/internals/memory-model.md:161 +#: src/internals/memory-model.md:146 msgid "" "Device/container budgets scale trigger and reclaim ceilings down. Released " "blocks are reused thread-locally under a process-wide byte cap before " @@ -79677,15 +79482,15 @@ msgstr "" "GC para los niveles de presión y el comportamiento de tapa y drenaje de la " "piscina." -#: src/internals/memory-model.md:166 +#: src/internals/memory-model.md:151 msgid "Escape hatches and diagnostics" msgstr "Válvulas de escape y diagnósticos" -#: src/internals/memory-model.md:170 +#: src/internals/memory-model.md:155 msgid "`PERRY_GEN_GC=0` / `off` / `false`" msgstr "`PERRY_GEN_GC=0` / `off` / `false`" -#: src/internals/memory-model.md:170 +#: src/internals/memory-model.md:155 msgid "" "Disable generational mode; fall back to full mark-sweep (intended for " "bisection only)." @@ -79693,25 +79498,26 @@ msgstr "" "Deshabilita el modo generacional; recurre al mark-sweep completo (destinado " "solo a bisección)." -#: src/internals/memory-model.md:171 +#: src/internals/memory-model.md:156 msgid "`PERRY_GC_FORCE_EVACUATE=1`" msgstr "`PERRY_GC_FORCE_EVACUATE=1`" -#: src/internals/memory-model.md:171 +#: src/internals/memory-model.md:156 msgid "" -"With generated write barriers active and policy evacuation allowed, stress-" -"copy every marked non-pinned nursery object instead of only tenured " -"survivors." +"Stress evacuation of eligible live nursery objects; requires active barriers" +" and movable roots. Explicit `gc()` runs an evacuating minor before its full" +" sweep under this control." msgstr "" -"Con las barreras de escritura generadas activas y la evacuación por política " -"permitida, copia forzosamente cada objeto de nursery marcado y no anclado, " -"en lugar de solo los supervivientes tenured." +"Fuerza la evacuación de objetos jóvenes vivos elegibles como prueba de " +"estrés; exige barreras activas y raíces reubicables. Con este control, " +"`gc()` explícito ejecuta una recolección menor evacuadora antes del barrido " +"completo." -#: src/internals/memory-model.md:172 +#: src/internals/memory-model.md:157 msgid "`PERRY_GC_VERIFY_EVACUATION=1`" msgstr "`PERRY_GC_VERIFY_EVACUATION=1`" -#: src/internals/memory-model.md:172 +#: src/internals/memory-model.md:157 msgid "" "After an evacuation that actually forwards objects, panic if any mutable " "live slot still points at a forwarded nursery object after rewrite." @@ -79720,11 +79526,11 @@ msgstr "" "slot mutable vivo todavía apunta a un objeto de nursery reenviado después " "del reescritura." -#: src/internals/memory-model.md:173 +#: src/internals/memory-model.md:158 msgid "`PERRY_WRITE_BARRIERS=0` / `off` / `false`" msgstr "`PERRY_WRITE_BARRIERS=0` / `off` / `false`" -#: src/internals/memory-model.md:173 +#: src/internals/memory-model.md:158 msgid "" "Disable codegen-emitted write barriers at compile time and runtime exact " "helper barriers at runtime for benchmark/debug bisection. Unset, `=1`, " @@ -79735,11 +79541,11 @@ msgstr "" "bisección de benchmarks/depuración. Sin definir, `=1`, `=on` y `=true` " "mantienen las barreras habilitadas." -#: src/internals/memory-model.md:174 src/internals/garbage-collector.md:413 +#: src/internals/memory-model.md:159 src/internals/garbage-collector.md:409 msgid "`PERRY_GC_DIAG=1`" msgstr "`PERRY_GC_DIAG=1`" -#: src/internals/memory-model.md:174 +#: src/internals/memory-model.md:159 msgid "" "Print per-cycle diagnostics, including one evacuation-policy line for cycles " "where evacuation was considered and for `barriers_inactive` skips." @@ -79748,11 +79554,11 @@ msgstr "" "evacuación para los ciclos en que se consideró la evacuación y para los " "saltos de `barriers_inactive`." -#: src/internals/memory-model.md:176 +#: src/internals/memory-model.md:161 msgid "Rooting-bug instruments" msgstr "Instrumentos de enraizamiento-bug" -#: src/internals/memory-model.md:178 +#: src/internals/memory-model.md:163 msgid "" "A value that is live but not rooted across a collection point leaves nothing " "behind at collection time — there is literally nothing for the collector to " @@ -79771,11 +79577,11 @@ msgstr "" "colapsar esa latencia de detección. **Todos están predeterminados y inertes " "cuando están apagados.**" -#: src/internals/memory-model.md:187 +#: src/internals/memory-model.md:172 msgid "`PERRY_GC_PROTECT_FROMSPACE=1`" msgstr "`PERRY_GC_PROTECT_FROMSPACE=1`" -#: src/internals/memory-model.md:187 +#: src/internals/memory-model.md:172 msgid "" "After an **evacuating (copying) minor**, do not recycle from-space. Retired " "Eden and active-survivor blocks are detached into a bounded quarantine, " @@ -79799,11 +79605,11 @@ msgstr "" "nativa, luego restaura `SIG_DFL` y devuelve para que la instrucción vuelva a " "fallar — un archivo de núcleo o depurador todavía ve el sitio de fallo real." -#: src/internals/memory-model.md:188 +#: src/internals/memory-model.md:173 msgid "`PERRY_GC_PROTECT_FROMSPACE=poison`" msgstr "`PERRY_GC_PROTECT_FROMSPACE=poison`" -#: src/internals/memory-model.md:188 +#: src/internals/memory-model.md:173 msgid "" "As above without `mprotect`: poison only. Use where a fault is unwanted, or " "for the sub-page block edges `mprotect` cannot cover (those are always " @@ -79813,11 +79619,11 @@ msgstr "" "deseado, o para los bordes de bloques de subpágina `mprotect` no puede " "cubrir (estos siempre están llenos de veneno y contados por separado)." -#: src/internals/memory-model.md:189 +#: src/internals/memory-model.md:174 msgid "`PERRY_GC_PROTECT_FROMSPACE_DEPTH=N`" msgstr "`PERRY_GC_PROTECT_FROMSPACE_DEPTH=N`" -#: src/internals/memory-model.md:189 +#: src/internals/memory-model.md:174 msgid "" "How many retired page-sets stay quarantined (default `4`, minimum `1`). " "Expired sets are restored to read/write and **recycled back into Eden**, " @@ -79832,11 +79638,11 @@ msgstr "" "space bytes` y ninguna página `mprotect`'d se entrega nunca al sistema de " "asignación." -#: src/internals/memory-model.md:190 +#: src/internals/memory-model.md:175 msgid "`PERRY_GC_FROMSPACE_SCAN_ABORT=1`" msgstr "`PERRY_GC_FROMSPACE_SCAN_ABORT=1`" -#: src/internals/memory-model.md:190 +#: src/internals/memory-model.md:175 msgid "" "Abort on the **first** offending slot the whole-heap from-space scan finds, " "printing slot, holder, target (including the target's `obj_type`) and a " @@ -79849,11 +79655,11 @@ msgstr "" "`PERRY_GC_FROMSPACE_SCAN=1`; anteriormente era silenciosamente inerte por sí " "solo." -#: src/internals/memory-model.md:191 +#: src/internals/memory-model.md:176 msgid "`PERRY_GC_SCHEDULE_SEED=`" msgstr "`PERRY_GC_SCHEDULE_SEED=`" -#: src/internals/memory-model.md:191 +#: src/internals/memory-model.md:176 msgid "" "**Seeded GC-schedule fuzzing** — when nursery pressure is not due, add a " "minor collection at a handled safepoint when a deterministic pseudo-random " @@ -79885,11 +79691,11 @@ msgstr "" "Compone con los dos anteriores; ese emparejamiento es lo que convierte un " "error de enraizamiento en una falla precisa inmediata." -#: src/internals/memory-model.md:192 +#: src/internals/memory-model.md:177 msgid "`PERRY_GC_SCHEDULE_RATE=<0..1>`" msgstr "`PERRY_GC_SCHEDULE_RATE=<0..1>`" -#: src/internals/memory-model.md:192 +#: src/internals/memory-model.md:177 msgid "" "Expected fraction of eligible handled safepoints that receive an " "_additional_ schedule-triggered collection (default `0.05`). Inert without a " @@ -79907,7 +79713,7 @@ msgstr "" "scavenge` de V8, y el punto donde la semilla deja de importar porque cada " "ordinal es seleccionado. Pinza de valores fuera de rango." -#: src/internals/memory-model.md:194 +#: src/internals/memory-model.md:179 msgid "" "These instruments have explicit caveats, because each has burned a prior " "investigation:" @@ -79915,7 +79721,7 @@ msgstr "" "Estos instrumentos tienen advertencias explícitas, porque cada uno ha " "quemado una investigación previa:" -#: src/internals/memory-model.md:197 +#: src/internals/memory-model.md:182 msgid "" "`PERRY_GC_PROTECT_FROMSPACE` gates **only** the copying minor's from-space " "reset. A run with the knob on and zero copying minors protects nothing. " @@ -79927,7 +79733,7 @@ msgstr "" "no protege nada. Busque una línea `[gc-fromspace-protect] retired_set=#N` " "bajo `PERRY_GC_DIAG=1`." -#: src/internals/memory-model.md:200 +#: src/internals/memory-model.md:185 #, fuzzy msgid "" "**Depth is the knob to raise when a suspected bug does not fault.** A stale " @@ -79961,7 +79767,7 @@ msgstr "" "general: profundidad ≥ el número de puntos de seguridad que sobrevive el " "valor sospechoso." -#: src/internals/memory-model.md:213 +#: src/internals/memory-model.md:198 msgid "" "`PERRY_GC_SCHEDULE_SEED` cannot select loop back-edge polls that codegen " "never produced. Those are a **compile-time** property " @@ -79989,7 +79795,7 @@ msgstr "" "hay veredicto firme (una semilla escasa legítimamente forzar nada no es un " "instrumento roto)." -#: src/internals/memory-model.md:223 +#: src/internals/memory-model.md:208 msgid "" "**`PERRY_GC_SCHEDULE_SEED`'s determinism is per-thread, and that is the " "honest scope.** The safepoint counter is thread-local: no wall clock, no " @@ -80013,7 +79819,7 @@ msgstr "" "Un contador global sería estrictamente peor — haría que incluso el programa " "de un solo hilo dependera de la interrelación. Informe qué caso ha medido." -#: src/internals/memory-model.md:232 +#: src/internals/memory-model.md:217 msgid "" "**A clean sweep means nothing without a safepoint count.** The mode prints " "`[gc-schedule] done: seed=… safepoints=… scheduled_collections=…` at exit, " @@ -80029,7 +79835,7 @@ msgstr "" "causa habitual es un binario compilado sin `PERRY_GC_MOVING_LOOP_POLLS=1`, " "que no tiene puntos de seguridad en bucle para un horario para seleccionar." -#: src/internals/memory-model.md:238 +#: src/internals/memory-model.md:223 msgid "" "**Page protection is Unix-only.** `mprotect` / `sigaction` / `sysconf` are " "not exposed by the `libc` crate on `x86_64-pc-windows-msvc`, a target `perry-" @@ -80044,11 +79850,11 @@ msgstr "" "`bytes_protected` permanece `0` mientras que `bytes_poisoned` cuenta todo el " "rango retirado." -#: src/internals/memory-model.md:244 +#: src/internals/memory-model.md:229 msgid "Why this design" msgstr "Por qué este diseño" -#: src/internals/memory-model.md:246 +#: src/internals/memory-model.md:231 msgid "" "The combination—NaN-boxing, per-thread arenas, target-aware precise roots, " "registered runtime scanners, barriers, and generational aging—is what lets " @@ -80059,23 +79865,21 @@ msgstr "" "barreras y envejecimiento generacional) es lo que permite a Perry pasar a " "través de LLVM y aún así ejecutar un montón gestionado en movimiento." -#: src/internals/memory-model.md:250 +#: src/internals/memory-model.md:235 msgid "" -"Going to native code does not preclude having a GC. It means the collector's " -"relationship with compiled code is mediated by an ABI and the selected root " -"map; the linked runtime remains a real tracing collector. There is nothing " -"reference-counted at runtime." +"Going to native code does not preclude having a GC. It means the collector's" +" relationship with compiled code is mediated by an ABI and the selected root" +" map; the linked runtime remains a real tracing collector." msgstr "" -"Ir al código nativo no impide tener un GC. Significa que la relación del " -"coleccionista con el código compilado está mediada por un ABI y el mapa raíz " -"seleccionado; el tiempo de ejecución vinculado sigue siendo un recopilador " -"real de rastreo. No hay nada de referencia en tiempo de ejecución." +"El código nativo puede tener GC. La relación entre recolector y código " +"compilado está mediada por una ABI y el mapa de raíces elegido; el runtime " +"enlazado sigue siendo un recolector por trazado real." -#: src/internals/memory-model.md:255 +#: src/internals/memory-model.md:239 msgid "Profiling Perry's memory on macOS" msgstr "Perfilando la memoria de Perry en macOS" -#: src/internals/memory-model.md:257 +#: src/internals/memory-model.md:241 msgid "" "Two things to know before reading `vmmap`, Instruments' VM Tracker, or " "`footprint` output for a Perry binary:" @@ -80083,7 +79887,7 @@ msgstr "" "Dos cosas a saber antes de leer `vmmap`, VM Tracker de instrumentos, o " "salida `footprint` para un binario Perry:" -#: src/internals/memory-model.md:260 +#: src/internals/memory-model.md:244 msgid "" "**The heap does not show up under `MALLOC_*`.** Perry routes all runtime " "allocation through mimalloc (`#[global_allocator]`, issue #62), whose " @@ -80095,7 +79899,7 @@ msgstr "" "#62), cuyos mapas aparecen como sus propias regiones anónimas, no en las " "zonas malloc del sistema." -#: src/internals/memory-model.md:264 +#: src/internals/memory-model.md:248 msgid "" "**Those regions used to render as `IOAccelerator`** — i.e. GPU driver memory " "— because mimalloc tags its mappings with VM tag 100, which macOS tooling " @@ -80114,7 +79918,7 @@ msgstr "" "usted mismo — el tiempo de ejecución se detiene a una configuración " "explícita de env." -#: src/internals/memory-model.md:272 +#: src/internals/memory-model.md:256 msgid "" "The retag runs from a `__DATA,__mod_init_func` constructor (`perry-runtime/" "src/mimalloc_os_tag.rs`), _not_ from `js_gc_init`, and that placement is " @@ -80137,7 +79941,7 @@ msgstr "" "lo movió antes de `main`. En una construcción entre esos dos, esperar ~ todo " "el montón como `IOAccelerator` y una región de `Memory Tag 240` token." -#: src/internals/memory-model.md:282 +#: src/internals/memory-model.md:266 msgid "" "So: large `IOAccelerator` regions on a current build are a _bug_, not a " "documentation caveat — most likely the module initializer was dropped from " @@ -80154,7 +79958,7 @@ msgstr "" "`mi_option_get(mi_option_os_tag)` reporta 240 en el caso roto también, por " "lo que no es un diagnóstico." -#: src/internals/memory-model.md:289 +#: src/internals/memory-model.md:273 msgid "" "Also note that mimalloc purges freed memory with `MADV_FREE`\\-style advice: " "macOS keeps such pages counted in RSS and `phys_footprint` until memory " @@ -80167,7 +79971,7 @@ msgstr "" "encabezado RSS sobrestiman lo que el proceso realmente se mantendría bajo " "presión." -#: src/internals/memory-model.md:294 +#: src/internals/memory-model.md:278 msgid "" "**`__DATA` dirty is not the inline caches any more.** Every property-access " "site used to own a 96-byte `[12 x i64]` cache global — 25 MB of `__bss` on a " @@ -80181,11 +79985,11 @@ msgid "" "that reports `0` there executed no property site that could prime." msgstr "" -#: src/internals/memory-model.md:305 +#: src/internals/memory-model.md:289 msgid "Source map" msgstr "Mapa de fuentes" -#: src/internals/memory-model.md:307 +#: src/internals/memory-model.md:291 msgid "" "Paths, not line numbers: a line number is a claim nothing re-derives, and " "every row of this table once pointed into a `gc.rs` that no longer exists." @@ -80194,183 +79998,186 @@ msgstr "" "vuelve a derivarse, y cada fila de esta tabla una vez apuntaba a un `gc.rs` " "que ya no existe." -#: src/internals/memory-model.md:310 +#: src/internals/memory-model.md:294 msgid "Topic" msgstr "Tema" -#: src/internals/memory-model.md:310 +#: src/internals/memory-model.md:294 msgid "Symbol to look for" msgstr "Símbolo a buscar" -#: src/internals/memory-model.md:312 +#: src/internals/memory-model.md:296 msgid "NaN-boxing tags" msgstr "NaN-boxing tags" -#: src/internals/memory-model.md:312 -msgid "`crates/perry-runtime/src/value/tags.rs`" +#: src/internals/memory-model.md:296 +#, fuzzy +msgid "`crates/perry-runtime/src/value/tags.rs`, `crates/perry-abi/src/lib.rs`" msgstr "`crates/perry-runtime/src/value/tags.rs`" -#: src/internals/memory-model.md:312 -msgid "`POINTER_TAG`, `STRING_TAG`" +#: src/internals/memory-model.md:296 +#, fuzzy +msgid "`POINTER_TAG`, `STRING_TAG`, `HANDLE_TAG`" msgstr "`POINTER_TAG`, `STRING_TAG`" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`GcHeader`, type/flag constants" msgstr "`GcHeader`, constantes de tipo/flag" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`crates/perry-runtime/src/gc/types.rs`" msgstr "`crates/perry-runtime/src/gc/types.rs`" -#: src/internals/memory-model.md:313 +#: src/internals/memory-model.md:297 msgid "`GcHeader`" msgstr "`GcHeader`" -#: src/internals/memory-model.md:314 +#: src/internals/memory-model.md:298 msgid "`gc_malloc`" msgstr "`gc_malloc`" -#: src/internals/memory-model.md:314 +#: src/internals/memory-model.md:298 msgid "`crates/perry-runtime/src/gc/malloc.rs`" msgstr "`crates/perry-runtime/src/gc/malloc.rs`" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "Shadow frames (fallback root lowering)" msgstr "Marcos de sombras (reducción de la raíz de retroceso)" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "`crates/perry-runtime/src/gc/roots/shadow_stack.rs`" msgstr "`crates/perry-runtime/src/gc/roots/shadow_stack.rs`" -#: src/internals/memory-model.md:315 +#: src/internals/memory-model.md:299 msgid "`js_shadow_frame_push`" msgstr "`js_shadow_frame_push`" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "Registered root scanners" msgstr "Escáneres de raíces registrados" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "`crates/perry-runtime/src/gc/roots.rs`" msgstr "`crates/perry-runtime/src/gc/roots.rs`" -#: src/internals/memory-model.md:316 +#: src/internals/memory-model.md:300 msgid "`gc_register_mutable_root_scanner`" msgstr "`gc_register_mutable_root_scanner`" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "Copying minor" msgstr "Copiar menor" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "`crates/perry-runtime/src/gc/copying.rs`" msgstr "`crates/perry-runtime/src/gc/copying.rs`" -#: src/internals/memory-model.md:317 +#: src/internals/memory-model.md:301 msgid "`move_young`" msgstr "`move_young`" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "Whole-block in-place promotion" msgstr "Promoción de todo el bloque en el lugar" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "`crates/perry-runtime/src/arena/promote.rs`" msgstr "`crates/perry-runtime/src/arena/promote.rs`" -#: src/internals/memory-model.md:318 +#: src/internals/memory-model.md:302 msgid "`finish_in_place_promotion`" msgstr "`finish_in_place_promotion`" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "Promotion policy" msgstr "Política de promoción" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "`crates/perry-runtime/src/gc/promote_in_place.rs`" msgstr "`crates/perry-runtime/src/gc/promote_in_place.rs`" -#: src/internals/memory-model.md:319 +#: src/internals/memory-model.md:303 msgid "`PROMOTE_SURVIVAL_THRESHOLD_PERMILLE`" msgstr "`PROMOTE_SURVIVAL_THRESHOLD_PERMILLE`" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "Adaptive tenuring threshold" msgstr "Umbral de mantenimiento adaptativo" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "`crates/perry-runtime/src/gc/tenuring.rs`" msgstr "`crates/perry-runtime/src/gc/tenuring.rs`" -#: src/internals/memory-model.md:320 +#: src/internals/memory-model.md:304 msgid "`tenuring_survivals`" msgstr "`tenuring_survivals`" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "Write barriers" msgstr "Barreras de escritura" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "`crates/perry-runtime/src/gc/barrier_store.rs`" msgstr "`crates/perry-runtime/src/gc/barrier_store.rs`" -#: src/internals/memory-model.md:321 +#: src/internals/memory-model.md:305 msgid "`runtime_write_barrier_slot`" msgstr "`runtime_write_barrier_slot`" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "Explicit pinning" msgstr "Pinzas explícitas" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "`crates/perry-runtime/src/gc/pin.rs`" msgstr "`crates/perry-runtime/src/gc/pin.rs`" -#: src/internals/memory-model.md:322 +#: src/internals/memory-model.md:306 msgid "`pin_object`" msgstr "`pin_object`" -#: src/internals/memory-model.md:323 +#: src/internals/memory-model.md:307 msgid "Conservative-pin predicate" msgstr "Conservador-pin predicado" -#: src/internals/memory-model.md:323 -msgid "`crates/perry-runtime/src/gc/verify.rs`" +#: src/internals/memory-model.md:307 +#, fuzzy +msgid "`crates/perry-runtime/src/gc/rewrite.rs`" msgstr "`crates/perry-runtime/src/gc/verify.rs`" -#: src/internals/memory-model.md:323 +#: src/internals/memory-model.md:307 msgid "`is_conservatively_pinned`" msgstr "`is_conservatively_pinned`" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "Collection triggers and pacing" msgstr "Detonadores de recogida y estimulación" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "`crates/perry-runtime/src/gc/policy.rs`" msgstr "`crates/perry-runtime/src/gc/policy.rs`" -#: src/internals/memory-model.md:324 +#: src/internals/memory-model.md:308 msgid "`gc_check_trigger`" msgstr "`gc_check_trigger`" -#: src/internals/memory-model.md:325 +#: src/internals/memory-model.md:309 msgid "Current operations page" msgstr "Operaciones actuales página" -#: src/internals/memory-model.md:325 +#: src/internals/memory-model.md:309 msgid "`docs/src/internals/garbage-collector.md`" msgstr "`docs/src/internals/garbage-collector.md`" -#: src/internals/memory-model.md:326 +#: src/internals/memory-model.md:310 msgid "Design plan (historical)" msgstr "Plan de diseño (histórico)" -#: src/internals/memory-model.md:326 +#: src/internals/memory-model.md:310 msgid "`docs/generational-gc-plan.md`" msgstr "`docs/generational-gc-plan.md`" -#: src/internals/memory-model.md:340 +#: src/internals/memory-model.md:324 msgid "" "Those rows are not decorative: each is bound to a `gc-symbol` marker above " "and re-derived by `scripts/check_gc_doc_claims.py`, so a rename that leaves " @@ -82356,40 +82163,15 @@ msgstr "Colector de basura: arquitectura y operaciones actuales" #: src/internals/garbage-collector.md:3 msgid "" -"**Current as of 2026-08-12.** This is the source of truth for the collector " -"that ships. The [generational plan](https://github.com/PerryTS/perry/blob/" -"main/docs/generational-gc-plan.md) and [statepoint experiment](https://" -"github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment.md) are " -"chronological evidence; their opening decisions describe the date they were " -"written, not today's defaults." -msgstr "" -"**Actual desde 2026-08-12.** Esta es la fuente de la verdad para el colector " -"que los barcos. Los [plan generacional](https://github.com/PerryTS/perry/" -"blob/main/docs/generational-gc-plan.md) y [Experimento de estado](https://" -"github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment.md) son " -"evidencia cronológica; sus decisiones de apertura describen la fecha en que " -"fueron escritos, no los defaults de hoy." - -#: src/internals/garbage-collector.md:9 -msgid "" -"This page deliberately names **no issue numbers**. A sentence of the form " -"\"that phase is still unsliced (see the tracker)\" becomes false the moment " -"the tracker closes, and nothing in the tree notices — which is exactly how " -"three paragraphs here went stale inside one landing train. State what the " -"code does; the tracker's state is not in this repository. Every number below " -"is bound to the constant it came from by a `gc-fact` marker and re-derived " -"by `scripts/check_gc_doc_claims.py`, which `lint` runs." -msgstr "" -"Esta página nombra deliberadamente **sin números de emisión**. Una frase de " -"la forma \"esa fase todavía está sin cortar (ver el rastreador)\" se vuelve " -"falsa en el momento en que el rastreador se cierra, y nada en los avisos del " -"árbol - que es exactamente cómo tres párrafos aquí se quedó rancio dentro de " -"un tren de aterrizaje. Indique lo que hace el código; el estado del " -"rastreador no está en este repositorio. Cada número de abajo está unido a la " -"constante que proviene de un marcador `gc-fact` y re-derivado por `scripts/" -"check_gc_doc_claims.py`, que `lint` ejecuta." - -#: src/internals/garbage-collector.md:18 +"**Reviewed against the code on 2026-11-01.** Historical plans record earlier" +" designs. Source paths, `gc-fact` constants and `gc-symbol` checks below " +"bind the operational description to the implementation." +msgstr "" +"**Revisado contra el código el 2026-11-01.** Los planes históricos describen" +" diseños anteriores. Las rutas, constantes `gc-fact` y comprobaciones `gc-" +"symbol` siguientes vinculan la descripción operativa con la implementación." + +#: src/internals/garbage-collector.md:5 msgid "" "Perry uses a per-thread, tracing generational collector. JavaScript values " "are NaN-boxed, allocations carry an 8-byte `GcHeader`, and each runtime " @@ -82406,11 +82188,11 @@ msgstr "" "código de bajada de raíces vive en `crates/perry-codegen/src/codegen/` y " "`native_root_coverage/`." -#: src/internals/garbage-collector.md:25 +#: src/internals/garbage-collector.md:12 msgid "Collection paths" msgstr "Rutas de recogida" -#: src/internals/garbage-collector.md:27 +#: src/internals/garbage-collector.md:14 #, fuzzy msgid "" "New GC-managed allocations normally enter nursery blocks of one aligned " @@ -82419,11 +82201,11 @@ msgstr "" "Las nuevas asignaciones gestionadas por GC normalmente entran en 1 bloque de " "viveros MiB" -#: src/internals/garbage-collector.md:30 +#: src/internals/garbage-collector.md:17 msgid "A collection can take one of three paths:" msgstr "Una colección puede tomar uno de tres caminos:" -#: src/internals/garbage-collector.md:32 +#: src/internals/garbage-collector.md:19 msgid "" "**Copying minor.** At a precise safepoint, live young objects are copied, " "roots and heap slots are rewritten, and whole from-space blocks are reset. " @@ -82436,7 +82218,7 @@ msgstr "" "promovidos a `OLD_ARENA`. Este es el camino rápido del vivero, y tiene una " "variante de bloque entero que se describe a continuación." -#: src/internals/garbage-collector.md:36 +#: src/internals/garbage-collector.md:23 msgid "" "**Non-moving minor/fallback.** When collection begins somewhere that cannot " "safely relocate every live reference, the nursery is marked and swept in " @@ -82449,7 +82231,7 @@ msgstr "" "también utilizan una ruta que no se mueve. Esta es la ruta de los " "`HAS_SURVIVED` plano → `TENURED` par de bandera envejece objetos en." -#: src/internals/garbage-collector.md:40 +#: src/internals/garbage-collector.md:27 msgid "" "**Full mark-sweep.** Major pacing, critical host pressure, explicit full " "work, or `PERRY_GEN_GC=0` trace both generations and reclaim dead old " @@ -82459,7 +82241,7 @@ msgstr "" "del huésped, el trabajo completo explícito, o `PERRY_GEN_GC=0` trazan ambas " "generaciones y recuperan objetos viejos muertos, así como basura de vivero." -#: src/internals/garbage-collector.md:44 +#: src/internals/garbage-collector.md:31 msgid "" "**Block-granular reclamation in the full sweep.** A synchronous full sweep " "reclaims an arena block without entering it when the cycle's exact pointer " @@ -82473,7 +82255,7 @@ msgid "" "and bytes on each sweep's `[gc] blocks:` line." msgstr "" -#: src/internals/garbage-collector.md:57 +#: src/internals/garbage-collector.md:44 msgid "" "**The allocation-point trigger check.** `gc_check_trigger` runs on every " "`gc_malloc` and every arena block fill, and nearly always finds nothing due. " @@ -82490,13 +82272,13 @@ msgid "" "runtime/src/gc/trigger_watermark.rs`)." msgstr "" -#: src/internals/garbage-collector.md:72 +#: src/internals/garbage-collector.md:59 msgid "" "**Per-live-object cost of a synchronous full.** Three parts of a full scale " "with the live set, and each has a cheaper exact form:" msgstr "" -#: src/internals/garbage-collector.md:75 +#: src/internals/garbage-collector.md:62 msgid "" "_Membership._ The census answers \"is this address an arena object start?\" " "from one object-start bitmap per censused block (one bit per 8-byte " @@ -82505,7 +82287,7 @@ msgid "" "one bit test." msgstr "" -#: src/internals/garbage-collector.md:80 +#: src/internals/garbage-collector.md:67 msgid "" "_Remembered-set rebuild._ Full mark drains record live old parents' strong " "old→young and old→malloc slots through the same descriptor visit that marks " @@ -82515,14 +82297,14 @@ msgid "" "already traced. No second old-generation walk is needed." msgstr "" -#: src/internals/garbage-collector.md:89 +#: src/internals/garbage-collector.md:76 msgid "" "_Sweep page accounting._ Consecutive single-page old objects are summed and " "applied to their page's metadata once, before any page-index flush that " "could zero it." msgstr "" -#: src/internals/garbage-collector.md:95 +#: src/internals/garbage-collector.md:82 msgid "" "**Promoted-cohort fulls.** Old-reclaim pacing credits every promotion to its " "growth baseline, so old-gen garbage that a minor promoted and that died " @@ -82545,7 +82327,7 @@ msgid "" "`[gc-trigger]` lines and a `[gc-promoted-cohort]` line per cohort full." msgstr "" -#: src/internals/garbage-collector.md:118 +#: src/internals/garbage-collector.md:105 msgid "" "**One pass per block in a synchronous full.** An unbudgeted census and an " "unbudgeted sweep parse each arena block themselves instead of calling the " @@ -82559,7 +82341,7 @@ msgid "" "that is not a forwarding stub without queueing it." msgstr "" -#: src/internals/garbage-collector.md:136 +#: src/internals/garbage-collector.md:123 msgid "" "`PERRY_GC_SCAVENGE` is on by default and lets nursery pressure route to the " "direct minor. `PERRY_GC_SCAVENGE_NURSERY_MB` tunes its base high-water cap, " @@ -82569,24 +82351,24 @@ msgstr "" "vivero a la menor directa. `PERRY_GC_SCAVENGE_NURSERY_MB` sintoniza su tapa " "base de agua alta, 16 MiB por defecto" -#: src/internals/garbage-collector.md:140 +#: src/internals/garbage-collector.md:127 msgid "" "The effective cap is a survival-driven ladder over that base, counted in " "quarters of it" msgstr "" -#: src/internals/garbage-collector.md:143 +#: src/internals/garbage-collector.md:130 msgid "each thread starts at a quarter of the base" msgstr "" -#: src/internals/garbage-collector.md:145 +#: src/internals/garbage-collector.md:132 msgid "" "(4 MiB) and stays there while its minors find little alive, which keeps the " "young generation's footprint small. While survivor influx stays above 4% of " "the cap the ladder grows in debounced ×2 steps, up to 4× the base" msgstr "" -#: src/internals/garbage-collector.md:149 +#: src/internals/garbage-collector.md:136 msgid "" "(64 MiB), and it shrinks back while influx stays below 1%. On survivor-heavy " "and live-set-bound workloads copying survivors, not a minor's fixed cost, " @@ -82598,7 +82380,7 @@ msgid "" "falls back to full mark-sweep." msgstr "" -#: src/internals/garbage-collector.md:158 +#: src/internals/garbage-collector.md:145 msgid "" "Old-generation page defragmentation is implemented, including the mutable-" "root rewrite contract that relocating an old page requires, but it is **opt-" @@ -82618,11 +82400,11 @@ msgstr "" "La evacuación de los viveros y el barrido normal de la vieja generación no " "se ven afectados de ninguna manera." -#: src/internals/garbage-collector.md:166 +#: src/internals/garbage-collector.md:153 msgid "Aging, promotion, and where an object is born" msgstr "Envejecimiento, promoción, y donde nace un objeto" -#: src/internals/garbage-collector.md:168 +#: src/internals/garbage-collector.md:155 msgid "" "The copying minor's tenuring threshold is **adaptive, not fixed**: it is the " "largest number of survivals `S` whose projected survivor occupancy still " @@ -82633,7 +82415,7 @@ msgstr "" "proyectada todavía se ajusta al tamaño deseado de sobreviviente, sujetado a " "un techo de 4" -#: src/internals/garbage-collector.md:172 +#: src/internals/garbage-collector.md:159 msgid "" "and it drops to 1 (promote on first copy) when a measured survival-rate lock " "shows the aging round is filtering nothing. The loop is always on and has no " @@ -82646,7 +82428,7 @@ msgstr "" "runtime/src/gc/tenuring.rs` es su definición y establece la señal feedback " "que utiliza." -#: src/internals/garbage-collector.md:177 +#: src/internals/garbage-collector.md:164 msgid "" "While the first nursery-growth sample is awaiting its first survivor round, " "copying minors keep pointer-bearing parents young for one additional round. " @@ -82664,7 +82446,7 @@ msgid "" "early." msgstr "" -#: src/internals/garbage-collector.md:191 +#: src/internals/garbage-collector.md:178 msgid "" "**Whole-block in-place promotion.** When the previous cycle measured a young-" "survival ratio at or above 95%" @@ -82672,7 +82454,7 @@ msgstr "" "**Ascenso a toda la cuadra.** Cuando el ciclo anterior midió una relación " "joven-supervivencia al 95% o por encima de éste" -#: src/internals/garbage-collector.md:194 +#: src/internals/garbage-collector.md:181 msgid "" "the next copying minor relabels the young blocks as old-gen instead of " "evacuating them object by object: nothing moves, so nothing in the heap or " @@ -82687,7 +82469,7 @@ msgstr "" "próximo ciclo en lugar de asumir— excepto en el régimen plenamente vivo al " "98% o por encima de él" -#: src/internals/garbage-collector.md:200 +#: src/internals/garbage-collector.md:187 msgid "" "where the trace itself is skipped and every object on the block is " "registered as live. Two budgets bound the retained garbage that costs: a " @@ -82697,11 +82479,11 @@ msgstr "" "vivo. Dos presupuestos unieron la basura retenida que cuesta: un límite " "máximo en bytes muertos promovidos" -#: src/internals/garbage-collector.md:204 +#: src/internals/garbage-collector.md:191 msgid "and an untraced-promotion floor" msgstr "y un piso de promoción sin rastrear" -#: src/internals/garbage-collector.md:206 +#: src/internals/garbage-collector.md:193 msgid "" "that forces a measuring cycle before the assumption can run away. A full " "collection resets both. `PERRY_GC_PROMOTE_IN_PLACE=0` reverts to object-by-" @@ -82716,67 +82498,103 @@ msgstr "" "resuelto) desactivan la ruta de forma directa en lugar de dejar de ejercer " "silenciosamente su propio sujeto." -#: src/internals/garbage-collector.md:214 -msgid "" -"**Born-old thresholds are type-dependent.** A pointer-free allocation above " -"16 KiB" -msgstr "" -"**Los umbrales de edad son dependientes del tipo.** Una asignación libre de " -"punteros superior a 16 KiB" - -#: src/internals/garbage-collector.md:217 +#: src/internals/garbage-collector.md:201 msgid "" -"is born in old-gen; a pointer-bearing one stays nursery-resident until 128 " -"KiB" +"**Size selects storage, not necessarily age.** The large-object cutoffs are " +"16 KiB for pointer-free cells and 128 KiB for pointer-bearing cells. " +"Eligible leaves and containers enter dedicated non-moving young storage: a " +"minor traces their children, reclaims dead cells, or promotes survivors at " +"the same address. Types with external backing, move hooks, or other address-" +"stability contracts retain their specialized allocation policy." msgstr "" -"nace de edad avanzada; uno que lleva punteros permanece residente en el " -"vivero hasta 128 KiB" +"**El tamaño selecciona el almacenamiento, no necesariamente la edad.** Los " +"umbrales son 16 KiB para celdas sin punteros y 128 KiB para celdas con " +"punteros. Las hojas y contenedores elegibles entran en almacenamiento joven " +"específico no móvil: una recolección menor recorre sus hijos, libera las " +"celdas muertas o promociona supervivientes sin cambiar su dirección. Los " +"tipos con almacenamiento externo, hooks de movimiento u otros contratos de " +"estabilidad de dirección conservan su política especializada." -#: src/internals/garbage-collector.md:219 +#: src/internals/garbage-collector.md:206 msgid "" -"The split matters when reading a survival ratio: a flat threshold sends the " -"intermediate backing stores of a growing array straight to old-gen, so the " -"garbage they abandon never appears in the young generation at all." -msgstr "" -"La división importa cuando se lee una relación de supervivencia: un umbral " -"plano envía las tiendas intermedias de respaldo de una matriz en crecimiento " -"directamente a la vieja generación, por lo que la basura que abandonan nunca " -"aparece en la generación joven en absoluto." - -#: src/internals/garbage-collector.md:223 +"Admission is derived from GC type descriptors in `crates/perry-" +"runtime/src/gc/types.rs`; allocation and collection live in `crates/perry-" +"runtime/src/arena/allocators.rs` and `crates/perry-" +"runtime/src/arena/young_large.rs`. Scoped JSON birth rules can keep eligible" +" storage in the ordinary nursery beyond the normal cutoff." +msgstr "" +"La admisión deriva de los descriptores GC en `crates/perry-" +"runtime/src/gc/types.rs`; la asignación y recolección están en " +"`crates/perry-runtime/src/arena/allocators.rs` y `crates/perry-" +"runtime/src/arena/young_large.rs`. Reglas acotadas de creación JSON pueden " +"mantener almacenamiento elegible en la generación joven ordinaria por encima" +" del umbral normal." + +#: src/internals/garbage-collector.md:208 +msgid "" +"The inline nursery cursor carries a permission bound at " +"`InlineArenaState.size`. It is zero while allocate-black is active; the " +"physical block extent remains in `ArenaBlock.size`. Every birth-color " +"transition and cursor refill updates this same bound. A successful positive-" +"size bump therefore proves idle birth color for a complete unpublished " +"layout, while the refused path retains ordinary coloring and mark seeding. " +"Native roots still use LLVM statepoints." +msgstr "" +"El cursor inline de la generación joven contiene un límite de permiso en " +"`InlineArenaState.size`. Vale cero durante allocate-black; la extensión " +"física sigue en `ArenaBlock.size`. Cada cambio de color inicial y recarga " +"del cursor actualiza ese límite. Un incremento positivo exitoso demuestra el" +" color inicial inactivo de un layout completo aún no publicado; el rechazo " +"conserva el coloreado y sembrado de marcas ordinarios. Las raíces nativas " +"siguen usando statepoints LLVM." + +#: src/internals/garbage-collector.md:215 +msgid "" +"For compiler-owned descriptors uniquely named in the whole-image layout " +"plan, fixed births initialize the header with the immutable template word " +"from that same plan. Opaque or ambiguous descriptors keep their checked " +"runtime load; no runtime shape lookup or validity structure is introduced." +msgstr "" +"Los descriptores del compilador con nombre único en el plan de layout de " +"toda la imagen inicializan los headers de creaciones fijas con la palabra " +"plantilla inmutable de ese plan. Los descriptores opacos o ambiguos " +"conservan su carga comprobada en ejecución; no se introduce búsqueda de " +"shapes ni estructura de validez en ejecución." + +#: src/internals/garbage-collector.md:220 msgid "Roots, by target" msgstr "Raíces, por objetivo" -#: src/internals/garbage-collector.md:225 +#: src/internals/garbage-collector.md:222 msgid "One root-set analysis feeds two lowerings:" msgstr "Un análisis de conjunto de raíces alimenta dos descensos:" -#: src/internals/garbage-collector.md:227 +#: src/internals/garbage-collector.md:224 msgid "target" msgstr "target" -#: src/internals/garbage-collector.md:227 +#: src/internals/garbage-collector.md:224 msgid "shipped precise-root lowering" msgstr "Rebaja de raíz precisa enviada" -#: src/internals/garbage-collector.md:229 +#: src/internals/garbage-collector.md:226 msgid "64-bit AArch64/arm64 and x86-64, including x86-64 Windows" msgstr "AArch64/arm64 y x86-64 de 64 bits, incluidos x86-64 Windows" -#: src/internals/garbage-collector.md:229 +#: src/internals/garbage-collector.md:226 msgid "LLVM RS4GC statepoints plus Perry's compact native stack map" msgstr "" "LLVM Puntos de estado RS4GC más el mapa de pila nativo compacto de Perry" -#: src/internals/garbage-collector.md:230 +#: src/internals/garbage-collector.md:227 msgid "wasm32 WASI and ARM64 Windows" msgstr "" -#: src/internals/garbage-collector.md:230 +#: src/internals/garbage-collector.md:227 msgid "Perry shadow frames" msgstr "Perry marcos de sombra" -#: src/internals/garbage-collector.md:232 +#: src/internals/garbage-collector.md:229 msgid "" "Rooting is selected by the target. Native platforms always use LLVM " "statepoints; environment controls and function-size estimates cannot select " @@ -82787,7 +82605,7 @@ msgid "" "rewrites those same locations before generated code reloads them." msgstr "" -#: src/internals/garbage-collector.md:240 +#: src/internals/garbage-collector.md:237 msgid "" "WASI uses platform shadow frames because it has no native frame walker or " "statepoint stack-map backend. ARM64 Windows lacks the required native " @@ -82795,7 +82613,7 @@ msgid "" "compiler emission rather than pretending its LP64 offsets work." msgstr "" -#: src/internals/garbage-collector.md:245 +#: src/internals/garbage-collector.md:242 msgid "" "Runtime-owned roots do not live in generated frames. Registered scanners " "visit module globals, pending async work, caches, registries, and other side " @@ -82811,7 +82629,7 @@ msgstr "" "`RuntimeHandle` y deben releer un mango después de una llamada que pueda " "recoger." -#: src/internals/garbage-collector.md:251 +#: src/internals/garbage-collector.md:248 msgid "" "Root owners supply the representation. Generated globals and native " "statepoint homes keep JSValue words at rest; raw managed locals are boxed " @@ -82839,7 +82657,7 @@ msgid "" "the same producer contract." msgstr "" -#: src/internals/garbage-collector.md:274 +#: src/internals/garbage-collector.md:271 msgid "" "Legacy shadow-frame homes on WASI and ARM64 Windows can still mix raw " "pointers and JSValues. Their source kind retains the mixed decoder and " @@ -82850,7 +82668,7 @@ msgid "" "rather than validating every root that aliases it." msgstr "" -#: src/internals/garbage-collector.md:282 +#: src/internals/garbage-collector.md:279 msgid "" "The conservative native-stack scan is not part of the production default: " "`Auto` resolves to `SkipDisabled`. `PERRY_CONSERVATIVE_STACK_SCAN=full` is " @@ -82865,11 +82683,11 @@ msgstr "" "la copia menor no elegible; es una evidencia útil, no un segundo motor de " "enraizamiento normal." -#: src/internals/garbage-collector.md:288 +#: src/internals/garbage-collector.md:285 msgid "Barriers and weak references" msgstr "Barreras y referencias débiles" -#: src/internals/garbage-collector.md:290 +#: src/internals/garbage-collector.md:287 msgid "" "Every old-to-young pointer publication must hit the remembered-set barrier. " "Codegen emits barriers for generated heap stores and runtime helpers perform " @@ -82882,7 +82700,7 @@ msgstr "" "propias tiendas exactas. Un rastro menor recordaba a viejos padres en vez de " "rastrear a toda la vieja generación." -#: src/internals/garbage-collector.md:295 +#: src/internals/garbage-collector.md:292 msgid "" "WeakRef, WeakMap, WeakSet, and FinalizationRegistry targets are excluded " "from the strong trace. Weak processing is **registry-scoped on every path**: " @@ -82903,11 +82721,11 @@ msgstr "" "lugar de O(arena) y un ciclo presupuestado puede volver al mutante entre los " "titulares." -#: src/internals/garbage-collector.md:305 +#: src/internals/garbage-collector.md:302 msgid "Budgets, memory pressure, and released blocks" msgstr "Presupuestos, presión de memoria y bloques liberados" -#: src/internals/garbage-collector.md:307 +#: src/internals/garbage-collector.md:304 msgid "" "The collector derives a heap budget, in priority order, from " "`PERRY_GC_HEAP_LIMIT` (MiB), Apple embedded available-memory APIs, container " @@ -82922,15 +82740,15 @@ msgstr "" "aplazamiento de guarderías y umbrales de presión RSS hacia abajo; los " "valores predeterminados de desktop/server permanecen inalterados." -#: src/internals/garbage-collector.md:313 +#: src/internals/garbage-collector.md:310 msgid "Platform hosts call `js_gc_memory_pressure(level)`:" msgstr "Plataforma hosts llama a `js_gc_memory_pressure(level)`:" -#: src/internals/garbage-collector.md:315 +#: src/internals/garbage-collector.md:312 msgid "warning (`1`) requests a prompt minor;" msgstr "warning (`1`) requests a prompt minor;" -#: src/internals/garbage-collector.md:316 +#: src/internals/garbage-collector.md:313 msgid "" "critical (`2+`) requests a full collection so old-gen garbage and idle arena " "blocks can be reclaimed;" @@ -82938,7 +82756,7 @@ msgstr "" "Critical (`2+`) solicita una recogida completa para recuperar la basura de " "la vieja generación y los bloques de arena inactivos;" -#: src/internals/garbage-collector.md:318 +#: src/internals/garbage-collector.md:315 msgid "" "if collection is unsafe, the request is made sticky and drains at the next " "precise safepoint/allocation check." @@ -82946,22 +82764,22 @@ msgstr "" "if collection is unsafe, the request is made sticky and drains at the next " "precise safepoint/allocation check." -#: src/internals/garbage-collector.md:321 +#: src/internals/garbage-collector.md:318 msgid "" -"Released 1 MiB blocks first enter a **per-thread LIFO reuse pool under a " -"process-wide byte cap**. The reuse order is thread-local; the budget is not " -"— a single global reservation is what bounds N live agents to one allowance " -"instead of N of them. The cap is derived by `gc_block_pool_cap_with_budget` " -"in `crates/perry-runtime/src/gc/heap_budget.rs`: 64 MiB" +"Released regular arena blocks first enter a **per-thread LIFO reuse pool " +"under a process-wide byte cap**. The reuse order is thread-local; the budget" +" is not — a single global reservation is what bounds N live agents to one " +"allowance instead of N of them. The cap is derived by " +"`gc_block_pool_cap_with_budget` in `crates/perry-" +"runtime/src/gc/heap_budget.rs`: 64 MiB" msgstr "" -"Lanzado 1 MiB bloques primero entrar en una **Grupo de reutilización LIFO " -"por hilo bajo un tapón de bytes de todo el proceso**. La orden de " -"reutilización es thread-local; el presupuesto no lo es — una única reserva " -"global es lo que limita a N agentes vivos a una asignación en lugar de N de " -"ellos. La tapa se deriva de `gc_block_pool_cap_with_budget` en `crates/perry-" +"Los bloques normales liberados entran primero en un **pool LIFO por hilo con" +" límite de bytes para todo el proceso**. El orden es local al hilo; el " +"presupuesto no: una reserva global limita N agentes a una única cuota. El " +"límite lo deriva `gc_block_pool_cap_with_budget` en `crates/perry-" "runtime/src/gc/heap_budget.rs`: 64 MiB" -#: src/internals/garbage-collector.md:327 +#: src/internals/garbage-collector.md:323 #, fuzzy msgid "" "on an unconstrained desktop/server process, scaled to one eighth of a device/" @@ -82982,7 +82800,7 @@ msgstr "" "unsafe/deferred y vacía la piscina cuando la colección completa debe " "terminar su recuperación de arena." -#: src/internals/garbage-collector.md:339 +#: src/internals/garbage-collector.md:335 msgid "" "Reported heap usage (`js_arena_stats`, `process.memoryUsage().heapUsed`) is " "an exact post-collection **live census** plus incremental deltas, not a sum " @@ -82992,11 +82810,11 @@ msgstr "" "heapUsed`) es una recogida exacta **censo en vivo** más deltas " "incrementales, no una suma de bloques de compensación de agua alta." -#: src/internals/garbage-collector.md:343 +#: src/internals/garbage-collector.md:339 msgid "Idle-time reclaim" msgstr "" -#: src/internals/garbage-collector.md:345 +#: src/internals/garbage-collector.md:341 msgid "" "Every automatic collection above is scheduled by allocation, so a program " "that bursts and then goes quiet keeps whatever the last mid-burst cycle left " @@ -83007,40 +82825,40 @@ msgid "" "completed since its last full, and 3000 ms" msgstr "" -#: src/internals/garbage-collector.md:353 +#: src/internals/garbage-collector.md:349 msgid "" "have passed since that collection was observed, it opens a full budgeted " "cycle with the block-pool drain armed and steps it in 4000 µs" msgstr "" -#: src/internals/garbage-collector.md:356 +#: src/internals/garbage-collector.md:352 msgid "" "slices, returning to the loop the moment a wake is pending. Two reducer " "fulls are never closer than 10000 ms" msgstr "" -#: src/internals/garbage-collector.md:359 +#: src/internals/garbage-collector.md:355 msgid "apart. A full that lowers old-gen occupancy by less than 5 percent" msgstr "" -#: src/internals/garbage-collector.md:361 +#: src/internals/garbage-collector.md:357 msgid "of what it started with (or by less than 4 MiB" msgstr "" -#: src/internals/garbage-collector.md:363 +#: src/internals/garbage-collector.md:359 msgid "" ") doubles the number of collections required before the next attempt, up to " "32" msgstr "" -#: src/internals/garbage-collector.md:365 +#: src/internals/garbage-collector.md:361 msgid "" "(two to that power); a productive full resets the requirement to one. The " "same slicing finishes a budgeted cycle the pacer left open when the mutator " "went quiet." msgstr "" -#: src/internals/garbage-collector.md:369 +#: src/internals/garbage-collector.md:365 msgid "" "Arena capacity has a second re-arm signal because returning a proven-empty " "block deliberately takes two full collection observations, while the " @@ -83048,157 +82866,157 @@ msgid "" "capacity floor," msgstr "" -#: src/internals/garbage-collector.md:373 +#: src/internals/garbage-collector.md:369 msgid "two consecutive post-collection live/capacity readings" msgstr "" -#: src/internals/garbage-collector.md:375 +#: src/internals/garbage-collector.md:371 msgid "at or below 50 percent" msgstr "" -#: src/internals/garbage-collector.md:377 +#: src/internals/garbage-collector.md:373 msgid "" "open a bounded right-size episode. It grants only enough idle fulls to reach " "two full observations" msgstr "" -#: src/internals/garbage-collector.md:380 +#: src/internals/garbage-collector.md:376 msgid "" "(counting any full already in the low-utilization streak), and stops early " "once live data reaches 60 percent of capacity." msgstr "" -#: src/internals/garbage-collector.md:383 +#: src/internals/garbage-collector.md:379 msgid "" "After a bounded episode, stable low occupancy cannot immediately repeat it: " "a new episode re-arms only after utilization reaches 70 percent" msgstr "" -#: src/internals/garbage-collector.md:386 +#: src/internals/garbage-collector.md:382 msgid "or capacity grows by at least both 25 percent and 8 MiB." msgstr "" -#: src/internals/garbage-collector.md:389 +#: src/internals/garbage-collector.md:385 msgid "" "That hysteresis preserves burst headroom and prevents an unreleasably " "fragmented heap from buying a full every ten seconds. The right-size debt " "still passes through the reducer's quiet, rate, wake, and work-budget gates." msgstr "" -#: src/internals/garbage-collector.md:394 +#: src/internals/garbage-collector.md:390 msgid "" "`PERRY_GC_DIAG=1` reports the reducer's counters on the `[gc-idle-reclaim]` " "exit line, and `PERRY_GC_IDLE_RECLAIM=0` disables it." msgstr "" -#: src/internals/garbage-collector.md:399 +#: src/internals/garbage-collector.md:395 msgid "Supported controls" msgstr "Controles soportados" -#: src/internals/garbage-collector.md:401 +#: src/internals/garbage-collector.md:397 msgid "" "These are the operational controls most useful outside collector development:" msgstr "" "Estos son los controles operativos más útiles para el desarrollo de " "colectores externos:" -#: src/internals/garbage-collector.md:403 +#: src/internals/garbage-collector.md:399 msgid "knob" msgstr "knob" -#: src/internals/garbage-collector.md:403 +#: src/internals/garbage-collector.md:399 msgid "purpose" msgstr "purpose" -#: src/internals/garbage-collector.md:405 +#: src/internals/garbage-collector.md:401 msgid "`PERRY_GEN_GC=0`" msgstr "`PERRY_GEN_GC=0`" -#: src/internals/garbage-collector.md:405 +#: src/internals/garbage-collector.md:401 msgid "bisection fallback to full mark-sweep" msgstr "reflujo de la bisección al barrido completo de marcas" -#: src/internals/garbage-collector.md:406 +#: src/internals/garbage-collector.md:402 msgid "`PERRY_WRITE_BARRIERS=0`" msgstr "`PERRY_WRITE_BARRIERS=0`" -#: src/internals/garbage-collector.md:406 +#: src/internals/garbage-collector.md:402 msgid "compile/runtime barrier bisection; also forces full mark-sweep" msgstr "" "compile/runtime bisección de barrera; también fuerza la marca-barra completa" -#: src/internals/garbage-collector.md:407 +#: src/internals/garbage-collector.md:403 msgid "`PERRY_GC_SCAVENGE=0`" msgstr "`PERRY_GC_SCAVENGE=0`" -#: src/internals/garbage-collector.md:407 +#: src/internals/garbage-collector.md:403 msgid "disable direct nursery scavenging for pacing comparison" msgstr "" "deshabilitar la recolección directa de viveros para la comparación de " "estimulación" -#: src/internals/garbage-collector.md:408 +#: src/internals/garbage-collector.md:404 msgid "`PERRY_GC_PROMOTE_IN_PLACE=0`" msgstr "`PERRY_GC_PROMOTE_IN_PLACE=0`" -#: src/internals/garbage-collector.md:408 +#: src/internals/garbage-collector.md:404 msgid "revert whole-block promotion to object-by-object evacuation" msgstr "" "revertir la promoción de bloques enteros a la evacuación de objetos por " "objetos" -#: src/internals/garbage-collector.md:409 +#: src/internals/garbage-collector.md:405 msgid "`PERRY_GC_SCAVENGE_NURSERY_MB=N`" msgstr "`PERRY_GC_SCAVENGE_NURSERY_MB=N`" -#: src/internals/garbage-collector.md:409 +#: src/internals/garbage-collector.md:405 msgid "set the base nursery cap" msgstr "establecer la tapa base del vivero" -#: src/internals/garbage-collector.md:410 +#: src/internals/garbage-collector.md:406 msgid "`PERRY_GC_HEAP_LIMIT=N`" msgstr "`PERRY_GC_HEAP_LIMIT=N`" -#: src/internals/garbage-collector.md:410 +#: src/internals/garbage-collector.md:406 msgid "override the process heap budget in MiB" msgstr "anular el presupuesto del montón de proceso en MiB" -#: src/internals/garbage-collector.md:411 +#: src/internals/garbage-collector.md:407 msgid "`PERRY_CONSERVATIVE_STACK_SCAN=full`" msgstr "`PERRY_CONSERVATIVE_STACK_SCAN=full`" -#: src/internals/garbage-collector.md:411 +#: src/internals/garbage-collector.md:407 msgid "diagnostic full native-stack scan; disables copying" msgstr "" "diagnóstico de exploración completa de pila nativa; deshabilita la copia" -#: src/internals/garbage-collector.md:412 +#: src/internals/garbage-collector.md:408 msgid "`PERRY_GC_TRACE=1`" msgstr "`PERRY_GC_TRACE=1`" -#: src/internals/garbage-collector.md:412 +#: src/internals/garbage-collector.md:408 msgid "emit structured per-cycle trace records" msgstr "emitir registros estructurados por ciclo de seguimiento" -#: src/internals/garbage-collector.md:413 +#: src/internals/garbage-collector.md:409 msgid "" "emit human-readable collector diagnostics (per cycle, plus `[gc-trigger]`/" "`[gc-full]`/`[gc-budgeted]`/`[gc-charge]` decision and charge attribution " "and the per-minor `[gc-survival]` root attribution)" msgstr "" -#: src/internals/garbage-collector.md:414 +#: src/internals/garbage-collector.md:410 #, fuzzy msgid "`PERRY_ALLOC_SITE_SAMPLE=N`" msgstr "`PERRY_GC_HEAP_LIMIT=N`" -#: src/internals/garbage-collector.md:414 +#: src/internals/garbage-collector.md:410 msgid "" "sample the arena allocation-site histogram every N bytes (`[alloc-site]`); " "`1`/`on` selects the default interval" msgstr "" -#: src/internals/garbage-collector.md:416 +#: src/internals/garbage-collector.md:412 #, fuzzy msgid "" "Rooting stress uses `PERRY_GC_SCHEDULE_SEED`, `PERRY_GC_SCHEDULE_RATE`, " @@ -83228,7 +83046,7 @@ msgstr "" "`PERRY_STACKMAP_WALKER`, pero no son modos de colector adicionales " "soportados." -#: src/internals/garbage-collector.md:430 +#: src/internals/garbage-collector.md:426 msgid "" "`scripts/check_gc_env_knobs.py` derives the accepted names from live runtime/" "codegen/compiler parsers and rejects a current document, executable script, " @@ -83257,11 +83075,11 @@ msgstr "" "el comportamiento falla `cargo-test`; ninguno puede quedarse en silencio " "mientras esta página sigue reclamando lo viejo." -#: src/internals/garbage-collector.md:442 +#: src/internals/garbage-collector.md:438 msgid "Validation and CI authority" msgstr "Validación y autoridad de IC" -#: src/internals/garbage-collector.md:444 +#: src/internals/garbage-collector.md:440 msgid "" "As of 2026-08-16, branch protection requires exactly one status, `pr-gate` — " "the fan-in of `test.yml`'s PR tier (see `docs/src/testing/ci-tiers.md`; the " @@ -83273,39 +83091,39 @@ msgstr "" "src/testing/ci-tiers.md`; la política de nivel es `scripts/ci_plan.py`). La " "cobertura específica del GC se divide deliberadamente:" -#: src/internals/garbage-collector.md:448 +#: src/internals/garbage-collector.md:444 msgid "where it runs" msgstr "donde corre" -#: src/internals/garbage-collector.md:448 +#: src/internals/garbage-collector.md:444 msgid "required status" msgstr "estado requerido" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "root-holder custody, GC-knob drift, and this page's path/number claims" msgstr "" "La custodia del titular de la raíz, la deriva de GC-knob, y las " "reclamaciones path/number de esta página" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "`test.yml` → `lint`" msgstr "`test.yml` → `lint`" -#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:446 msgid "yes (via `pr-gate`)" msgstr "Sí (a través de `pr-gate`)" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "runtime unit suite and `run_memory_stability_tests.sh` four-mode matrix" msgstr "" "unidad de tiempo de ejecución suite y `run_memory_stability_tests.sh` matriz " "de cuatro modos" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "`test.yml` → `cargo-test`" msgstr "`test.yml` → `cargo-test`" -#: src/internals/garbage-collector.md:451 +#: src/internals/garbage-collector.md:447 msgid "" "yes (via `pr-gate`; the PR tier is diff-scoped, the sweep/full tiers run the " "workspace)" @@ -83313,7 +83131,7 @@ msgstr "" "sí (a través de `pr-gate`; el nivel PR es diff-scoped, los niveles sweep/" "full ejecutan el espacio de trabajo)" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "" "GC × representation-selection matrix, rooting-bug instruments, write-barrier " "stress" @@ -83321,27 +83139,27 @@ msgstr "" "CG × matriz de selección de representación, instrumentos de enraizamiento-" "bug, esfuerzo de barrera de escritura" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "`test.yml` → `gc-stress`" msgstr "`test.yml` → `gc-stress`" -#: src/internals/garbage-collector.md:452 +#: src/internals/garbage-collector.md:448 msgid "yes (via `pr-gate`; PR subset on PRs, full matrix in the sweep)" msgstr "" "sí (a través de `pr-gate`; subconjunto PR en PRs, matriz completa en el " "barrido)" -#: src/internals/garbage-collector.md:453 +#: src/internals/garbage-collector.md:449 msgid "emitted root dominance, including native statepoint IR" msgstr "dominio de la raíz emitido, incluyendo el estado nativo IR" -#: src/internals/garbage-collector.md:453 +#: src/internals/garbage-collector.md:449 msgid "`gc-root-dominance.yml`" msgstr "`gc-root-dominance.yml`" -#: src/internals/garbage-collector.md:453 -#: src/internals/garbage-collector.md:454 -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:449 +#: src/internals/garbage-collector.md:450 +#: src/internals/garbage-collector.md:451 msgid "" "not branch-required; PR arm opt-in via `run-extended-tests`, six-hourly on " "`main`" @@ -83349,27 +83167,27 @@ msgstr "" "no se requiere rama; brazo PR opt-in vía `run-extended-tests`, seis horas en " "`main`" -#: src/internals/garbage-collector.md:454 +#: src/internals/garbage-collector.md:450 msgid "pinned collector counters/RSS/wall matrix" msgstr "Colector empotrado counters/RSS/wall matriz" -#: src/internals/garbage-collector.md:454 +#: src/internals/garbage-collector.md:450 msgid "`gc-ratchet.yml`" msgstr "`gc-ratchet.yml`" -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:451 msgid "thread-local mechanism/policy budget" msgstr "presupuesto local mechanism/policy" -#: src/internals/garbage-collector.md:455 +#: src/internals/garbage-collector.md:451 msgid "`tls-budget.yml`" msgstr "`tls-budget.yml`" -#: src/internals/garbage-collector.md:457 +#: src/internals/garbage-collector.md:453 msgid "Useful local preflight commands:" msgstr "Útiles comandos locales de prevuelo:" -#: src/internals/garbage-collector.md:470 +#: src/internals/garbage-collector.md:466 msgid "" "The dedicated performance/rooting workflows have broader compiler and host " "requirements; their workflow files are the authority for exact commands and " @@ -83379,11 +83197,11 @@ msgstr "" "amplios de compilador y host; sus archivos de flujo de trabajo son la " "autoridad para órdenes exactas y filtros de relevancia." -#: src/internals/garbage-collector.md:474 +#: src/internals/garbage-collector.md:470 msgid "Historical evidence" msgstr "Pruebas históricas" -#: src/internals/garbage-collector.md:476 +#: src/internals/garbage-collector.md:472 msgid "" "[Generational GC plan](https://github.com/PerryTS/perry/blob/main/docs/" "generational-gc-plan.md): original design and phase-by-phase landing log." @@ -83392,7 +83210,7 @@ msgstr "" "generational-gc-plan.md): diseño original y registro de aterrizaje fase por " "fase." -#: src/internals/garbage-collector.md:478 +#: src/internals/garbage-collector.md:474 msgid "" "[Statepoint GC experiment](https://github.com/PerryTS/perry/blob/main/docs/" "statepoint-gc-experiment.md): chronological prototype measurements and " @@ -83402,7 +83220,7 @@ msgstr "" "statepoint-gc-experiment.md): mediciones y correcciones cronológicas de " "prototipos que conducen al valor predeterminado de raíz nativa." -#: src/internals/garbage-collector.md:480 +#: src/internals/garbage-collector.md:476 msgid "" "[GC rooting invariant](gc-rooting-invariant.md): current codegen soundness " "rule, checker modes, known blind spots, and debugging instruments." @@ -83411,7 +83229,7 @@ msgstr "" "sonido del códice, modos de verificación, puntos ciegos conocidos e " "instrumentos de depuración." -#: src/internals/garbage-collector.md:482 +#: src/internals/garbage-collector.md:478 msgid "" "[Memory Model](memory-model.md): NaN-boxing, allocation representation, and " "platform memory-tooling notes." @@ -85651,6 +85469,103 @@ msgstr "" "**En caso de duda, enraízalo.** Una ranura redundante cuesta una tienda. Una " "falta cuesta un día, y le cuesta a quien golpea el accidente, no a ti." +#: src/internals/program-facts.md:3 +msgid "" +"`perry-wpfacts` analyzes lowered HIR value flow, not declared TypeScript " +"types. The default driver solves per-module units and exchanges contracts to" +" a fixed point. `PERRY_FACTS_UNITS=0` selects the whole-program reference " +"solve; `PERRY_PROGRAM_FACTS=0` disables the pass. Work or memory limits " +"discard unproved results and preserve generic behavior." +msgstr "" +"`perry-wpfacts` analiza el flujo de valores del HIR bajado, no los tipos " +"declarados. Por defecto, el controlador resuelve unidades por módulo e " +"intercambia contratos hasta un punto fijo. `PERRY_FACTS_UNITS=0` selecciona " +"la resolución global de referencia; `PERRY_PROGRAM_FACTS=0` desactiva el " +"pase. Los límites de trabajo o memoria descartan resultados no demostrados y" +" conservan el comportamiento genérico." + +#: src/internals/program-facts.md:5 +msgid "" +"Facts describe field writers, definite birth keys, value kinds, escaping " +"objects, stable field prefixes, fixed layouts, receiver sets and function " +"targets. A known field offset does not alone prove property presence: " +"declared or reserved fields may still contain a hole. Typed-array facts also" +" require evidence about exposure, detachment and resizing." +msgstr "" +"Los hechos describen escritores de campos, claves seguras al crear, clases " +"de valores, objetos que escapan, prefijos estables de campos, layouts fijos," +" receptores y destinos de funciones. Conocer un offset no demuestra la " +"presencia de la propiedad: los campos declarados o reservados pueden " +"contener un hueco. Los hechos de arrays tipados requieren además evidencia " +"de exposición, desconexión y cambio de tamaño." + +#: src/internals/program-facts.md:7 +msgid "" +"Codegen consumes these facts today. A proven receiver and field can use a " +"fixed-offset load or store; multiple layouts may require class selection. " +"Uncertain receivers retain guards and a complete generic fallback. " +"Interpreted code is modeled as unknown behavior, with prototype-write guards" +" where required. Pointer stores still need GC barriers, and managed values " +"still need roots." +msgstr "" +"Codegen ya consume estos hechos. Un receptor y campo demostrados permiten " +"cargas o escrituras con offset fijo; distintos layouts pueden requerir " +"selección de clase. Los receptores inciertos conservan comprobaciones y una " +"alternativa genérica completa. El código interpretado se modela como " +"comportamiento desconocido, con comprobaciones de escritura de prototipos " +"donde corresponda. Las escrituras de punteros siguen necesitando barreras " +"GC, y los valores administrados, raíces." + +#: src/internals/program-facts.md:9 +msgid "" +"Local initializer evidence and whole-program facts are distinct analyses. An" +" annotation alone cannot justify unboxed arithmetic, a field load or removal" +" of a GC root. Native scalar and packed-array paths need their own " +"representation, mutation and escape proofs; there is no blanket promise that" +" all TypeScript values remain boxed or become unboxed." +msgstr "" +"La evidencia de inicializadores locales y los hechos globales son análisis " +"distintos. Una anotación no justifica aritmética sin empaquetado, cargas de " +"campos ni eliminar una raíz GC. Las rutas de escalares nativos y arrays " +"compactos necesitan pruebas propias de representación, mutación y escape; no" +" se promete que todos los valores TypeScript estén siempre empaquetados o " +"desempaquetados." + +#: src/internals/program-facts.md:11 +msgid "" +"Use `PERRY_FACTS_DUMP=` for analysis and elision records, " +"`PERRY_FACTS_CONSUMED=` for source sites consuming facts, and " +"`PERRY_FACTS_REPORT=1` for solver statistics. `PERRY_FACTS_ELIDE=0` keeps " +"analysis but disables its check elision. Contract builds " +"(`PERRY_CHECK_CONTRACTS=1`, runtime `contract-checks`) validate claims " +"against actual objects. [Fallback diagnostics](fallback-diagnostics.md) " +"explain retained generic operations." +msgstr "" +"Use `PERRY_FACTS_DUMP=` para registros de análisis y eliminación de " +"comprobaciones, `PERRY_FACTS_CONSUMED=` para los sitios que consumen " +"hechos y `PERRY_FACTS_REPORT=1` para estadísticas. `PERRY_FACTS_ELIDE=0` " +"mantiene el análisis pero desactiva la eliminación de comprobaciones. Las " +"compilaciones de contratos (`PERRY_CHECK_CONTRACTS=1`, runtime `contract-" +"checks`) validan las afirmaciones con objetos reales. Los [diagnósticos de " +"fallback](fallback-diagnostics.md) explican las operaciones genéricas " +"conservadas." + +#: src/internals/program-facts.md:13 +msgid "" +"The implementation starts in " +"`crates/perry/src/commands/compile/program_facts.rs` and `crates/perry-" +"wpfacts/src/`. Codegen receives `ProgramFactsView` in `crates/perry-" +"codegen/src/codegen/program_facts_view.rs`; field consumers live in " +"`crates/perry-codegen/src/expr/wpfacts_access.rs`. Fact digests participate " +"in object-cache identity." +msgstr "" +"La implementación comienza en " +"`crates/perry/src/commands/compile/program_facts.rs` y `crates/perry-" +"wpfacts/src/`. Codegen recibe `ProgramFactsView` en `crates/perry-" +"codegen/src/codegen/program_facts_view.rs`; los consumidores de campos están" +" en `crates/perry-codegen/src/expr/wpfacts_access.rs`. Los resúmenes de " +"hechos forman parte de la identidad de la caché de objetos." + #: src/internals/local-binding-type-evidence.md:3 msgid "" "Perry compiles TypeScript, but TypeScript annotations do not exist at " @@ -85850,10 +85765,19 @@ msgstr "" "de NaN." #: src/internals/local-binding-type-evidence.md:79 +msgid "" +"[Program facts](program-facts.md) provide a separate cross-module analysis. " +"Local initializer evidence is not the sole source of specialization proofs." +msgstr "" +"Los [hechos del programa](program-facts.md) aportan un análisis separado " +"entre módulos. La evidencia de inicializadores locales no es la única fuente" +" de pruebas de especialización." + +#: src/internals/local-binding-type-evidence.md:81 msgid "Static inventory and CI gate" msgstr "Inventario estático y puerta CI" -#: src/internals/local-binding-type-evidence.md:81 +#: src/internals/local-binding-type-evidence.md:83 msgid "" "`scripts/local_binding_type_audit.py` scans production HIR/codegen sources " "for both accessors and for remaining lower-level type-map reads. Its count-" @@ -85864,17 +85788,17 @@ msgstr "" "nivel inferior restantes. Su inventario exacto es `scripts/" "local_binding_type_allowlist.json`." -#: src/internals/local-binding-type-evidence.md:85 +#: src/internals/local-binding-type-evidence.md:87 msgid "Each inventory group states one of three verdicts:" msgstr "Cada grupo de inventario establece uno de los tres veredictos:" -#: src/internals/local-binding-type-evidence.md:87 +#: src/internals/local-binding-type-evidence.md:89 msgid "`runtime-validated`: emitted code checks the current runtime value;" msgstr "" "`runtime-validated`: el código emitido comprueba el valor actual del tiempo " "de ejecución;" -#: src/internals/local-binding-type-evidence.md:88 +#: src/internals/local-binding-type-evidence.md:90 msgid "" "`representation-proven`: another analysis establishes the storage fact and " "invalidates it on writes;" @@ -85882,14 +85806,14 @@ msgstr "" "`representation-proven`: otro análisis establece el hecho de almacenamiento " "y lo invalida en las escrituras;" -#: src/internals/local-binding-type-evidence.md:90 +#: src/internals/local-binding-type-evidence.md:92 msgid "" "`metadata-only`: the type read cannot select a runtime answer or layout." msgstr "" "`metadata-only`: el tipo de lectura no puede seleccionar una respuesta o " "diseño en tiempo de ejecución." -#: src/internals/local-binding-type-evidence.md:92 +#: src/internals/local-binding-type-evidence.md:94 msgid "" "The gate fails when a consumer is added without a verdict, when a count " "changes, when an entry matches nothing, or when code bypasses the accessors " @@ -85904,7 +85828,7 @@ msgstr "" "de inventario, y un escaneo vacío, por lo que un escáner rancio no puede " "informar de un éxito vacuo." -#: src/internals/local-binding-type-evidence.md:98 +#: src/internals/local-binding-type-evidence.md:100 msgid "Run it locally with:" msgstr "Ejecutarlo localmente con:" @@ -87073,9 +86997,9 @@ msgstr "" msgid "" "`#[deny]` the escape hatch per-module as each module finishes, so migrated " "code cannot regress. **Done, as a test rather than an attribute**: Rust has " -"no `#[deny]` for \"do not call this `pub(crate)` function from this " -"module\", so `rooting::migration_ledger` `include_str!`s each finished " -"module and fails the build if it names `expr::temp_root`." +"no `#[deny]` for \"do not call this `pub(crate)` function from this module" +"\", so `rooting::migration_ledger` `include_str!`s each finished module and " +"fails the build if it names `expr::temp_root`." msgstr "" "`#[deny]` la escotilla de escape por módulo a medida que termina cada " "módulo, por lo que el código migrado no puede retroceder. **Hecho, como " @@ -87538,9 +87462,9 @@ msgstr "¿Qué hacen los estados a este RFC" #: src/internals/rfc-rooting-by-construction.md:372 msgid "" -"Adopting layer 2 **deletes several of this document's \"what it cannot " -"catch\" entries rather than mitigating them**, because the shadow frame " -"stops existing:" +"Adopting layer 2 **deletes several of this document's \"what it cannot catch" +"\" entries rather than mitigating them**, because the shadow frame stops " +"existing:" msgstr "" "Capa de adopción 2 **elimina varias de las entradas de este documento \"lo " "que no puede capturar\" en lugar de mitigarlas**, porque el marco de sombra " @@ -90127,52 +90051,70 @@ msgstr "" msgid "Compilation Pipeline" msgstr "Pipeline de compilación" -#: src/contributing/architecture.md:22 +#: src/contributing/architecture.md:23 msgid "Crate Map" msgstr "Mapa de crates" -#: src/contributing/architecture.md:26 +#: src/contributing/architecture.md:27 msgid "CLI driver, command parsing, compilation orchestration" msgstr "Driver de CLI, análisis de comandos, orquestación de compilación" -#: src/contributing/architecture.md:27 +#: src/contributing/architecture.md:28 msgid "`perry-parser`" msgstr "`perry-parser`" -#: src/contributing/architecture.md:27 +#: src/contributing/architecture.md:28 msgid "SWC wrapper for TypeScript parsing" msgstr "Wrapper de SWC para el análisis de TypeScript" -#: src/contributing/architecture.md:28 +#: src/contributing/architecture.md:29 msgid "`perry-hir`" msgstr "`perry-hir`" -#: src/contributing/architecture.md:28 +#: src/contributing/architecture.md:29 msgid "HIR types and data structures, plus AST→HIR lowering" msgstr "Tipos de HIR y estructuras de datos, además de la reducción de AST→HIR" -#: src/contributing/architecture.md:29 +#: src/contributing/architecture.md:30 msgid "`perry-transform`" msgstr "`perry-transform`" -#: src/contributing/architecture.md:29 +#: src/contributing/architecture.md:30 msgid "IR passes: function inlining, closure conversion, async lowering" msgstr "" "Pases de IR: inlining de funciones, conversión de closures, descenso de async" -#: src/contributing/architecture.md:30 +#: src/contributing/architecture.md:31 +#, fuzzy +msgid "`perry-wpfacts`" +msgstr "`perry-parser`" + +#: src/contributing/architecture.md:31 +msgid "Value-flow facts and cross-module contracts" +msgstr "Hechos de flujo de valores y contratos entre módulos" + +#: src/contributing/architecture.md:32 +#, fuzzy +msgid "`perry-abi`" +msgstr "`perry-hir`" + +#: src/contributing/architecture.md:32 +msgid "Shared compiler/runtime value and layout contract" +msgstr "Contrato compartido de valores y layouts entre compilador y runtime" + +#: src/contributing/architecture.md:33 msgid "`perry-codegen`" msgstr "`perry-codegen`" -#: src/contributing/architecture.md:30 +#: src/contributing/architecture.md:33 msgid "LLVM-based native code generation" msgstr "Generación de código nativo basada en LLVM" -#: src/contributing/architecture.md:31 +#: src/contributing/architecture.md:34 msgid "`perry-codegen-wasm`" msgstr "`perry-codegen-wasm`" -#: src/contributing/architecture.md:31 +#: src/contributing/architecture.md:34 msgid "" "WebAssembly code generation for `--target web` / `--target wasm` (HIR → WASM " "bytecode + JS bridge)" @@ -90180,11 +90122,11 @@ msgstr "" "Generación de código WebAssembly para `--target web` / `--target wasm` (HIR " "→ bytecode WASM + puente JS)" -#: src/contributing/architecture.md:32 +#: src/contributing/architecture.md:35 msgid "`perry-codegen-js`" msgstr "`perry-codegen-js`" -#: src/contributing/architecture.md:32 +#: src/contributing/architecture.md:35 msgid "" "Legacy JavaScript code generator (still present for the JS minifier; the JS-" "emit `--target web` path was consolidated into `perry-codegen-wasm`)" @@ -90193,19 +90135,19 @@ msgstr "" "JS; la ruta de emisión JS con `--target web` fue consolidada en `perry-" "codegen-wasm`)" -#: src/contributing/architecture.md:33 +#: src/contributing/architecture.md:36 msgid "`perry-codegen-swiftui`" msgstr "`perry-codegen-swiftui`" -#: src/contributing/architecture.md:33 +#: src/contributing/architecture.md:36 msgid "SwiftUI code generation for WidgetKit extensions" msgstr "Generación de código SwiftUI para extensiones WidgetKit" -#: src/contributing/architecture.md:34 +#: src/contributing/architecture.md:37 msgid "`perry-runtime`" msgstr "`perry-runtime`" -#: src/contributing/architecture.md:34 +#: src/contributing/architecture.md:37 msgid "" "Runtime library: NaN-boxed values, GC, arena allocator, objects, arrays, " "strings" @@ -90213,118 +90155,116 @@ msgstr "" "Biblioteca de runtime: valores NaN-boxed, GC, asignador de arena, objetos, " "arreglos, strings" -#: src/contributing/architecture.md:35 +#: src/contributing/architecture.md:38 msgid "`perry-ffi`" msgstr "`perry-ffi`" -#: src/contributing/architecture.md:35 +#: src/contributing/architecture.md:38 msgid "Stable interface used by native binding crates" msgstr "Interfaz estable utilizada por cajas de unión nativas" -#: src/contributing/architecture.md:36 +#: src/contributing/architecture.md:39 msgid "`perry-stdlib`" msgstr "`perry-stdlib`" -#: src/contributing/architecture.md:36 +#: src/contributing/architecture.md:39 msgid "Runtime-coupled Node.js and Perry standard-library implementations" msgstr "" "Implementaciones estándar-biblioteca Node.js y Perry acopladas durante el " "tiempo de ejecución" -#: src/contributing/architecture.md:37 +#: src/contributing/architecture.md:40 msgid "`perry-ext-*`" msgstr "`perry-ext-*`" -#: src/contributing/architecture.md:37 +#: src/contributing/architecture.md:40 msgid "Independently linked native bindings selected per program" msgstr "" "Encuadernaciones nativas conectadas independientemente seleccionadas por " "programa" -#: src/contributing/architecture.md:38 +#: src/contributing/architecture.md:41 msgid "`perry-ui` / `perry-ui-model`" msgstr "`perry-ui` / `perry-ui-model`" -#: src/contributing/architecture.md:38 +#: src/contributing/architecture.md:41 msgid "Shared UI interface and public model metadata" msgstr "Interfaz de interfaz compartida y metadatos de modelos públicos" -#: src/contributing/architecture.md:39 +#: src/contributing/architecture.md:42 msgid "macOS UI (AppKit)" msgstr "UI de macOS (AppKit)" -#: src/contributing/architecture.md:40 +#: src/contributing/architecture.md:43 msgid "iOS UI (UIKit)" msgstr "UI de iOS (UIKit)" -#: src/contributing/architecture.md:42 +#: src/contributing/architecture.md:45 msgid "Key Concepts" msgstr "Conceptos clave" -#: src/contributing/architecture.md:44 +#: src/contributing/architecture.md:47 msgid "NaN-Boxing" msgstr "NaN-Boxing" -#: src/contributing/architecture.md:46 -msgid "" -"All JavaScript values are represented as 64-bit NaN-boxed values. The upper " -"16 bits encode the type tag:" -msgstr "" -"Todos los valores de JavaScript se representan como valores NaN-boxed de 64 " -"bits. Los 16 bits superiores codifican la etiqueta de tipo:" - -#: src/contributing/architecture.md:48 +#: src/contributing/architecture.md:51 msgid "Tag" msgstr "Etiqueta" -#: src/contributing/architecture.md:50 +#: src/contributing/architecture.md:53 msgid "String (lower 48 bits = pointer)" msgstr "String (48 bits inferiores = puntero)" -#: src/contributing/architecture.md:51 +#: src/contributing/architecture.md:54 msgid "Pointer/Object (lower 48 bits = pointer)" msgstr "Puntero/Objeto (48 bits inferiores = puntero)" -#: src/contributing/architecture.md:52 +#: src/contributing/architecture.md:55 msgid "Int32 (lower 32 bits = integer)" msgstr "Int32 (32 bits inferiores = entero)" -#: src/contributing/architecture.md:53 +#: src/contributing/architecture.md:56 msgid "BigInt (lower 48 bits = pointer)" msgstr "BigInt (48 bits inferiores = puntero)" -#: src/contributing/architecture.md:54 +#: src/contributing/architecture.md:57 +msgid "Inline short string" +msgstr "Cadena corta inline" + +#: src/contributing/architecture.md:59 msgid "Special constants" msgstr "Constantes especiales" -#: src/contributing/architecture.md:54 +#: src/contributing/architecture.md:59 msgid "undefined, null, true, false" msgstr "undefined, null, true, false" -#: src/contributing/architecture.md:55 +#: src/contributing/architecture.md:60 msgid "Any other" msgstr "Cualquier otro" -#: src/contributing/architecture.md:55 +#: src/contributing/architecture.md:60 msgid "Float64 (the full 64 bits)" msgstr "Float64 (los 64 bits completos)" -#: src/contributing/architecture.md:59 +#: src/contributing/architecture.md:64 msgid "" -"Generational mark-sweep GC, per-thread arena split into nursery + old-gen. " -"Roots come from a precise shadow stack (emitted by codegen), a conservative " -"native-stack scan, and 9 registered runtime scanners. Two-bit aging tenures " -"objects after surviving 2 minor cycles; a write barrier maintains a " -"remembered set for old → young pointers." +"The per-thread tracing collector combines copying nursery collection, non-" +"moving fallbacks and full mark-sweep. Native roots use LLVM statepoints; " +"supported fallback targets use shadow frames. Runtime root scanners cover " +"globals and side tables. Conservative stack scanning is diagnostic-only. " +"Adaptive aging, large young storage and whole-block promotion have separate " +"policies." msgstr "" -"GC de marca y barrido generacional, arena por hilo dividida en nursery + old-" -"gen. Las raíces provienen de un shadow stack preciso (emitido por el " -"codegen), un escaneo conservador de la pila nativa y 9 escáneres de runtime " -"registrados. El envejecimiento de dos bits promueve objetos a tenured tras " -"sobrevivir 2 ciclos menores; una barrera de escritura mantiene un conjunto " -"de recordados para punteros de old → young." +"El recolector por trazado de cada hilo combina recolección joven por copia, " +"alternativas no móviles y mark-sweep completo. Las raíces nativas usan " +"statepoints LLVM; las plataformas alternativas admitidas usan shadow frames." +" Los escáneres del runtime cubren globales y tablas auxiliares. El escaneo " +"conservador de pila es solo diagnóstico. El envejecimiento adaptativo, los " +"objetos jóvenes grandes y la promoción de bloques completos tienen políticas" +" independientes." -#: src/contributing/architecture.md:61 +#: src/contributing/architecture.md:66 msgid "" "See [Internals → Memory Model](../internals/memory-model.md) for the full " "picture (NaN-boxing, heap layout, root discovery, generational behaviour, " @@ -90334,71 +90274,79 @@ msgstr "" "panorama completo (NaN-boxing, disposición del heap, descubrimiento de " "raíces, comportamiento generacional, variables de entorno de escape)." -#: src/contributing/architecture.md:63 +#: src/contributing/architecture.md:68 msgid "Handle-Based UI" msgstr "UI basada en handles" -#: src/contributing/architecture.md:65 +#: src/contributing/architecture.md:70 msgid "" -"UI widgets are represented as small integer handles NaN-boxed with " -"`POINTER_TAG`. Each handle maps to a native platform widget (NSButton, " +"Native UI registry IDs use `HANDLE_TAG` (`0xFFF9`), distinct from managed " +"heap pointers. Each handle maps to a native platform widget (NSButton, " "UILabel, GtkButton, etc.). Two dispatch tables route method calls and " "property accesses to the correct FFI function." msgstr "" -"Los widgets de UI se representan como handles de entero pequeño NaN-boxed " -"con `POINTER_TAG`. Cada handle se asigna a un widget de plataforma nativa " -"(NSButton, UILabel, GtkButton, etc.). Dos tablas de despacho enrutan las " -"llamadas a métodos y los accesos a propiedades a la función FFI correcta." +"Los IDs del registro de UI nativa usan `HANDLE_TAG` (`0xFFF9`), distintos de" +" los punteros al heap administrado. Cada handle corresponde a un widget " +"nativo (NSButton, UILabel, GtkButton, etc.). Dos tablas dirigen llamadas a " +"métodos y accesos a propiedades a la función FFI correcta." -#: src/contributing/architecture.md:67 +#: src/contributing/architecture.md:72 msgid "Source Code Organization" msgstr "Organización del código fuente" -#: src/contributing/architecture.md:69 -msgid "The codegen crate is organized into focused modules:" -msgstr "El crate de codegen está organizado en módulos enfocados:" - -#: src/contributing/architecture.md:86 -msgid "The HIR lowering was split into 8 modules:" -msgstr "El descenso de HIR fue dividido en 8 módulos:" +#: src/contributing/architecture.md:74 +msgid "" +"Start with `crates/perry-hir/src/lower/` for AST lowering, `crates/perry-" +"codegen/src/codegen/` for module orchestration, and its sibling `expr/`, " +"`stmt/`, `lower_call/` and `native_root_coverage/` trees for emission. " +"[Program facts](../internals/program-facts.md) connect analysis to layout " +"and specialization. Turnloop supplies the event loop; Tokio is not part of " +"the runtime." +msgstr "" +"Empiece en `crates/perry-hir/src/lower/` para bajar AST, `crates/perry-" +"codegen/src/codegen/` para orquestar módulos y los directorios hermanos " +"`expr/`, `stmt/`, `lower_call/` y `native_root_coverage/` para emisión. Los " +"[hechos del programa](../internals/program-facts.md) conectan análisis, " +"layout y especialización. Turnloop proporciona el bucle de eventos; Tokio no" +" forma parte del runtime." -#: src/contributing/architecture.md:100 +#: src/contributing/architecture.md:76 #, fuzzy msgid "Adding a native UI widget" msgstr "Cómo crear un binding nativo" -#: src/contributing/architecture.md:102 +#: src/contributing/architecture.md:78 msgid "A new widget touches four places:" msgstr "" -#: src/contributing/architecture.md:104 +#: src/contributing/architecture.md:80 msgid "" "Runtime: create the widget in `crates/perry-ui-macos/src/widgets/` and call " "`register_widget(view)`." msgstr "" -#: src/contributing/architecture.md:105 +#: src/contributing/architecture.md:81 msgid "" "FFI: export `perry_ui__create` from `crates/perry-ui-macos/src/lib." "rs`." msgstr "" -#: src/contributing/architecture.md:106 +#: src/contributing/architecture.md:82 msgid "" "Codegen: declare the extern and add the `NativeMethodCall` dispatch in " "`crates/perry-codegen/src/lower_call/native/native_ui_widgets_branch.rs`." msgstr "" -#: src/contributing/architecture.md:107 +#: src/contributing/architecture.md:83 msgid "" "HIR: `crates/perry-hir/src/lower/`, only if the widget has instance methods." msgstr "" -#: src/contributing/architecture.md:111 +#: src/contributing/architecture.md:87 msgid "[Building from Source](building.md)" msgstr "[Compilar desde el código fuente](building.md)" -#: src/contributing/architecture.md:112 +#: src/contributing/architecture.md:88 #, fuzzy msgid "See `AGENTS.md` in the repository root for implementation traps" msgstr "" @@ -90857,8 +90805,8 @@ msgstr "" #: src/contributing/building.md:21 msgid "" -"For the cached Make targets, install the pinned Mr Boxington once with `make " -"mbx-deps`." +"`make build-dev` provisions its pinned build tools automatically. For `make " +"build-prod`, install the pinned Mr Boxington once with `make mbx-deps`." msgstr "" #: src/contributing/building.md:24 @@ -91026,10 +90974,60 @@ msgstr "" "construcción sean visibles." #: src/contributing/building.md:83 +#, fuzzy +msgid "Faster development rebuilds with Blaze" +msgstr "Compilaciones de desarrollo" + +#: src/contributing/building.md:85 +msgid "" +"`make build-dev` installs verified [Blaze](https://github.com/indexable-inc/" +"blaze) and mbx 1.28.0 binaries, selects their matching `nightly-2026-10-25` " +"compiler, and activates the Cargo shim automatically. Linux GNU x64/ARM64 " +"and Apple Silicon macOS use the prebuilt toolchain pinned in `scripts/build-" +"tools.ref`. Setup fails if the download or compiler identity cannot be " +"verified." +msgstr "" + +#: src/contributing/building.md:91 +msgid "" +"The default upstream archive currently targets an older compiler. These " +"prebuilts come from the pinned upstream source, built once in the shared " +"[jdalton/blaze release workflow](https://github.com/jdalton/blaze/actions/" +"workflows/release.yaml). Project builds download them without compiling " +"Blaze. The development Docker image installs them under `/opt/mbx-blaze`, " +"outside the checkout mount." +msgstr "" + +#: src/contributing/building.md:97 +msgid "" +"Development builds retain `force-unwind-tables=yes` for exception transport. " +"They use full unwind tables because Blaze refuses `use-sync-unwind=yes`. " +"Host production commands retain the repository's compiler and compact-table " +"configuration unless custom Rust flags override it. The development image " +"uses full tables by default; use the production Dockerfile for distribution " +"builds. `PERRY_DEV_RUSTFLAGS` customizes development flags and must preserve " +"`-C force-unwind-tables=yes`." +msgstr "" + +#: src/contributing/building.md:104 +msgid "" +"The first build loads the workspace. Later supported edits use Blaze while " +"mbx caches dependencies and manages worktree targets. Explicit `--release` " +"and `--profile` builds stay on Cargo and mbx. The Rust unit-test CI job uses " +"the same prebuilts and mbx object cache. Other production checks retain the " +"normal compiler path. The tools remain experimental. In a Linux ARM64 " +"development image, `perry-native-registration` passed 27 tests through " +"Blaze. The `no_std` `perry-abi` crate passed four tests only after Blaze " +"reported missing `lang_start` and fell back to Cargo. Full workspace " +"compatibility is not established. Linux ARM64 and Apple Silicon passed a " +"separate edited-program probe that also verifies an mbx dependency cache hit." +msgstr "" + +#: src/contributing/building.md:115 msgid "Compiler cache for ephemeral worktrees" msgstr "Caché de compilación para árboles de trabajo efímeros" -#: src/contributing/building.md:85 +#: src/contributing/building.md:117 msgid "" "Short-lived worktrees and coding agents can opt into a compiler cache shared " "outside the repository. Install `sccache` in your user environment, then run " @@ -91040,16 +91038,16 @@ msgstr "" "`sccache` en su entorno de usuario, luego ejecute Cargo a través de la " "envoltura:" -#: src/contributing/building.md:91 +#: src/contributing/building.md:123 #, fuzzy msgid "# Slim, optimized developer CLI\n" msgstr "# Slim, desarrollador optimizado CLI" -#: src/contributing/building.md:97 +#: src/contributing/building.md:129 msgid "" "The wrapper disables Cargo incremental compilation because `sccache` cannot " -"cache incremental artifacts. It stores compiler objects under " -"`${XDG_CACHE_HOME:-$HOME/.cache}/perry/sccache` by default, not in the " +"cache incremental artifacts. It stores compiler objects under `" +"${XDG_CACHE_HOME:-$HOME/.cache}/perry/sccache` by default, not in the " "worktree; set `SCCACHE_DIR` or `SCCACHE_CACHE_SIZE` to override its `12G` " "cache policy. It does not install or configure `sccache` globally." msgstr "" @@ -91059,7 +91057,7 @@ msgstr "" "árbol de trabajo; establece `SCCACHE_DIR` o `SCCACHE_CACHE_SIZE` para anular " "su política de caché `12G`. No instala ni configura `sccache` globalmente." -#: src/contributing/building.md:103 +#: src/contributing/building.md:135 msgid "" "In a long-lived worktree, use ordinary `cargo` commands instead so the local " "incremental cache remains available. Cache benefit depends on the compiler, " @@ -91070,16 +91068,16 @@ msgstr "" "disponible. El beneficio de Cache depende del compilador, las banderas, las " "dependencias y cuánto trabajo previo puede reutilizar la cache." -#: src/contributing/building.md:107 +#: src/contributing/building.md:139 msgid "Build Specific Crates" msgstr "Compilar crates específicos" -#: src/contributing/building.md:110 +#: src/contributing/building.md:142 #, fuzzy msgid "# Runtime only (must rebuild stdlib too!)\n" msgstr "# Tiempo de ejecución solamente (debe reconstruir stdlib también!)" -#: src/contributing/building.md:112 +#: src/contributing/building.md:144 #, fuzzy msgid "" "# The .a static archives are emitted by separate wrapper crates (#5422), so " @@ -91088,7 +91086,7 @@ msgstr "" "# Los archivos estáticos .a son emitidos por cajas de envoltura separadas " "(#5422), así que un" -#: src/contributing/building.md:114 +#: src/contributing/building.md:146 #, fuzzy msgid "" "# explicitly when you need libperry_runtime.a / libperry_stdlib.a (e.g. to " @@ -91097,23 +91095,23 @@ msgstr "" "# explícitamente cuando necesita libperry_runtime.a / libperry_stdlib.a (e." "g. para enlazar" -#: src/contributing/building.md:118 +#: src/contributing/building.md:150 msgid "" "# `perry` refuses to link workspace archives that lack a GC-effects sidecar\n" msgstr "" -#: src/contributing/building.md:120 +#: src/contributing/building.md:152 msgid "" "\"the runtime GC effects sidecar cannot speak for the archives this program\n" "# links\"" msgstr "" -#: src/contributing/building.md:123 +#: src/contributing/building.md:155 #, fuzzy msgid "# Codegen only\n" msgstr "# Codificadores únicamente" -#: src/contributing/building.md:128 +#: src/contributing/building.md:160 msgid "" "**Important**: When rebuilding `perry-runtime`, you must also rebuild `perry-" "stdlib` because `libperry_stdlib.a` embeds perry-runtime as a static " @@ -91123,11 +91121,11 @@ msgstr "" "`perry-stdlib` porque `libperry_stdlib.a` incrusta perry-runtime como " "dependencia estática." -#: src/contributing/building.md:130 +#: src/contributing/building.md:162 msgid "Slim developer CLI" msgstr "Slim desarrollador CLI" -#: src/contributing/building.md:132 +#: src/contributing/building.md:164 msgid "" "The default build is the full official CLI. For compiler work you can build " "a slimmer CLI that omits the publish / mobile / updater / native / audit " @@ -91138,7 +91136,7 @@ msgstr "" "publicar / móvil / actualizador / nativo / auditoría y los backends no " "nativos del códice (#5422):" -#: src/contributing/building.md:140 +#: src/contributing/building.md:172 msgid "" "`dev-cli` keeps `compile` / `run` / `check` / `types` / `cache` / `dev`. " "Disabled commands drop out of `--help`, and disabled `--target` backends " @@ -91152,16 +91150,16 @@ msgstr "" "``\". Véase `crates/perry/Cargo.toml` para la lista completa de " "características (`full-cli`, `publish-cli`, `backend-wasm`, ...)." -#: src/contributing/building.md:145 +#: src/contributing/building.md:177 msgid "Run Tests" msgstr "Ejecutar pruebas" -#: src/contributing/building.md:148 +#: src/contributing/building.md:180 #, fuzzy msgid "# Product unit targets\n" msgstr "# Objetivos de las unidades de producto" -#: src/contributing/building.md:150 +#: src/contributing/building.md:182 #, fuzzy msgid "" "# Inspect the nightly CI test scope (all Linux-compatible test crates)\n" @@ -91169,42 +91167,42 @@ msgstr "" "# Inspeccione el alcance de prueba de IC nocturno (todas las cajas de prueba " "compatibles con Linux)" -#: src/contributing/building.md:153 +#: src/contributing/building.md:185 #, fuzzy msgid "# Specific crate\n" msgstr "# Caja específica" -#: src/contributing/building.md:159 +#: src/contributing/building.md:191 msgid "Compile and Run TypeScript" msgstr "Compilar y ejecutar TypeScript" -#: src/contributing/building.md:162 +#: src/contributing/building.md:194 #, fuzzy msgid "# Compile a TypeScript file\n" msgstr "# Compilar un archivo TypeScript" -#: src/contributing/building.md:165 +#: src/contributing/building.md:197 #, fuzzy msgid "# Debug: print HIR\n" msgstr "# Depuración: imprimir HIR" -#: src/contributing/building.md:170 +#: src/contributing/building.md:202 msgid "Development Workflow" msgstr "Flujo de trabajo de desarrollo" -#: src/contributing/building.md:172 +#: src/contributing/building.md:204 msgid "Make changes to the relevant crate" msgstr "Realiza cambios en el crate correspondiente" -#: src/contributing/building.md:173 +#: src/contributing/building.md:205 msgid "`cargo check -p perry` for fast product feedback" msgstr "`cargo check -p perry` para producto rápido feedback" -#: src/contributing/building.md:174 +#: src/contributing/building.md:206 msgid "Run tests for the crates affected by the change" msgstr "Realizar pruebas para las cajas afectadas por el cambio" -#: src/contributing/building.md:175 +#: src/contributing/building.md:207 msgid "" "Test with a real TypeScript file: `cargo run --release -- test.ts -o test " "&& ./test`" @@ -91212,23 +91210,23 @@ msgstr "" "Prueba con un archivo TypeScript real: `cargo run --release -- test.ts -o " "test && ./test`" -#: src/contributing/building.md:203 +#: src/contributing/building.md:235 msgid "[Architecture](architecture.md) — Crate map and pipeline overview" msgstr "" "[Arquitectura](architecture.md) — Mapa de crates y descripción del pipeline" -#: src/contributing/building.md:204 +#: src/contributing/building.md:236 #, fuzzy msgid "See `AGENTS.md` for implementation pitfalls" msgstr "" "Consulta `CLAUDE.md` para notas detalladas de implementación y advertencias" -#: src/contributing/building.md:206 +#: src/contributing/building.md:238 #, fuzzy msgid "Development debug information" msgstr "Compilaciones de desarrollo" -#: src/contributing/building.md:208 +#: src/contributing/building.md:240 msgid "" "`dev` (including tests) and `perry-dev` use `line-tables-only` debug " "information to retain file/line backtraces while reducing build storage and " @@ -91237,26 +91235,27 @@ msgid "" "stripping is unchanged." msgstr "" -#: src/contributing/building.md:213 +#: src/contributing/building.md:245 msgid "" "The policy is checked by `python3 scripts/check_dev_debug.py` in PR lint." msgstr "" -#: src/contributing/building.md:215 +#: src/contributing/building.md:247 msgid "Cached Linux linking" msgstr "" -#: src/contributing/building.md:217 +#: src/contributing/building.md:249 msgid "" -"Use `make mbx-deps` and the cached Make targets for local builds. Linux GNU " -"x86_64 and AArch64 targets use mold 2.42.0 via `.mbx.toml`; install `clang` " -"on the build host. MBX downloads the pinned mold release and verifies its " -"GitHub SHA-256 digest. macOS, Windows, Android and custom musl linker paths " -"keep the system linker. The core-runtime and Linux release scripts require " -"MBX instead of silently bypassing the cache when it is missing." +"The development Make target provisions its cache automatically. Production " +"builds use `make mbx-deps` first. Linux GNU x86_64 and AArch64 targets use " +"mold 2.42.0 via `.mbx.toml`; install `clang` on the build host. MBX " +"downloads the pinned mold release and verifies its GitHub SHA-256 digest. " +"macOS, Windows, Android and custom musl linker paths keep the system linker. " +"The core-runtime and Linux release scripts require MBX instead of silently " +"bypassing the cache when it is missing." msgstr "" -#: src/contributing/building.md:224 +#: src/contributing/building.md:257 msgid "" "Use `MBX_LINKER=system` explicitly for linker bisection. Do not replace " "`RUSTFLAGS`: Perry requires the unwind-table and x86_64 frame-pointer flags " @@ -91266,13 +91265,13 @@ msgid "" "comparisons." msgstr "" -#: src/contributing/building.md:230 +#: src/contributing/building.md:263 msgid "" "See [MBX managed linkers](https://mr-boxington.jdx.dev/linkers) and [mold]" "(https://github.com/rui314/mold)." msgstr "" -#: src/contributing/building.md:233 +#: src/contributing/building.md:266 msgid "" "For dedicated Linux agent hosts, use [compressed ZFS build storage](build-" "storage.md) and `make build-agent` to check storage before building." @@ -91820,8 +91819,8 @@ msgstr "2a. Receta de ejecución del simulador (iOS / tvOS)" #: src/contributing/releasing.md:187 msgid "" "`perry-ui-ios` and `perry-ui-tvos` honor `PERRY_UI_TEST_MODE=1` — when set, " -"the app renders one frame, optionally writes a screenshot to " -"`$PERRY_UI_SCREENSHOT_PATH`, and exits cleanly. Combine with `xcrun simctl` " +"the app renders one frame, optionally writes a screenshot to `" +"$PERRY_UI_SCREENSHOT_PATH`, and exits cleanly. Combine with `xcrun simctl` " "to verify a doc-example runs without a human:" msgstr "" "`perry-ui-ios` y `perry-ui-tvos` honran a `PERRY_UI_TEST_MODE=1` — cuando se " @@ -91857,8 +91856,8 @@ msgid "" "the Rust Tier-3 toolchain requires `+nightly -Zbuild-std` — see the `watchos-" "simulator` row in the matrix above." msgstr "" -"La misma receta funciona para el dispositivo `tvos-simulator` + `\"Apple " -"TV\"`. En watchOS la cadena de herramientas Rust Tier-3 requiere `+nightly -" +"La misma receta funciona para el dispositivo `tvos-simulator` + `\"Apple TV" +"\"`. En watchOS la cadena de herramientas Rust Tier-3 requiere `+nightly -" "Zbuild-std` — vea la fila `watchos-simulator` en la matriz anterior." #: src/contributing/releasing.md:214 @@ -92235,6 +92234,769 @@ msgstr "" "`existing_tag=vX.Y.Z`; añada `publish_npm=true` sólo cuando la pierna " "idempotente npm también necesite volver a intentarlo." +#~ msgid "" +#~ "Perry compiles TypeScript to native machine code via LLVM. There's no " +#~ "interpreter, no VM, no isolate. A function pointer is just a function " +#~ "pointer — it's valid on any thread." +#~ msgstr "" +#~ "Perry compila TypeScript a código máquina nativo a través de LLVM. No hay " +#~ "intérprete, ni VM, ni isolate. Un puntero a función es simplemente un " +#~ "puntero a función — es válido en cualquier hilo." + +#~ msgid "" +#~ "Each thread gets its own memory arena (bump allocator) and garbage " +#~ "collector. No synchronization overhead during computation. When a thread " +#~ "finishes, its arena is freed automatically." +#~ msgstr "" +#~ "Cada hilo obtiene su propia arena de memoria (allocator por bump) y " +#~ "recolector de basura. Sin sobrecarga de sincronización durante el " +#~ "cómputo. Cuando un hilo termina, su arena se libera automáticamente." + +#~ msgid "" +#~ "Perry includes a mark-sweep garbage collector. It runs automatically when " +#~ "memory pressure is detected (~8MB arena blocks), but you can also trigger " +#~ "it manually:" +#~ msgstr "" +#~ "Perry incluye un recolector de basura de tipo mark-sweep. Se ejecuta " +#~ "automáticamente cuando se detecta presión de memoria (bloques de arena de " +#~ "~8 MB), pero también puedes activarlo manualmente:" + +#~ msgid "" +#~ "The GC uses conservative stack scanning to find roots and supports arena-" +#~ "allocated objects (arrays, objects) and malloc-allocated objects " +#~ "(strings, closures, promises, BigInts, errors)." +#~ msgstr "" +#~ "El GC utiliza escaneo conservador de la pila para encontrar raíces y " +#~ "admite objetos asignados en arena (arrays, objetos) y objetos asignados " +#~ "con malloc (strings, closures, promesas, BigInts, errores)." + +#~ msgid "" +#~ "There are no guarantees for every dynamic JavaScript pattern (the " +#~ "[Limitations](limitations.md) still apply — no `eval`, no general dynamic " +#~ "`require()`), but most plain JavaScript projects compile and run." +#~ msgstr "" +#~ "No hay garantías para todos los patrones dinámicos JavaScript (el " +#~ "[Limitations](limitations.md) todavía se aplica — no `eval`, no " +#~ "`require()` dinámico general), pero la mayoría de los proyectos " +#~ "JavaScript simples compilar y ejecutar." + +#~ msgid "" +#~ "Perry erases types at compile time, similar to how `tsc` removes type " +#~ "annotations when emitting JavaScript. However, Perry also performs type " +#~ "inference to generate efficient native code." +#~ msgstr "" +#~ "Perry borra los tipos en tiempo de compilación, de forma similar a como " +#~ "`tsc` elimina las anotaciones de tipo al emitir JavaScript. Sin embargo, " +#~ "Perry también realiza inferencia de tipos para generar código nativo " +#~ "eficiente." + +#~ msgid "" +#~ "Common TypeScript utility types are erased at compile time (they don't " +#~ "affect code generation):" +#~ msgstr "" +#~ "Los tipos utilitarios comunes de TypeScript se borran en tiempo de " +#~ "compilación (no afectan a la generación de código):" + +#~ msgid "" +#~ "At runtime, all values are NaN-boxed — the generic parameter doesn't " +#~ "affect code generation." +#~ msgstr "" +#~ "En tiempo de ejecución, todos los valores están NaN-boxed — el parámetro " +#~ "genérico no afecta a la generación de código." + +#~ msgid "" +#~ "This resolves cross-file types, interfaces, and generics via an IPC " +#~ "protocol. It falls back gracefully if the type checker is not installed." +#~ msgstr "" +#~ "Esto resuelve tipos, interfaces y genéricos entre archivos mediante un " +#~ "protocolo IPC. Se degrada de forma elegante si el verificador de tipos no " +#~ "está instalado." + +#~ msgid "" +#~ "Union types are recognized syntactically but don't affect code generation:" +#~ msgstr "" +#~ "Los tipos unión se reconocen sintácticamente pero no afectan a la " +#~ "generación de código:" + +#~ msgid "No eval() or Dynamic Code" +#~ msgstr "Sin eval() ni código dinámico" + +#~ msgid "" +#~ "Perry compiles to native code ahead of time. It cannot evaluate a code " +#~ "string that is only known at runtime. A _constant_ code string is the " +#~ "exception — `eval(\"1 + 1\")` and `new Function(\"a\", \"b\", \"return a " +#~ "+ b\")` are compiled to real native functions (#1679). Only a body built " +#~ "from runtime data hits this limit:" +#~ msgstr "" +#~ "Perry se compila con el código nativo antes de tiempo. No puede evaluar " +#~ "una cadena de código que sólo se conoce en tiempo de ejecución. Una " +#~ "cadena de código _constant_ es la excepción — `eval(\"1 + 1\")` y `new " +#~ "Function(\"a\", \"b\", \"return a + b\")` se compilan con funciones " +#~ "nativas reales (#1679). Sólo un cuerpo construido a partir de datos de " +#~ "tiempo de ejecución alcanza este límite:" + +#~ msgid "" +#~ "```typescript,no-test\n" +#~ "// Constant body → compiled natively (works)\n" +#~ "const add = new Function(\"a\", \"b\", \"return a + b\");\n" +#~ "\n" +#~ "// Runtime-built body → cannot be compiled ahead of time\n" +#~ "function run(src: string) { return new Function(`return (${src})`)(); }\n" +#~ "```" +#~ msgstr "" +#~ "```typescript,no-test\n" +#~ "// Constant body → compiled natively (works)\n" +#~ "const add = new Function(\"a\", \"b\", \"return a + b\");\n" +#~ "\n" +#~ "// Runtime-built body → cannot be compiled ahead of time\n" +#~ "function run(src: string) { return new Function(`return (${src})`)(); }\n" +#~ "```" + +#~ msgid "Default: deferred runtime error + compile-time notice (#5206)" +#~ msgstr "" +#~ "Predeterminado: error de tiempo de ejecución diferido + aviso de tiempo " +#~ "de compilación (#5206)" + +#~ msgid "" +#~ "By default a runtime-unknown `eval(...)` / `new Function()` " +#~ "site does **not** block the build. Perry compiles it to a value that " +#~ "throws a descriptive `Error` _only if it is actually reached_ (an " +#~ "`eval(...)` throws when evaluated; a `new Function(...)` returns a " +#~ "function that throws when called), and prints a single end-of-compile " +#~ "notice listing every degraded site:" +#~ msgstr "" +#~ "Por defecto, un sitio `eval(...)` / `new Function()` " +#~ "desconocido hace **no** bloquear la construcción. Perry lo compila a un " +#~ "valor que arroja una `Error` descriptiva _solo si realmente se alcanza_ " +#~ "(un `eval(...)` lanza cuando se evalúa; un `new Function(...)` devuelve " +#~ "una función que lanza cuando se llama), e imprime un solo aviso de final " +#~ "de compilación que enumera cada sitio degradado:" + +#~ msgid "" +#~ "This lets a single such call in a cold path ship without aborting the " +#~ "whole build, while still failing loudly (and catchably) if that path runs." +#~ msgstr "" +#~ "Esto permite que una sola llamada de este tipo en una nave de camino frío " +#~ "sin abortar toda la construcción, mientras que todavía falla en voz alta " +#~ "(y atrapable) si ese camino corre." + +#~ msgid "Dynamic `import()` with a runtime-computed specifier (#5230)" +#~ msgstr "" +#~ "`import()` dinámico con un especificador computado en tiempo de ejecución " +#~ "(#5230)" + +#, fuzzy +#~ msgid "" +#~ "For code modules, a dynamic `import(spec)` whose `spec` is only known at " +#~ "runtime (a plugin loader building a path from a variable) is subject to " +#~ "the **same defer/notice/strict policy** as `eval`. By default it compiles " +#~ "to a rejected `Promise` carrying a descriptive `Error` (so `await " +#~ "import(spec)` throws _only if reached_), is listed in the shared notice " +#~ "above under the `import(...)` kind, and does **not** abort the build. " +#~ "This lets an app with a plugin-loader path compile and run its core, with " +#~ "only the plugin-load path throwing if exercised." +#~ msgstr "" +#~ "Un dinámico `import(spec)` cuyo `spec` sólo se conoce en tiempo de " +#~ "ejecución (un cargador de plugins que construye una ruta desde una " +#~ "variable) está sujeto a la **la misma política de defer/notice/strict** " +#~ "como `eval`. De forma predeterminada compila a un `Promise` rechazado que " +#~ "lleva un `Error` descriptivo (por lo que `await import(spec)` lanza _solo " +#~ "si se alcanza_), se enumera en el aviso compartido arriba bajo el tipo " +#~ "`import(...)`, y lo hace **no** abortar la construcción. Esto permite que " +#~ "una aplicación con un plugin-loader path compile y ejecute su núcleo, con " +#~ "sólo el plugin-loader path lanzar si se ejercita." + +#~ msgid "" +#~ "Resolvable specifiers are unaffected and still compile + load: string " +#~ "literals (`import(\"./mod.js\")`), ternaries of resolvable arms, template " +#~ "literals over `const` locals (``import(`./${KIND}.js`)``), finite string-" +#~ "literal-union parameters, and directory globs." +#~ msgstr "" +#~ "Los especificadores resolubles no se ven afectados y aún así compilan + " +#~ "carga: literales de cadena (`import(\"./mod.js\")`), ternarios de brazos " +#~ "resolubles, literales de plantilla sobre locales de `const` (``import(`./" +#~ "${KIND}.js`)``), parámetros finitos de unión de cadena-literal y globs de " +#~ "directorio." + +#~ msgid "" +#~ "```typescript,no-test\n" +#~ "// Resolvable → compiled + loaded as today.\n" +#~ "const real = await import(\"./real.js\");\n" +#~ "\n" +#~ "// Runtime-computed → deferred (default): throws only if this line runs.\n" +#~ "async function loadPlugin(name: string) {\n" +#~ " return await import(name + \".js\");\n" +#~ "}\n" +#~ "```" +#~ msgstr "" +#~ "```typescript,no-test\n" +#~ "// Resolvable → compiled + loaded as today.\n" +#~ "const real = await import(\"./real.js\");\n" +#~ "\n" +#~ "// Runtime-computed → deferred (default): throws only if this line runs.\n" +#~ "async function loadPlugin(name: string) {\n" +#~ " return await import(name + \".js\");\n" +#~ "}\n" +#~ "```" + +#~ msgid "Strict mode: refuse at compile time" +#~ msgstr "Modo estricto: rechazar en el momento de la compilación" + +#~ msgid "" +#~ "To make every runtime-unknown site a hard compile-time error instead, opt " +#~ "into strict-eval mode by any of:" +#~ msgstr "" +#~ "Para hacer de cada sitio de ejecución desconocido un error duro de " +#~ "compilación-tiempo en su lugar, opte por el modo estricto-eval por " +#~ "cualquiera de:" + +#~ msgid "the `--strict-eval` flag on `perry compile`," +#~ msgstr "el indicador `--strict-eval` en `perry compile`," + +#~ msgid "" +#~ "`\"perry\": { \"eval\": \"error\" }` (or `\"perry\": { \"strict\": true }" +#~ "`) in `package.json`, or" +#~ msgstr "" +#~ "`\"perry\": { \"eval\": \"error\" }` (o `\"perry\": { \"strict\": true }" +#~ "`) en `package.json`, o" + +#~ msgid "`[perry]` `eval = \"error\"` (or `strict = true`) in `perry.toml`." +#~ msgstr "`[perry]` `eval = \"error\"` (o `strict = true`) en `perry.toml`" + +#~ msgid "" +#~ "`perry.eval` accepts `\"defer\"` (the default) or `\"error\"`. Precedence " +#~ "is package.json/perry.toml config → `--strict-eval` (opts in). The legacy " +#~ "`PERRY_ALLOW_EVAL=1` environment variable still works: it forces non-" +#~ "strict (defer) mode for a one-off build, overriding any strict flag/" +#~ "config." +#~ msgstr "" +#~ "`perry.eval` acepta `\"defer\"` (el predeterminado) o `\"error\"`. " +#~ "Precedencia es package.json/perry.toml config → `--strict-eval` (opciones " +#~ "en). La variable de entorno `PERRY_ALLOW_EVAL=1` sigue funcionando: " +#~ "fuerza el modo no restringido para una construcción única, superando " +#~ "cualquier flag/config" + +#~ msgid "" +#~ "The same strict controls apply to runtime-computed dynamic `import()` " +#~ "(#5230). The broad `perry.strict = true` covers both eval and dynamic " +#~ "import. For a knob scoped to dynamic imports only, use `--strict-dynamic-" +#~ "import` or `\"perry\": { \"dynamicImport\": \"error\" }` (accepts `\"defer" +#~ "\"` (default) or `\"error\"`); the dedicated knob overrides the broad " +#~ "`perry.strict` for import sites. `PERRY_ALLOW_EVAL=1` is the shared AOT " +#~ "escape hatch — it forces defer for both eval and dynamic import." +#~ msgstr "" +#~ "Los mismos controles estrictos se aplican a la dinámica `import()` " +#~ "computada en tiempo de ejecución (#5230). La amplia `perry.strict = true` " +#~ "cubre tanto la importación eval como la dinámica. Para una perilla con " +#~ "alcances de importación dinámicos, utilice `--strict-dynamic-import` o `" +#~ "\"perry\": { \"dynamicImport\": \"error\" }` (acepta `\"defer\"` (por " +#~ "defecto) o `\"error\"`); la perilla dedicada sobrescribe la amplia `perry." +#~ "strict` para sitios de importación. `PERRY_ALLOW_EVAL=1` es la escotilla " +#~ "de escape AOT compartida, que obliga a diferir tanto para la importación " +#~ "eval como dinámica." + +#~ msgid "" +#~ "Test262 rows that only observe parsing or executing a code string remain " +#~ "intentional AOT exclusions, not runtime dynamic-code work. This includes " +#~ "the `language/white-space/comment-{multi,single}-{form-feed,horizontal-" +#~ "tab,nbsp,space,vertical-tab}.js` rows and the direct-eval reference row " +#~ "`language/types/reference/8.7.2-1-s.js`; they map to the AOT eval tracker " +#~ "(#1677), eval classifier diagnostics (#1678), and the limited literal " +#~ "`Function` folding work (#1679)." +#~ msgstr "" +#~ "Las filas de Test262 que solo observan el análisis sintáctico o la " +#~ "ejecución de una cadena de código siguen siendo exclusiones AOT " +#~ "intencionales, no trabajo de código dinámico en tiempo de ejecución. Esto " +#~ "incluye las filas `language/white-space/comment-{multi,single}-{form-feed," +#~ "horizontal-tab,nbsp,space,vertical-tab}.js` y la fila de referencia de " +#~ "eval directo `language/types/reference/8.7.2-1-s.js`; se corresponden con " +#~ "el rastreador de eval AOT (#1677), los diagnósticos del clasificador de " +#~ "eval (#1678) y el trabajo de plegado limitado de `Function` literal " +#~ "(#1679)." + +#~ msgid "No User-Space CommonJS require()" +#~ msgstr "Sin espacio de usuario CommonJS require()" + +#~ msgid "Use static ESM imports in Perry source:" +#~ msgstr "Usa importaciones ESM estáticas en código fuente Perry:" + +#~ msgid "" +#~ "```text\n" +#~ "// Supported\n" +#~ "import { foo } from \"./module\";\n" +#~ "\n" +#~ "// Not supported\n" +#~ "const mod = require(\"./module\");\n" +#~ "const mod = await import(\"./module\");\n" +#~ "```" +#~ msgstr "" +#~ "```text\n" +#~ "// Supported\n" +#~ "import { foo } from \"./module\";\n" +#~ "\n" +#~ "// Not supported\n" +#~ "const mod = require(\"./module\");\n" +#~ "const mod = await import(\"./module\");\n" +#~ "```" + +#~ msgid "" +#~ "Perry has internal CommonJS compatibility paths for some npm package " +#~ "wrappers, but user-written modules should use static `import` " +#~ "declarations." +#~ msgstr "" +#~ "Perry tiene rutas de compatibilidad internas con CommonJS para algunos " +#~ "wrappers de paquetes npm, pero los módulos escritos por el usuario deben " +#~ "usar declaraciones `import` estáticas." + +#~ msgid "" +#~ "**JavaScript source compiles too.** Perry accepts `.js`, `.cjs`, `.mjs`, " +#~ "and `.jsx` files as compiler input — they are parsed as JavaScript and " +#~ "lowered through the same native pipeline as TypeScript, so no type " +#~ "annotations are required. The limitations on this page still apply (no " +#~ "`eval`, no general dynamic `require()`, etc.), but plain JavaScript " +#~ "projects compile and run in most cases." +#~ msgstr "" +#~ "**La fuente JavaScript también se compila.** Perry acepta archivos `.js`, " +#~ "`.cjs`, `.mjs` y `.jsx` como entrada del compilador, que son analizados " +#~ "como JavaScript y bajados a través de la misma tubería nativa como " +#~ "TypeScript, por lo que no se requieren anotaciones de tipo. Las " +#~ "limitaciones en esta página siguen siendo aplicables (no `eval`, no " +#~ "`require()` dinámico general, etc.), pero los proyectos JavaScript " +#~ "simples compilar y ejecutar en la mayoría de los casos." + +#~ msgid "Weak References Retain Their Targets" +#~ msgstr "Las referencias débiles retienen sus objetivos" + +#~ msgid "" +#~ "`WeakMap`, `WeakSet`, `WeakRef`, and `FinalizationRegistry` are " +#~ "implemented and their APIs behave as expected — `set` / `get` / `has` / " +#~ "`delete`, `add`, `deref()`, and `register` / `unregister` all work and " +#~ "return the right values. `WeakMap` and `WeakSet` use **reference** " +#~ "equality, so two distinct objects never collide on the same slot." +#~ msgstr "" +#~ "`WeakMap`, `WeakSet`, `WeakRef` y `FinalizationRegistry` están " +#~ "implementados y sus APIs se comportan como se espera — `set` / `get` / " +#~ "`has` / `delete`, `add`, `deref()` y `register` / `unregister` funcionan " +#~ "y devuelven los valores correctos. `WeakMap` y `WeakSet` usan igualdad " +#~ "por **referencia**, por lo que dos objetos distintos nunca colisionan en " +#~ "el mismo slot." + +#~ msgid "" +#~ "The one caveat is that Perry's garbage collector does not yet treat these " +#~ "references as _weak_, so targets are **retained rather than collected**. " +#~ "The current runtime stores `WeakRef` targets and `FinalizationRegistry` " +#~ "registrations in ordinary object/array fields (`crates/perry-runtime/src/" +#~ "weakref.rs`), and the adjacent GC root scanners do not have a weak-slot " +#~ "clearing/finalizer queue hook yet. In practice:" +#~ msgstr "" +#~ "La única advertencia es que el recolector de basura de Perry aún no trata " +#~ "estas referencias como _débiles_, por lo que los objetivos se **retienen " +#~ "en lugar de recolectarse**. El runtime actual almacena los objetivos de " +#~ "`WeakRef` y los registros de `FinalizationRegistry` en campos ordinarios " +#~ "de objetos/arrays (`crates/perry-runtime/src/weakref.rs`), y los " +#~ "escáneres de raíces de GC adyacentes aún no tienen un gancho de cola de " +#~ "limpieza/finalización de slots débiles. En la práctica:" + +#~ msgid "" +#~ "`WeakRef.deref()` always returns the original target (it is never " +#~ "reported as collected)." +#~ msgstr "" +#~ "`WeakRef.deref()` siempre devuelve el objetivo original (nunca se reporta " +#~ "como recolectado)." + +#~ msgid "" +#~ "`FinalizationRegistry` records registrations but never fires its cleanup " +#~ "callback." +#~ msgstr "" +#~ "`FinalizationRegistry` registra las inscripciones pero nunca ejecuta su " +#~ "callback de limpieza." + +#~ msgid "" +#~ "`WeakMap` / `WeakSet` keep their keys alive (they behave like a reference-" +#~ "keyed `Map` / `Set`)." +#~ msgstr "" +#~ "`WeakMap` / `WeakSet` mantienen vivas sus claves (se comportan como un " +#~ "`Map` / `Set` indexado por referencia)." + +#~ msgid "" +#~ "This is safe for **correctness** — code that reads through these APIs " +#~ "gets the right values. It only matters if you depend on collection " +#~ "_timing_ to reclaim memory or to run finalizer side effects." +#~ msgstr "" +#~ "Esto es seguro en cuanto a **corrección** — el código que lee a través de " +#~ "estas APIs obtiene los valores correctos. Solo importa si dependes del " +#~ "_momento_ de recolección para liberar memoria o ejecutar efectos " +#~ "secundarios de finalizadores." + +#~ msgid "" +#~ "**Not supported**: Packages requiring native addons (`.node` files), " +#~ "`eval()`, dynamic `require()`, or Node.js internals." +#~ msgstr "" +#~ "**No soportados**: Paquetes que requieren addons nativos (archivos `." +#~ "node`), `eval()`, `require()` dinámico o elementos internos de Node.js." + +#~ msgid "Dynamic Behavior" +#~ msgstr "Comportamiento dinámico" + +#~ msgid "" +#~ "For cases where you need dynamic behavior, use the JavaScript runtime " +#~ "fallback:" +#~ msgstr "" +#~ "Para los casos en que necesitas comportamiento dinámico, usa el fallback " +#~ "del runtime de JavaScript:" + +#~ msgid "" +#~ "```text\n" +#~ "import { jsEval } from \"perry/jsruntime\";\n" +#~ "// Routes specific code through QuickJS for dynamic evaluation\n" +#~ "```" +#~ msgstr "" +#~ "```text\n" +#~ "import { jsEval } from \"perry/jsruntime\";\n" +#~ "// Routes specific code through QuickJS for dynamic evaluation\n" +#~ "```" + +#~ msgid "" +#~ "Perry gives you real OS threads with a one-line API. No worker setup, no " +#~ "message ports, no structured clone overhead. Just `parallelMap`, " +#~ "`parallelFilter`, and `spawn`." +#~ msgstr "" +#~ "Perry te ofrece threads reales del sistema operativo con una API de una " +#~ "sola línea. Sin configuración de workers, sin puertos de mensajes, sin " +#~ "overhead de structured clone. Solo `parallelMap`, `parallelFilter` y " +#~ "`spawn`." + +#~ msgid "" +#~ "This is something **no JavaScript runtime can do**. V8, Bun, and Deno are " +#~ "all locked to one thread per isolate. Perry compiles to native code — " +#~ "there are no isolates, no GIL, no structural limitations. Your code runs " +#~ "on real OS threads with the full power of every CPU core." +#~ msgstr "" +#~ "Esto es algo que **ningún runtime de JavaScript puede hacer**. V8, Bun y " +#~ "Deno están todos bloqueados a un solo hilo por isolate. Perry compila a " +#~ "código nativo — no hay isolates, no hay GIL, no hay limitaciones " +#~ "estructurales. Tu código se ejecuta en hilos reales del sistema operativo " +#~ "con toda la potencia de cada núcleo de CPU." + +#~ msgid "Why This Matters" +#~ msgstr "Por Qué Esto Importa" + +#~ msgid "" +#~ "JavaScript's single-threaded model is its biggest performance bottleneck. " +#~ "Here's how runtimes try to work around it:" +#~ msgstr "" +#~ "El modelo de un solo hilo de JavaScript es su mayor cuello de botella de " +#~ "rendimiento. Así es como los runtimes intentan trabajar alrededor de ello:" + +#~ msgid "Runtime" +#~ msgstr "Runtime" + +#~ msgid "\"Multi-threading\"" +#~ msgstr "\"Multi-threading\"" + +#~ msgid "Reality" +#~ msgstr "Realidad" + +#~ msgid "**Node.js**" +#~ msgstr "**Node.js**" + +#~ msgid "" +#~ "Separate V8 isolates. Data copied via structured clone. ~2MB RAM per " +#~ "worker. Complex API." +#~ msgstr "" +#~ "Isolates de V8 separados. Datos copiados mediante clonación estructurada. " +#~ "~2MB de RAM por worker. API compleja." + +#~ msgid "**Deno**" +#~ msgstr "**Deno**" + +#~ msgid "Same as Node — isolated heaps, message passing only." +#~ msgstr "Igual que Node — heaps aislados, solo paso de mensajes." + +#~ msgid "**Bun**" +#~ msgstr "**Bun**" + +#~ msgid "Same architecture. Faster structured clone, still isolated." +#~ msgstr "" +#~ "Misma arquitectura. Clonación estructurada más rápida, sigue siendo " +#~ "aislado." + +#~ msgid "**Perry**" +#~ msgstr "**Perry**" + +#~ msgid "`parallelMap` / `spawn`" +#~ msgstr "`parallelMap` / `spawn`" + +#~ msgid "" +#~ "Real OS threads. Lightweight (8MB stack). One-line API. Compile-time " +#~ "safety." +#~ msgstr "" +#~ "Hilos reales del sistema operativo. Ligeros (stack de 8MB). API de una " +#~ "línea. Seguridad en tiempo de compilación." + +#~ msgid "" +#~ "The fundamental problem: V8 uses a garbage-collected heap that **cannot " +#~ "be shared** between threads. Every \"worker\" is an entirely separate " +#~ "JavaScript engine instance with its own heap, its own GC, and its own " +#~ "copy of your data." +#~ msgstr "" +#~ "El problema fundamental: V8 usa un heap con recolección de basura que " +#~ "**no puede compartirse** entre hilos. Cada \"worker\" es una instancia " +#~ "completamente separada del motor de JavaScript con su propio heap, su " +#~ "propio GC y su propia copia de los datos." + +#~ msgid "" +#~ "Perry doesn't have this limitation. It compiles TypeScript to native " +#~ "machine code. Values are transferred between threads using zero-cost " +#~ "copies for numbers and efficient serialization for objects — no separate " +#~ "engine instances, no multi-megabyte overhead per thread." +#~ msgstr "" +#~ "Perry no tiene esta limitación. Compila TypeScript a código máquina " +#~ "nativo. Los valores se transfieren entre hilos usando copias de costo " +#~ "cero para números y serialización eficiente para objetos — sin instancias " +#~ "de motor separadas, sin sobrecarga de varios megabytes por hilo." + +#~ msgid "" +#~ "Perry enforces thread safety **at compile time**. You don't need to think " +#~ "about race conditions, mutexes, or data corruption." +#~ msgstr "" +#~ "Perry aplica la seguridad de hilos **en tiempo de compilación**. No es " +#~ "necesario pensar en condiciones de carrera, mutexes ni corrupción de " +#~ "datos." + +#~ msgid "" +#~ "Each worker thread has its own memory arena. Objects created on one " +#~ "thread can never be accessed from another thread. Values cross thread " +#~ "boundaries only through deep-copy serialization, which Perry handles " +#~ "automatically and invisibly." +#~ msgstr "" +#~ "Cada hilo de trabajo tiene su propia arena de memoria. Los objetos " +#~ "creados en un hilo nunca pueden ser accedidos desde otro hilo. Los " +#~ "valores cruzan los límites de los hilos únicamente mediante serialización " +#~ "por copia profunda, que Perry gestiona de forma automática e invisible." + +#~ msgid "" +#~ "Fail the build if any runtime-unknown `eval(...)` / `new " +#~ "Function()` site is reachable. By default such a site is " +#~ "compiled to a deferred runtime error (throws only if reached) and a " +#~ "compile-time notice is printed. Also settable via `perry.eval = \"error" +#~ "\"` / `perry.strict = true` (package.json or perry.toml). " +#~ "`PERRY_ALLOW_EVAL=1` forces it off. See [Limitations](../language/" +#~ "limitations.md#no-eval-or-dynamic-code)." +#~ msgstr "" +#~ "Falla en la compilación si se puede acceder a cualquier sitio desconocido " +#~ "de `eval(...)` / `new Function()`. Por defecto, tal sitio " +#~ "se compila a un error de tiempo de ejecución diferido (sólo se lanza si " +#~ "se alcanza) y se imprime un aviso de tiempo de compilación. También se " +#~ "puede configurar mediante `perry.eval = \"error\"` / `perry.strict = " +#~ "true` (package.json o perry.toml). `PERRY_ALLOW_EVAL=1` lo obliga a " +#~ "retirarse. Véase [Limitations](../language/limitations.md#no-eval-or-" +#~ "dynamic-code)." + +#~ msgid "" +#~ "Fail the build if a dynamic `import(...)` has a runtime-computed (non-" +#~ "resolvable) specifier. By default such a site is compiled to a rejected " +#~ "`Promise` that throws a descriptive `Error` only if reached, and is " +#~ "listed in the same end-of-build notice as deferred eval sites. Also " +#~ "settable via `perry.dynamicImport = \"error\"` / `perry.strict = true` " +#~ "(package.json or perry.toml). `PERRY_ALLOW_EVAL=1` forces it off. " +#~ "Resolvable forms (string literals, ternaries of resolvable arms, template " +#~ "literals over const locals, finite union-typed params, glob) are " +#~ "unaffected. See [Limitations](../language/limitations.md#no-eval-or-" +#~ "dynamic-code)." +#~ msgstr "" +#~ "Fallar la construcción si un `import(...)` dinámico tiene un " +#~ "especificador de tiempo de ejecución (no resoluble). Por defecto, este " +#~ "sitio se compila a un `Promise` rechazado que lanza un `Error` " +#~ "descriptivo sólo si se alcanza, y está listado en el mismo aviso de fin " +#~ "de construcción que sitios de valores diferidos. También se puede " +#~ "configurar mediante `perry.dynamicImport = \"error\"` / `perry.strict = " +#~ "true` (package.json o perry.toml). `PERRY_ALLOW_EVAL=1` lo obliga a " +#~ "retirarse. Las formas resolubles (encadenados literales, ternarios de " +#~ "brazos resolubles, plantillas literales sobre locales de const, params " +#~ "finitos de tipo unión, glob) no se ven afectadas. Véase [Limitations](../" +#~ "language/limitations.md#no-eval-or-dynamic-code)." + +#~ msgid "" +#~ "If you've ever wondered \"does Perry use reference counting?\" — no. " +#~ "There is no `Rc` at runtime. Perry has a real tracing GC, described below." +#~ msgstr "" +#~ "Si alguna vez se preguntó «¿usa Perry reference counting?» — no. No " +#~ "existe ningún `Rc` en tiempo de ejecución. Perry tiene un GC de rastreo " +#~ "real, descrito a continuación." + +#~ msgid "" +#~ "Perry is single-threaded by default, and each thread owns its own heap. " +#~ "Sharing across threads happens via deep copy (`SerializedValue`), not " +#~ "shared memory, so the GC never has to synchronize across threads." +#~ msgstr "" +#~ "Perry es monohilo por defecto, y cada hilo posee su propio heap. El " +#~ "intercambio entre hilos se realiza mediante copia profunda " +#~ "(`SerializedValue`), no mediante memoria compartida, por lo que el GC " +#~ "nunca necesita sincronizarse entre hilos." + +#~ msgid "Within a thread, the heap is two arenas:" +#~ msgstr "Dentro de un hilo, el heap está formado por dos arenas:" + +#~ msgid "" +#~ "**`ARENA`** — the nursery. New allocations land here. Carved into 1 MB " +#~ "blocks (since v0.5.196)." +#~ msgstr "" +#~ "**`ARENA`** — la nursery. Las nuevas asignaciones aterrizan aquí. " +#~ "Dividida en bloques de 1 MB (desde v0.5.196)." + +#~ msgid "" +#~ "**`OLD_ARENA`** — the old generation. Holds objects that have survived " +#~ "enough minor GCs to be tenured." +#~ msgstr "" +#~ "**`OLD_ARENA`** — la generación antigua. Contiene objetos que han " +#~ "sobrevivido suficientes GC menores para ser promovidos." + +#~ msgid "" +#~ "Allocation goes through `gc_malloc(size, obj_type)` in the `gc/` module " +#~ "tree. LLVM-generated code emits calls to this for every object literal, " +#~ "array literal, closure capture, string concat, BigInt operation, etc. " +#~ "Going through the GC allocation funnel is how the collector accounts for " +#~ "memory and decides when to collect." +#~ msgstr "" +#~ "La asignación pasa por `gc_malloc(size, obj_type)` en el árbol de módulos " +#~ "`gc/`. El código generado por LLVM emite llamadas a esto para cada objeto " +#~ "literal, array literal, captura de cierre, cadena concat, operación " +#~ "BigInt, etc. Ir a través del embudo de asignación GC es cómo el colector " +#~ "cuenta para la memoria y decide cuándo recoger." + +#~ msgid "" +#~ "One pointer-local analysis feeds two correct lowerings. On supported 64-" +#~ "bit AArch64/arm64 and x86-64 targets, native RS4GC statepoints plus " +#~ "Perry's compact stack map are the default. `arm64_32` watchOS, ARM64 " +#~ "Windows, and unsupported architectures use Perry's heap-backed shadow " +#~ "frames. The fallback is a root lowering, not an unrooted mode; see [the " +#~ "current GC page](garbage-collector.md#roots-by-target)." +#~ msgstr "" +#~ "Un análisis local de punteros alimenta dos descensos correctos. En los " +#~ "objetivos compatibles de 64 bits AArch64/arm64 y x86-64, los puntos de " +#~ "estado nativos RS4GC más el mapa de pila compacto de Perry son los " +#~ "predeterminados. `arm64_32` watchOS, ARM64 Windows y arquitecturas no " +#~ "soportadas utilizan los marcos de sombras de Perry. El retroceso es un " +#~ "descenso de raíz, no un modo sin raíces; véase [la página actual del GC]" +#~ "(garbage-collector.md#roots-by-target)." + +#~ msgid "" +#~ "**The non-copying minor** uses two flag bits in `gc_flags` (`crates/perry-" +#~ "runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` on the first minor an " +#~ "object survives, `GC_FLAG_TENURED` on the second, at which point the " +#~ "object is logically promoted. The two-bit scheme avoids a counter field " +#~ "in the header, and a tenured object initially stays physically where it " +#~ "is — that promotion is a flag flip, not a copy. Whether such objects are " +#~ "then evacuated into `OLD_ARENA` (with every reference rewritten) is a " +#~ "policy decision taken per cycle from nursery/RSS pressure and measured " +#~ "movable candidates, and it requires generated write barriers to be active." +#~ msgstr "" +#~ "**El menor que no copia** utiliza dos bits de la bandera en `gc_flags` " +#~ "(`crates/perry-runtime/src/gc/types.rs`): `GC_FLAG_HAS_SURVIVED` en el " +#~ "primer menor un objeto sobrevive, `GC_FLAG_TENURED` en el segundo, en " +#~ "cuyo punto el objeto se promueve lógicamente. El esquema de dos bits " +#~ "evita un campo de contador en la cabecera, y un objeto en posesión " +#~ "inicialmente permanece físicamente donde está - que la promoción es un " +#~ "flip de bandera, no una copia. Si estos objetos son evacuados a " +#~ "`OLD_ARENA` (con cada reescrito de referencia) es una decisión política " +#~ "tomada por ciclo de presión nursery/RSS y candidatos móviles medidos, y " +#~ "requiere barreras de escritura generadas para estar activo." + +#~ msgid "" +#~ "With generated write barriers active and policy evacuation allowed, " +#~ "stress-copy every marked non-pinned nursery object instead of only " +#~ "tenured survivors." +#~ msgstr "" +#~ "Con las barreras de escritura generadas activas y la evacuación por " +#~ "política permitida, copia forzosamente cada objeto de nursery marcado y " +#~ "no anclado, en lugar de solo los supervivientes tenured." + +#~ msgid "" +#~ "**Current as of 2026-08-12.** This is the source of truth for the " +#~ "collector that ships. The [generational plan](https://github.com/PerryTS/" +#~ "perry/blob/main/docs/generational-gc-plan.md) and [statepoint experiment]" +#~ "(https://github.com/PerryTS/perry/blob/main/docs/statepoint-gc-experiment." +#~ "md) are chronological evidence; their opening decisions describe the date " +#~ "they were written, not today's defaults." +#~ msgstr "" +#~ "**Actual desde 2026-08-12.** Esta es la fuente de la verdad para el " +#~ "colector que los barcos. Los [plan generacional](https://github.com/" +#~ "PerryTS/perry/blob/main/docs/generational-gc-plan.md) y [Experimento de " +#~ "estado](https://github.com/PerryTS/perry/blob/main/docs/statepoint-gc-" +#~ "experiment.md) son evidencia cronológica; sus decisiones de apertura " +#~ "describen la fecha en que fueron escritos, no los defaults de hoy." + +#~ msgid "" +#~ "This page deliberately names **no issue numbers**. A sentence of the form " +#~ "\"that phase is still unsliced (see the tracker)\" becomes false the " +#~ "moment the tracker closes, and nothing in the tree notices — which is " +#~ "exactly how three paragraphs here went stale inside one landing train. " +#~ "State what the code does; the tracker's state is not in this repository. " +#~ "Every number below is bound to the constant it came from by a `gc-fact` " +#~ "marker and re-derived by `scripts/check_gc_doc_claims.py`, which `lint` " +#~ "runs." +#~ msgstr "" +#~ "Esta página nombra deliberadamente **sin números de emisión**. Una frase " +#~ "de la forma \"esa fase todavía está sin cortar (ver el rastreador)\" se " +#~ "vuelve falsa en el momento en que el rastreador se cierra, y nada en los " +#~ "avisos del árbol - que es exactamente cómo tres párrafos aquí se quedó " +#~ "rancio dentro de un tren de aterrizaje. Indique lo que hace el código; el " +#~ "estado del rastreador no está en este repositorio. Cada número de abajo " +#~ "está unido a la constante que proviene de un marcador `gc-fact` y re-" +#~ "derivado por `scripts/check_gc_doc_claims.py`, que `lint` ejecuta." + +#~ msgid "" +#~ "**Born-old thresholds are type-dependent.** A pointer-free allocation " +#~ "above 16 KiB" +#~ msgstr "" +#~ "**Los umbrales de edad son dependientes del tipo.** Una asignación libre " +#~ "de punteros superior a 16 KiB" + +#~ msgid "" +#~ "is born in old-gen; a pointer-bearing one stays nursery-resident until " +#~ "128 KiB" +#~ msgstr "" +#~ "nace de edad avanzada; uno que lleva punteros permanece residente en el " +#~ "vivero hasta 128 KiB" + +#~ msgid "" +#~ "The split matters when reading a survival ratio: a flat threshold sends " +#~ "the intermediate backing stores of a growing array straight to old-gen, " +#~ "so the garbage they abandon never appears in the young generation at all." +#~ msgstr "" +#~ "La división importa cuando se lee una relación de supervivencia: un " +#~ "umbral plano envía las tiendas intermedias de respaldo de una matriz en " +#~ "crecimiento directamente a la vieja generación, por lo que la basura que " +#~ "abandonan nunca aparece en la generación joven en absoluto." + +#~ msgid "" +#~ "All JavaScript values are represented as 64-bit NaN-boxed values. The " +#~ "upper 16 bits encode the type tag:" +#~ msgstr "" +#~ "Todos los valores de JavaScript se representan como valores NaN-boxed de " +#~ "64 bits. Los 16 bits superiores codifican la etiqueta de tipo:" + +#~ msgid "" +#~ "Generational mark-sweep GC, per-thread arena split into nursery + old-" +#~ "gen. Roots come from a precise shadow stack (emitted by codegen), a " +#~ "conservative native-stack scan, and 9 registered runtime scanners. Two-" +#~ "bit aging tenures objects after surviving 2 minor cycles; a write barrier " +#~ "maintains a remembered set for old → young pointers." +#~ msgstr "" +#~ "GC de marca y barrido generacional, arena por hilo dividida en nursery + " +#~ "old-gen. Las raíces provienen de un shadow stack preciso (emitido por el " +#~ "codegen), un escaneo conservador de la pila nativa y 9 escáneres de " +#~ "runtime registrados. El envejecimiento de dos bits promueve objetos a " +#~ "tenured tras sobrevivir 2 ciclos menores; una barrera de escritura " +#~ "mantiene un conjunto de recordados para punteros de old → young." + +#~ msgid "The codegen crate is organized into focused modules:" +#~ msgstr "El crate de codegen está organizado en módulos enfocados:" + +#~ msgid "The HIR lowering was split into 8 modules:" +#~ msgstr "El descenso de HIR fue dividido en 8 módulos:" + #~ msgid "`perry-ext-events`" #~ msgstr "`perry-ext-events`" @@ -92894,8 +93656,8 @@ msgstr "" #~ "\n" #~ "function commanderExample(): void {\n" #~ " const program = new Command()\n" -#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI " -#~ "tool\")\n" +#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI tool" +#~ "\")\n" #~ "\n" #~ " program\n" #~ " .command(\"serve\")\n" @@ -92914,8 +93676,8 @@ msgstr "" #~ "\n" #~ "function commanderExample(): void {\n" #~ " const program = new Command()\n" -#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI " -#~ "tool\")\n" +#~ " program.name(\"my-cli\").version(\"1.0.0\").description(\"My CLI tool" +#~ "\")\n" #~ "\n" #~ " program\n" #~ " .command(\"serve\")\n" @@ -94275,8 +95037,8 @@ msgstr "" #~ msgstr "Luego golpe y etiqueta:" #~ msgid "" -#~ "# Bump [workspace.package] version in Cargo.toml AND the \"Current " -#~ "Version\"" +#~ "# Bump [workspace.package] version in Cargo.toml AND the \"Current Version" +#~ "\"" #~ msgstr "" #~ "# Versión Bump [workspace.package] en Cargo.toml Y la \"Versión actual\"" @@ -94590,9 +95352,9 @@ msgstr "" #~ "`StringHeader` from `perry-runtime` directly." #~ msgstr "" #~ "`StringHeader` se reexporta como el tipo canónico para tipos de retorno y " -#~ "parámetros en `extern \"C\"` — los wrappers deben escribir `pub extern " -#~ "\"C\" fn js_my_module_thing() -> *mut perry_ffi::StringHeader`, no " -#~ "importar `StringHeader` directamente desde `perry-runtime`." +#~ "parámetros en `extern \"C\"` — los wrappers deben escribir `pub extern \"C" +#~ "\" fn js_my_module_thing() -> *mut perry_ffi::StringHeader`, no importar " +#~ "`StringHeader` directamente desde `perry-runtime`." #~ msgid "What's NOT in v0.5" #~ msgstr "Qué NO está en v0.5" @@ -94816,8 +95578,8 @@ msgstr "" #~ "authoring inline styles compiles and links cleanly on Windows; the visual " #~ "rendering catches up when that issue lands." #~ msgstr "" -#~ "**Windows** tiene 5 propiedades en una \"familia de pintado " -#~ "diferido\" (`shadow`, `opacity`, `border_color`, `border_width`, `text." +#~ "**Windows** tiene 5 propiedades en una \"familia de pintado diferido" +#~ "\" (`shadow`, `opacity`, `border_color`, `border_width`, `text." #~ "decoration`) donde el símbolo FFI existe y almacena los parámetros " #~ "solicitados, pero se necesita un paso de renderizado personalizado " #~ "`WM_PAINT` para hacerlos visibles — rastreado en el issue [\\#210]" @@ -95269,9 +96031,8 @@ msgstr "" #~ msgid "" #~ "The result: at any GC safepoint, the collector can walk the shadow stack " #~ "and see the live NaN-boxed value of every TS-level local in every active " -#~ "frame, regardless of what LLVM did with registers. This is the " -#~ "\"precise\" half of the root scan — `shadow_stack_root_scanner` (`gc." -#~ "rs:3860`)." +#~ "frame, regardless of what LLVM did with registers. This is the \"precise" +#~ "\" half of the root scan — `shadow_stack_root_scanner` (`gc.rs:3860`)." #~ msgstr "" #~ "El resultado: en cualquier punto seguro del GC, el recolector puede " #~ "recorrer la shadow stack y ver el valor NaN-boxed vivo de cada local a " @@ -95533,9 +96294,6 @@ msgstr "" #~ msgid "No WeakMap/WeakRef" #~ msgstr "Sin WeakMap/WeakRef" -#~ msgid "Weak references are not implemented:" -#~ msgstr "Las referencias débiles no están implementadas:" - #~ msgid "No Proxy" #~ msgstr "Sin Proxy" diff --git a/docs/po/fr.po b/docs/po/fr.po index 02d05a2a93e..61c275afdc8 100644 --- a/docs/po/fr.po +++ b/docs/po/fr.po @@ -65,7 +65,7 @@ msgid "Native Layout Values" msgstr "Valeurs à disposition mémoire native" #: src/SUMMARY.md:21 src/language/decorators.md:1 -#: src/language/limitations.md:176 src/packages/porting.md:144 +#: src/language/limitations.md:85 src/packages/porting.md:144 msgid "Decorators" msgstr "Décorateurs" @@ -222,7 +222,7 @@ msgstr "Hooks" #: src/SUMMARY.md:68 src/threading/parallel-map.md:157 #: src/threading/parallel-filter.md:78 src/threading/spawn.md:191 -#: src/tui/examples.md:1 src/cli/flags.md:508 +#: src/tui/examples.md:1 src/cli/flags.md:530 msgid "Examples" msgstr "Exemples" @@ -246,7 +246,7 @@ msgstr "Plateformes" #: src/system/audio.md:99 src/system/media.md:86 src/system/other.md:20 #: src/system/other.md:37 src/plugins/native-extensions.md:302 #: src/plugins/appstore-review.md:96 src/testing/geisterhand.md:259 -#: src/testing/geisterhand.md:331 src/cli/flags.md:103 +#: src/testing/geisterhand.md:331 src/cli/flags.md:125 #: src/cli/app-updates.md:125 msgid "macOS" msgstr "macOS" @@ -315,7 +315,7 @@ msgstr "Publié sur l'App Store" #: src/widgets/platforms.md:18 src/widgets/platforms.md:25 #: src/widgets/platforms.md:49 src/widgets/platforms.md:76 #: src/plugins/native-extensions.md:303 src/plugins/appstore-review.md:109 -#: src/testing/geisterhand.md:261 src/cli/flags.md:108 src/cli/flags.md:109 +#: src/testing/geisterhand.md:261 src/cli/flags.md:130 src/cli/flags.md:131 #: src/cli/perry-toml.md:528 msgid "Android" msgstr "Android" @@ -326,7 +326,7 @@ msgstr "Android" msgid "Wear OS" msgstr "Wear OS" -#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:117 +#: src/SUMMARY.md:81 src/platforms/harmonyos.md:1 src/cli/flags.md:139 msgid "HarmonyOS NEXT" msgstr "HarmonyOS NEXT" @@ -345,7 +345,7 @@ msgstr "HarmonyOS NEXT" #: src/system/keychain.md:28 src/system/notifications.md:123 #: src/system/audio.md:103 src/system/media.md:92 src/system/other.md:23 #: src/system/other.md:40 src/testing/geisterhand.md:263 -#: src/testing/geisterhand.md:392 src/cli/flags.md:121 +#: src/testing/geisterhand.md:392 src/cli/flags.md:143 #: src/cli/app-updates.md:127 msgid "Windows" msgstr "Windows" @@ -368,11 +368,11 @@ msgstr "Linux (GTK4)" #: src/system/preferences.md:39 src/system/keychain.md:30 #: src/system/notifications.md:125 src/system/audio.md:104 #: src/system/audio_module.md:99 src/system/media.md:95 src/system/other.md:25 -#: src/system/other.md:42 src/cli/flags.md:120 +#: src/system/other.md:42 src/cli/flags.md:142 msgid "Web" msgstr "Web" -#: src/SUMMARY.md:86 src/cli/flags.md:119 +#: src/SUMMARY.md:86 src/cli/flags.md:141 msgid "WebAssembly" msgstr "WebAssembly" @@ -642,11 +642,11 @@ msgstr "`PERRY_SANDBOX_BUILDRS`" msgid "`--emit-attest` (binary attestation sidecar)" msgstr "`--emit-attest` (sidecar d'attestation binaire)" -#: src/SUMMARY.md:178 src/cli/flags.md:428 +#: src/SUMMARY.md:178 src/cli/flags.md:450 msgid "`--emit-sandbox`" msgstr "`--emit-sandbox`" -#: src/SUMMARY.md:179 src/cli/flags.md:429 +#: src/SUMMARY.md:179 src/cli/flags.md:451 msgid "`--lockdown`" msgstr "`--lockdown`" @@ -719,53 +719,57 @@ msgstr "Gestion explicite de la mémoire" msgid "The GC rooting invariant (codegen)" msgstr "L’invariant d’enracinement du GC (génération de code)" -#: src/SUMMARY.md:201 src/internals/local-binding-type-evidence.md:1 +#: src/SUMMARY.md:201 src/internals/program-facts.md:1 +msgid "Program facts" +msgstr "Faits du programme" + +#: src/SUMMARY.md:202 src/internals/local-binding-type-evidence.md:1 msgid "Local binding type evidence" msgstr "Preuves de type pour les liaisons locales" -#: src/SUMMARY.md:202 src/internals/codegen-mechanisms.md:1 +#: src/SUMMARY.md:203 src/internals/codegen-mechanisms.md:1 msgid "Codegen mechanisms and workload evidence" msgstr "" -#: src/SUMMARY.md:203 +#: src/SUMMARY.md:204 msgid "Incremental GC step bounds" msgstr "Limites des étapes du GC incrémental" -#: src/SUMMARY.md:204 src/internals/rfc-rooting-by-construction.md:1 +#: src/SUMMARY.md:205 src/internals/rfc-rooting-by-construction.md:1 msgid "RFC: rooting by construction" msgstr "RFC : enracinement par construction" -#: src/SUMMARY.md:205 src/internals/node-api-host.md:1 +#: src/SUMMARY.md:206 src/internals/node-api-host.md:1 msgid "Node-API host design" msgstr "Node-API host design" -#: src/SUMMARY.md:206 src/internals/fallback-diagnostics.md:1 +#: src/SUMMARY.md:207 src/internals/fallback-diagnostics.md:1 #, fuzzy msgid "Generic fallback diagnostics" msgstr "Sémantique générale des objets" -#: src/SUMMARY.md:208 +#: src/SUMMARY.md:209 msgid "Contributing" msgstr "Contribuer" -#: src/SUMMARY.md:210 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 +#: src/SUMMARY.md:211 src/platforms/watchos.md:25 src/platforms/harmonyos.md:5 #: src/testing/geisterhand.md:683 src/contributing/architecture.md:1 msgid "Architecture" msgstr "Architecture" -#: src/SUMMARY.md:211 src/contributing/crate-policy.md:1 +#: src/SUMMARY.md:212 src/contributing/crate-policy.md:1 msgid "Crate policy" msgstr "Politique des crates" -#: src/SUMMARY.md:212 src/contributing/building.md:1 +#: src/SUMMARY.md:213 src/contributing/building.md:1 msgid "Building from Source" msgstr "Compiler depuis les sources" -#: src/SUMMARY.md:213 src/contributing/build-storage.md:1 +#: src/SUMMARY.md:214 src/contributing/build-storage.md:1 msgid "Linux agent build storage" msgstr "" -#: src/SUMMARY.md:214 src/contributing/releasing.md:1 +#: src/SUMMARY.md:215 src/contributing/releasing.md:1 msgid "Releasing Perry" msgstr "Publier une version de Perry" @@ -1012,7 +1016,7 @@ msgstr "" "natifs — pas d'Electron, pas de WebView." #: src/introduction.md:81 src/getting-started/first-app.md:41 -#: src/threading/overview.md:323 src/threading/parallel-map.md:18 +#: src/threading/overview.md:308 src/threading/parallel-map.md:18 #: src/threading/parallel-filter.md:18 src/platforms/watchos.md:214 #: src/platforms/wasm.md:29 src/stdlib/overview.md:8 src/i18n/overview.md:78 #: src/widgets/overview.md:36 src/plugins/overview.md:7 @@ -1035,8 +1039,8 @@ msgstr "" #: src/introduction.md:96 src/getting-started/hello-world.md:155 #: src/getting-started/first-app.md:184 #: src/getting-started/project-config.md:327 -#: src/language/supported-features.md:631 src/language/type-system.md:149 -#: src/language/limitations.md:393 src/threading/overview.md:350 +#: src/language/supported-features.md:629 src/language/type-system.md:153 +#: src/language/limitations.md:250 src/threading/overview.md:392 #: src/ui/overview.md:239 src/ui/widgets.md:706 src/ui/layout.md:393 #: src/ui/styling.md:387 src/ui/state.md:226 src/ui/events.md:167 #: src/ui/canvas.md:93 src/ui/menus.md:119 src/ui/tray.md:106 @@ -1047,7 +1051,7 @@ msgstr "" #: src/platforms/watchos.md:316 src/platforms/android.md:97 #: src/platforms/wearos.md:208 src/platforms/windows.md:144 #: src/platforms/linux.md:84 src/platforms/web.md:21 src/platforms/wasm.md:191 -#: src/stdlib/overview.md:128 src/stdlib/fs.md:98 src/stdlib/http.md:408 +#: src/stdlib/overview.md:128 src/stdlib/fs.md:105 src/stdlib/http.md:408 #: src/stdlib/database.md:112 src/stdlib/crypto.md:87 #: src/stdlib/utilities.md:96 src/stdlib/other.md:333 src/i18n/overview.md:108 #: src/updater/overview.md:445 src/system/overview.md:77 @@ -1059,8 +1063,8 @@ msgstr "" #: src/plugins/overview.md:96 src/plugins/creating-plugins.md:162 #: src/plugins/hooks-and-events.md:144 src/plugins/native-extensions.md:324 #: src/plugins/appstore-review.md:199 src/cli/commands.md:437 -#: src/cli/flags.md:545 src/contributing/architecture.md:109 -#: src/contributing/building.md:201 +#: src/cli/flags.md:567 src/contributing/architecture.md:85 +#: src/contributing/building.md:233 msgid "Next Steps" msgstr "Étapes suivantes" @@ -1245,7 +1249,7 @@ msgstr "" #: src/system/background.md:78 src/system/other.md:18 src/system/other.md:35 #: src/widgets/data-fetching.md:142 src/widgets/platforms.md:14 #: src/plugins/native-extensions.md:298 src/testing/geisterhand.md:257 -#: src/cli/flags.md:96 src/cli/function-order.md:54 +#: src/cli/flags.md:118 src/cli/function-order.md:54 #: src/contributing/releasing.md:168 msgid "Platform" msgstr "Plateforme" @@ -1270,7 +1274,7 @@ msgstr "macOS x64 (Intel)" msgid "`@perryts/perry-darwin-x64`" msgstr "`@perryts/perry-darwin-x64`" -#: src/getting-started/installation.md:72 src/cli/flags.md:126 +#: src/getting-started/installation.md:72 src/cli/flags.md:148 msgid "Linux x64 (glibc)" msgstr "Linux x64 (glibc)" @@ -1278,7 +1282,7 @@ msgstr "Linux x64 (glibc)" msgid "`@perryts/perry-linux-x64`" msgstr "`@perryts/perry-linux-x64`" -#: src/getting-started/installation.md:73 src/cli/flags.md:127 +#: src/getting-started/installation.md:73 src/cli/flags.md:149 msgid "Linux arm64 (glibc)" msgstr "Linux arm64 (glibc)" @@ -1302,7 +1306,7 @@ msgstr "Linux arm64 (musl / Alpine)" msgid "`@perryts/perry-linux-arm64-musl`" msgstr "`@perryts/perry-linux-arm64-musl`" -#: src/getting-started/installation.md:76 src/cli/flags.md:122 +#: src/getting-started/installation.md:76 src/cli/flags.md:144 msgid "Windows x64" msgstr "Windows x64" @@ -1539,7 +1543,7 @@ msgstr "" #: src/i18n/overview.md:94 src/updater/overview.md:271 #: src/system/preferences.md:38 src/system/keychain.md:29 #: src/system/notifications.md:124 src/system/audio.md:102 -#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:125 +#: src/system/other.md:24 src/system/other.md:41 src/cli/flags.md:147 #: src/cli/app-updates.md:126 msgid "Linux" msgstr "Linux" @@ -2606,11 +2610,11 @@ msgstr "" #: src/getting-started/project-config.md:53 msgid "" -"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": " -"\"default\"` disables automatic Solid detection. A host setting overrides " -"per-file detection. The equivalent TOML setting is `[perry]` with `jsx = " -"{ runtime = \"@opentui/solid\" }`; TOML overrides the host's package " -"setting. React/Preact import sources keep their existing JSX behavior." +"The existing `\"jsx\": \"solid\"` selects `perry-solid`; `\"jsx\": \"default" +"\"` disables automatic Solid detection. A host setting overrides per-file " +"detection. The equivalent TOML setting is `[perry]` with `jsx = { runtime = " +"\"@opentui/solid\" }`; TOML overrides the host's package setting. React/" +"Preact import sources keep their existing JSX behavior." msgstr "" #: src/getting-started/project-config.md:59 @@ -2652,16 +2656,16 @@ msgid "" "Compiling third-party TypeScript into your binary is a privileged operation, " "so every entry in `compilePackages` must _also_ be matched by an entry in " "`perry.allow.compilePackages` in the same `package.json` — otherwise the " -"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/" -"*\"`), or the universal `\"*\"`:" +"build refuses. Patterns accept exact names, scope wildcards (`\"@scope/*" +"\"`), or the universal `\"*\"`:" msgstr "" "**Deux touches pour s'inscrire.** La liste d'un colis n'est pas suffisante à " "elle seule. La compilation de TypeScript de tiers dans votre binaire est une " "opération privilégiée, de sorte que chaque entrée dans `compilePackages` " "doit également être assortie d'une entrée dans `perry.allow.compilePackages` " "dans le même `package.json` sinon la compilation refuse. Les modèles " -"acceptent des noms exacts, des jokers de portée (`\"@scope/*\"`) ou le " -"`\"*\"` universel:" +"acceptent des noms exacts, des jokers de portée (`\"@scope/*\"`) ou le `\"*" +"\"` universel:" #: src/getting-started/project-config.md:87 src/cli/allow-perry-features.md:55 #: src/cli/allow-perry-features.md:70 src/cli/allow-perry-features.md:83 @@ -2730,9 +2734,9 @@ msgid "" "actual `.node` edge fails. Naming the addon package exactly still fails " "immediately with the native-addon diagnostic." msgstr "" -"Les formulaires universels (`\"auto\"`, `\"all\"`, `true`, ou un joker " -"`\"*\"`) sautent les paquets identifiés comme des addons natifs de Node au " -"lieu d'essayer de compiler les binaires N-API comme source. Cela permet à " +"Les formulaires universels (`\"auto\"`, `\"all\"`, `true`, ou un joker `\"*" +"\"`) sautent les paquets identifiés comme des addons natifs de Node au lieu " +"d'essayer de compiler les binaires N-API comme source. Cela permet à " "l'accélérateur natif optionnel protégé d'une dépendance de revenir à son " "implémentation JavaScript. Un sous-piste de source pure importé statiquement " "d'un tel paquet peut encore être compilé, mais un bord `.node` réel échoue. " @@ -3144,10 +3148,10 @@ msgstr "Champ" #: src/cli/commands.md:48 src/cli/commands.md:92 src/cli/commands.md:142 #: src/cli/commands.md:183 src/cli/commands.md:216 src/cli/commands.md:230 #: src/cli/commands.md:268 src/cli/commands.md:283 src/cli/commands.md:295 -#: src/cli/flags.md:80 src/cli/flags.md:135 src/cli/flags.md:143 -#: src/cli/flags.md:191 src/cli/flags.md:291 src/cli/flags.md:379 -#: src/cli/flags.md:399 src/cli/flags.md:412 src/cli/flags.md:419 -#: src/cli/flags.md:435 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 +#: src/cli/flags.md:102 src/cli/flags.md:157 src/cli/flags.md:165 +#: src/cli/flags.md:213 src/cli/flags.md:313 src/cli/flags.md:401 +#: src/cli/flags.md:421 src/cli/flags.md:434 src/cli/flags.md:441 +#: src/cli/flags.md:457 src/cli/perry-toml.md:117 src/cli/perry-toml.md:128 #: src/cli/perry-toml.md:156 src/cli/perry-toml.md:172 #: src/cli/perry-toml.md:182 src/cli/perry-toml.md:188 #: src/cli/perry-toml.md:198 src/cli/perry-toml.md:248 @@ -3353,7 +3357,7 @@ msgstr "" "pour les packages TS/JS purs, ou le repli sur le runtime JavaScript pour les " "packages complexes." -#: src/getting-started/project-config.md:289 src/contributing/building.md:177 +#: src/getting-started/project-config.md:289 src/contributing/building.md:209 msgid "Project Structure" msgstr "Structure du projet" @@ -3551,10 +3555,11 @@ msgstr "Notification locale" #: src/platforms/windows-7.md:25 src/container/overview.md:71 #: src/container/security.md:105 src/container/determinism.md:59 #: src/testing/geisterhand.md:87 src/testing/geisterhand.md:101 -#: src/cli/flags.md:562 src/cli/flags.md:668 src/cli/flags.md:669 +#: src/cli/flags.md:584 src/cli/flags.md:690 src/cli/flags.md:691 #: src/cli/dynamic-dispatch.md:123 src/cli/allow-js-runtime.md:67 #: src/cli/sandbox-buildrs.md:25 src/cli/sandbox-buildrs.md:53 -#: src/cli/perry-toml.md:431 src/internals/gc-rooting-invariant.md:285 +#: src/cli/perry-toml.md:431 src/internals/garbage-collector.md:463 +#: src/internals/gc-rooting-invariant.md:285 #: src/internals/codegen-mechanisms.md:56 #: src/internals/rfc-rooting-by-construction.md:354 #: src/contributing/releasing.md:202 @@ -3679,8 +3684,8 @@ msgid "" " // Arrow functions\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" msgstr "" @@ -3703,33 +3708,33 @@ msgstr "" " // Arrow functions\n" " const double = (x: number) => x * 2;\n" "\n" -" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} " -"sum=${sum(1, 2, 3)} double=${double(5)}`)\n" +" console.log(`functions: add=${add(2, 3)} greet=${greet(\"Perry\")} sum=" +"${sum(1, 2, 3)} double=${double(5)}`)\n" "}\n" "```" -#: src/language/supported-features.md:60 src/api/reference.md:223 -#: src/api/reference.md:257 src/api/reference.md:291 src/api/reference.md:349 -#: src/api/reference.md:373 src/api/reference.md:462 src/api/reference.md:509 -#: src/api/reference.md:530 src/api/reference.md:555 src/api/reference.md:830 -#: src/api/reference.md:913 src/api/reference.md:961 src/api/reference.md:981 -#: src/api/reference.md:1090 src/api/reference.md:1168 -#: src/api/reference.md:1207 src/api/reference.md:1256 -#: src/api/reference.md:1284 src/api/reference.md:1445 -#: src/api/reference.md:1653 src/api/reference.md:1676 -#: src/api/reference.md:1730 src/api/reference.md:1761 -#: src/api/reference.md:1815 src/api/reference.md:1859 -#: src/api/reference.md:1993 src/api/reference.md:2135 -#: src/api/reference.md:2371 src/api/reference.md:2860 -#: src/api/reference.md:2873 src/api/reference.md:2944 -#: src/api/reference.md:3006 src/api/reference.md:3115 -#: src/api/reference.md:3141 src/api/reference.md:3154 -#: src/api/reference.md:3165 src/api/reference.md:3292 -#: src/api/reference.md:3337 src/api/reference.md:3378 -#: src/api/reference.md:3398 src/api/reference.md:3427 -#: src/api/reference.md:3523 src/api/reference.md:3581 -#: src/api/reference.md:3617 src/api/reference.md:3635 -#: src/api/reference.md:3683 src/api/reference.md:3723 +#: src/language/supported-features.md:60 src/api/reference.md:224 +#: src/api/reference.md:258 src/api/reference.md:292 src/api/reference.md:351 +#: src/api/reference.md:375 src/api/reference.md:464 src/api/reference.md:511 +#: src/api/reference.md:532 src/api/reference.md:557 src/api/reference.md:832 +#: src/api/reference.md:915 src/api/reference.md:963 src/api/reference.md:983 +#: src/api/reference.md:1092 src/api/reference.md:1170 +#: src/api/reference.md:1209 src/api/reference.md:1258 +#: src/api/reference.md:1286 src/api/reference.md:1447 +#: src/api/reference.md:1655 src/api/reference.md:1678 +#: src/api/reference.md:1732 src/api/reference.md:1763 +#: src/api/reference.md:1817 src/api/reference.md:1861 +#: src/api/reference.md:1995 src/api/reference.md:2137 +#: src/api/reference.md:2376 src/api/reference.md:2869 +#: src/api/reference.md:2882 src/api/reference.md:2953 +#: src/api/reference.md:3015 src/api/reference.md:3124 +#: src/api/reference.md:3150 src/api/reference.md:3163 +#: src/api/reference.md:3174 src/api/reference.md:3301 +#: src/api/reference.md:3357 src/api/reference.md:3398 +#: src/api/reference.md:3418 src/api/reference.md:3447 +#: src/api/reference.md:3543 src/api/reference.md:3601 +#: src/api/reference.md:3637 src/api/reference.md:3655 +#: src/api/reference.md:3703 src/api/reference.md:3743 msgid "Classes" msgstr "Classes" @@ -3897,7 +3902,7 @@ msgstr "" "Les interfaces et alias de type sont effacés à la compilation (comme `tsc`). " "Ils existent uniquement pour la documentation et l'outillage de l'éditeur." -#: src/language/supported-features.md:144 src/threading/overview.md:343 +#: src/language/supported-features.md:144 src/threading/overview.md:328 #: src/threading/parallel-map.md:58 msgid "Arrays" msgstr "Tableaux" @@ -3942,11 +3947,11 @@ msgid "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" msgstr "" @@ -3988,15 +3993,15 @@ msgstr "" " console.log(item);\n" " }\n" "\n" -" console.log(`arrays: len=${len} doubled=${doubled.length} " -"filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} " -"joined=${joined} sliced=${sliced.length} sorted=${sorted.length} " -"reversed=${reversed.length} includes=${includes} every=${every} some=${some} " -"flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)\n" +" console.log(`arrays: len=${len} doubled=${doubled.length} filtered=" +"${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} " +"sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} " +"includes=${includes} every=${every} some=${some} flat=${flat.length} " +"concatted=${concatted.length} arr=${arr.length}`)\n" "}\n" "```" -#: src/language/supported-features.md:188 src/threading/overview.md:344 +#: src/language/supported-features.md:188 src/threading/overview.md:329 #: src/threading/parallel-map.md:59 msgid "Objects" msgstr "Objets" @@ -4085,8 +4090,8 @@ msgid "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" msgstr "" @@ -4114,8 +4119,8 @@ msgstr "" " }\n" "\n" " process(user)\n" -" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} " -"name=${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" +" console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=" +"${name} age=${age} email=${email} userName=${userName} id=${id}`)\n" "}\n" "```" @@ -4135,8 +4140,8 @@ msgid "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" msgstr "" @@ -4150,8 +4155,8 @@ msgstr "" "`;\n" " const expr = `Result: ${1 + 2}`;\n" "\n" -" console.log(`template-literals: greeting=${greeting} " -"multiline_len=${multiline.length} expr=${expr}`)\n" +" console.log(`template-literals: greeting=${greeting} multiline_len=" +"${multiline.length} expr=${expr}`)\n" "}\n" "```" @@ -4206,7 +4211,7 @@ msgstr "" "```" #: src/language/supported-features.md:282 src/native-libraries/overview.md:190 -#: src/native-libraries/abi.md:76 src/threading/overview.md:345 +#: src/native-libraries/abi.md:76 src/threading/overview.md:330 msgid "Closures" msgstr "Closures" @@ -4590,7 +4595,7 @@ msgstr "" "```" #: src/language/supported-features.md:470 src/native-libraries/overview.md:192 -#: src/internals/memory-model.md:22 +#: src/internals/memory-model.md:24 msgid "BigInt" msgstr "BigInt" @@ -4720,19 +4725,19 @@ msgstr "" "}\n" "```" -#: src/language/supported-features.md:563 src/contributing/architecture.md:57 +#: src/language/supported-features.md:563 src/contributing/architecture.md:62 msgid "Garbage Collection" msgstr "Ramasse-miettes" #: src/language/supported-features.md:565 msgid "" -"Perry includes a mark-sweep garbage collector. It runs automatically when " -"memory pressure is detected (~8MB arena blocks), but you can also trigger it " -"manually:" +"Perry uses a tracing generational collector with automatic allocation-" +"pressure and safepoint scheduling. You can also request collection " +"explicitly:" msgstr "" -"Perry inclut un ramasse-miettes mark-sweep. Il s'exécute automatiquement " -"lorsqu'une pression mémoire est détectée (blocs d'arène d'environ 8 Mo), " -"mais vous pouvez aussi le déclencher manuellement :" +"Perry utilise un collecteur générationnel par traçage, planifié " +"automatiquement selon la pression d’allocation et les safepoints. Vous " +"pouvez aussi demander explicitement une collecte :" #: src/language/supported-features.md:567 msgid "" @@ -4750,13 +4755,15 @@ msgstr "" #: src/language/supported-features.md:573 msgid "" -"The GC uses conservative stack scanning to find roots and supports arena-" -"allocated objects (arrays, objects) and malloc-allocated objects (strings, " -"closures, promises, BigInts, errors)." +"Copying minors, non-moving fallbacks and full sweeps cooperate with precise " +"generated roots and runtime root scanners. Conservative stack scanning is " +"diagnostic-only. See the [memory model](../internals/memory-model.md) for " +"allocation and rooting details." msgstr "" -"Le GC utilise un balayage conservateur de la pile pour trouver les racines " -"et prend en charge les objets alloués sur l'arène (tableaux, objets) et les " -"objets alloués via malloc (chaînes, closures, promesses, BigInts, erreurs)." +"Copies mineures, replis non mobiles et balayages complets coopèrent avec les" +" racines générées précises et les scanners du runtime. Le balayage " +"conservateur est uniquement diagnostique. Voir le [modèle " +"mémoire](../internals/memory-model.md) pour allocation et racines." #: src/language/supported-features.md:575 msgid "JSX/TSX" @@ -4845,20 +4852,19 @@ msgstr "" #: src/language/supported-features.md:615 msgid "" -"There are no guarantees for every dynamic JavaScript pattern (the " -"[Limitations](limitations.md) still apply — no `eval`, no general dynamic " -"`require()`), but most plain JavaScript projects compile and run." +"Dynamic evaluation and CommonJS have supported paths, with limits on " +"interpreter behavior and runtime module loading. See " +"[Limitations](limitations.md) for the current boundaries." msgstr "" -"Il n'y a pas de garanties pour chaque modèle dynamique JavaScript (le " -"[Limitations](limitations.md) s'applique toujours pas de `eval`, pas de " -"`require()` dynamique général), mais la plupart des projets JavaScript " -"simples sont compilés et exécutés." +"L’évaluation dynamique et CommonJS ont des chemins pris en charge, avec des " +"limites d’interprétation et de chargement à l’exécution. Voir les limites " +"actuelles dans [Limitations](limitations.md)." -#: src/language/supported-features.md:619 +#: src/language/supported-features.md:617 msgid "Node.js Compatibility" msgstr "Node.js Compatibility" -#: src/language/supported-features.md:621 +#: src/language/supported-features.md:619 msgid "" "Perry implements a large, real (non-stub) slice of the Node.js standard " "library — `fs`, `http`/`https`/`http2`, `net`/`tls`, `dns`/`dgram`, " @@ -4885,25 +4891,26 @@ msgstr "" "TypeScript d'environ 95%. La surface par module et les espaces restants sont " "tracés en `docs/runtime-parity-gaps.md`." -#: src/language/supported-features.md:633 +#: src/language/supported-features.md:631 msgid "[Type System](type-system.md) — Type inference and checking" msgstr "" "[Système de types](type-system.md) — Inférence et vérification de types" -#: src/language/supported-features.md:634 +#: src/language/supported-features.md:632 msgid "[Limitations](limitations.md) — What's not supported yet" msgstr "[Limitations](limitations.md) — Ce qui n'est pas encore pris en charge" #: src/language/type-system.md:3 msgid "" -"Perry erases types at compile time, similar to how `tsc` removes type " -"annotations when emitting JavaScript. However, Perry also performs type " -"inference to generate efficient native code." +"TypeScript annotations describe source-level intent; they do not validate " +"runtime values. Perry combines local evidence, program facts and runtime " +"guards to select native representations. Decorator metadata is an explicit " +"exception to annotation erasure." msgstr "" -"Perry efface les types à la compilation, de manière similaire à `tsc` qui " -"retire les annotations de type lors de la génération de JavaScript. " -"Cependant, Perry effectue également une inférence de types pour générer du " -"code natif efficace." +"Les annotations TypeScript expriment l’intention du source, sans valider les" +" valeurs à l’exécution. Perry combine preuves locales, faits du programme et" +" gardes à l’exécution pour choisir les représentations natives. Les " +"métadonnées des décorateurs font explicitement exception à l’effacement." #: src/language/type-system.md:5 msgid "Type Inference" @@ -5057,11 +5064,11 @@ msgstr "Types utilitaires" #: src/language/type-system.md:64 msgid "" -"Common TypeScript utility types are erased at compile time (they don't " -"affect code generation):" +"Utility types describe static relationships and do not introduce runtime " +"wrappers or validation:" msgstr "" -"Les types utilitaires courants de TypeScript sont effacés à la compilation " -"(ils n'affectent pas la génération de code) :" +"Les types utilitaires décrivent des relations statiques sans ajouter " +"d’enveloppes ni de validation à l’exécution :" #: src/language/type-system.md:75 msgid "" @@ -5120,11 +5127,13 @@ msgstr "" #: src/language/type-system.md:100 msgid "" -"At runtime, all values are NaN-boxed — the generic parameter doesn't affect " -"code generation." +"A generic parameter alone is not a runtime representation proof. Generic " +"value boundaries use NaN-boxing, while proven computations can use native " +"scalars and specialized storage." msgstr "" -"À l'exécution, toutes les valeurs sont NaN-boxées — le paramètre générique " -"n'affecte pas la génération de code." +"Un paramètre générique ne prouve pas une représentation à l’exécution. Les " +"frontières génériques utilisent NaN-boxing ; les calculs prouvés peuvent " +"utiliser des scalaires natifs et du stockage spécialisé." #: src/language/type-system.md:102 msgid "Type Checking with `--type-check`" @@ -5140,12 +5149,17 @@ msgstr "" #: src/language/type-system.md:110 msgid "" -"This resolves cross-file types, interfaces, and generics via an IPC " -"protocol. It falls back gracefully if the type checker is not installed." +"This starts `tsgo`, loads the project tsconfig and requests diagnostics and " +"resolved types over IPC. Reported type errors stop compilation. Missing " +"tooling, project-load failures or unavailable diagnostics can leave " +"compilation running without checking; this option is not a guaranteed " +"standalone type-check gate." msgstr "" -"Cela résout les types, interfaces et génériques inter-fichiers via un " -"protocole IPC. Il bascule en mode dégradé si le vérificateur de types n'est " -"pas installé." +"Démarre `tsgo`, charge la tsconfig et demande diagnostics et types résolus " +"par IPC. Les erreurs de type signalées arrêtent la compilation. L’absence " +"d’outils, un échec de chargement ou des diagnostics indisponibles peuvent " +"laisser continuer sans vérification : cette option ne garantit pas à elle " +"seule un contrôle de types bloquant." #: src/language/type-system.md:112 msgid "" @@ -5162,10 +5176,13 @@ msgstr "Types union et intersection" #: src/language/type-system.md:116 msgid "" -"Union types are recognized syntactically but don't affect code generation:" +"Unions and intersections do not create runtime tags of their own. " +"Specialization requires evidence about the actual value, such as a validated" +" guard:" msgstr "" -"Les types union sont reconnus syntaxiquement mais n'affectent pas la " -"génération de code :" +"Unions et intersections ne créent pas de tags à l’exécution. La " +"spécialisation exige des preuves sur la valeur réelle, par exemple une garde" +" validée :" #: src/language/type-system.md:118 msgid "" @@ -5239,13 +5256,31 @@ msgstr "" "L'annotation `value is string` est effacée, mais la vérification `typeof` " "fonctionne à l'exécution." +#: src/language/type-system.md:149 +msgid "Program facts and specialization" +msgstr "Faits du programme et spécialisation" + #: src/language/type-system.md:151 +msgid "" +"The default facts pass analyzes value flow across modules. It can prove " +"receiver sets, field layouts and call targets; unknown behavior retains " +"checks or the generic path. See [Program facts](../internals/program-" +"facts.md) and [Local binding type evidence](../internals/local-binding-type-" +"evidence.md)." +msgstr "" +"La passe de faits par défaut analyse le flux de valeurs entre modules. Elle " +"peut prouver ensembles de receveurs, layouts de champs et cibles d’appels ; " +"un comportement inconnu conserve les vérifications ou le chemin générique. " +"Voir [Faits du programme](../internals/program-facts.md) et [Preuves de " +"types des liaisons locales](../internals/local-binding-type-evidence.md)." + +#: src/language/type-system.md:155 msgid "[Supported Features](supported-features.md) — Complete feature list" msgstr "" "[Fonctionnalités prises en charge](supported-features.md) — Liste complète " "des fonctionnalités" -#: src/language/type-system.md:152 +#: src/language/type-system.md:156 msgid "[Limitations](limitations.md) — What's not supported" msgstr "[Limitations](limitations.md) — Ce qui n'est pas pris en charge" @@ -5382,7 +5417,7 @@ msgstr "" #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 #: src/widgets/creating-widgets.md:48 src/widgets/configuration.md:70 -#: src/testing/geisterhand.md:84 src/cli/flags.md:135 src/cli/perry-toml.md:117 +#: src/testing/geisterhand.md:84 src/cli/flags.md:157 src/cli/perry-toml.md:117 #: src/cli/perry-toml.md:128 src/cli/perry-toml.md:156 #: src/cli/perry-toml.md:172 src/cli/perry-toml.md:182 #: src/cli/perry-toml.md:188 src/cli/perry-toml.md:198 @@ -5392,8 +5427,8 @@ msgstr "" #: src/cli/perry-toml.md:334 src/cli/perry-toml.md:353 #: src/cli/perry-toml.md:365 src/cli/perry-toml.md:375 #: src/cli/perry-toml.md:385 src/cli/perry-toml.md:414 -#: src/cli/perry-toml.md:441 src/internals/memory-model.md:15 -#: src/contributing/architecture.md:48 +#: src/cli/perry-toml.md:441 src/internals/memory-model.md:17 +#: src/contributing/architecture.md:51 msgid "Type" msgstr "Type" @@ -5490,7 +5525,7 @@ msgid "IEEE-754 binary32" msgstr "Les données de référence sont les suivantes:" #: src/language/native-values.md:75 src/native-libraries/manifest-v1.md:223 -#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:27 +#: src/native-libraries/manifest-v1.md:238 src/internals/memory-model.md:30 msgid "`f64`" msgstr "`f64`" @@ -5534,8 +5569,8 @@ msgstr "" #: src/language/native-values.md:90 msgid "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -5549,8 +5584,8 @@ msgid "" "```" msgstr "" "```typescript,no-test\n" -"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/" -"native\";\n" +"import { i8, i16, u8, u16, i32, u32, u64, isize, f32 } from \"perry/native" +"\";\n" "\n" "const delta = i8(dynamicDelta);\n" "const offset16 = i16(dynamicOffset);\n" @@ -6170,151 +6205,42 @@ msgstr "" "via des vérifications `typeof` explicites et `instanceof`." #: src/language/limitations.md:29 -msgid "No eval() or Dynamic Code" -msgstr "Pas d'eval() ni de code dynamique" +msgid "Dynamic code and module loading" +msgstr "Code dynamique et chargement de modules" #: src/language/limitations.md:31 msgid "" -"Perry compiles to native code ahead of time. It cannot evaluate a code " -"string that is only known at runtime. A _constant_ code string is the " -"exception — `eval(\"1 + 1\")` and `new Function(\"a\", \"b\", \"return a + " -"b\")` are compiled to real native functions (#1679). Only a body built from " -"runtime data hits this limit:" -msgstr "" -"Perry compile en code natif à l'avance. Il ne peut pas évaluer une chaîne de " -"code qui n'est connue qu'à l'exécution. Une chaîne de code _constant_ est " -"l'exception `eval(\"1 + 1\")` et `new Function(\"a\", \"b\", \"return a + " -"b\")` sont compilées en fonctions natives réelles (#1679). Seul un corps " -"construit à partir de données d'exécution atteint cette limite:" - -#: src/language/limitations.md:37 -msgid "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Constant body → compiled natively (works)\n" -"const add = new Function(\"a\", \"b\", \"return a + b\");\n" -"\n" -"// Runtime-built body → cannot be compiled ahead of time\n" -"function run(src: string) { return new Function(`return (${src})`)(); }\n" -"```" - -#: src/language/limitations.md:45 -msgid "Default: deferred runtime error + compile-time notice (#5206)" -msgstr "Par défaut: erreur d'exécution différée + avis de compilation (#5206)" - -#: src/language/limitations.md:47 -msgid "" -"By default a runtime-unknown `eval(...)` / `new Function()` " -"site does **not** block the build. Perry compiles it to a value that throws " -"a descriptive `Error` _only if it is actually reached_ (an `eval(...)` " -"throws when evaluated; a `new Function(...)` returns a function that throws " -"when called), and prints a single end-of-compile notice listing every " -"degraded site:" -msgstr "" -"Par défaut, un site `eval(...)` / `new Function()` à exécution " -"inconnue **Je ne sais pas** Bloquez le bâtiment. Perry le compile à une " -"valeur qui renvoie un `Error` descriptif _uniquement s'il est réellement " -"atteint_ (un `eval(...)` renvoie lorsqu'il est évalué; un `new " -"Function(...)` renvoie une fonction qui renvoie lorsqu'il est appelé), et " -"imprime un seul avis de fin de compilation répertoriant chaque site dégradé:" - -#: src/language/limitations.md:61 -msgid "" -"This lets a single such call in a cold path ship without aborting the whole " -"build, while still failing loudly (and catchably) if that path runs." -msgstr "" -"Cela permet à un seul appel de ce type dans un navire de voie froide sans " -"interrompre toute la construction, tout en échouant fortement (et " -"facilement) si cette voie se déroule." - -#: src/language/limitations.md:64 -msgid "Dynamic `import()` with a runtime-computed specifier (#5230)" -msgstr "" -"Dynamique `import()` avec un spécificateur calculé en temps d'exécution " -"(#5230)" - -#: src/language/limitations.md:66 -#, fuzzy -msgid "" -"For code modules, a dynamic `import(spec)` whose `spec` is only known at " -"runtime (a plugin loader building a path from a variable) is subject to the " -"**same defer/notice/strict policy** as `eval`. By default it compiles to a " -"rejected `Promise` carrying a descriptive `Error` (so `await import(spec)` " -"throws _only if reached_), is listed in the shared notice above under the " -"`import(...)` kind, and does **not** abort the build. This lets an app with " -"a plugin-loader path compile and run its core, with only the plugin-load " -"path throwing if exercised." -msgstr "" -"Un `import(spec)` dynamique dont le `spec` n'est connu qu'à l'exécution (un " -"chargeur de plug-in construisant un chemin à partir d'une variable) est " -"soumis à la **même stratégie defer/notice/strict** comme `eval`. Par défaut, " -"il compile à un `Promise` rejeté portant un `Error` descriptif (donc `await " -"import(spec)` lance _ seulement si atteint_), est répertorié dans l'avis " -"partagé ci-dessus sous le type `import(...)`, et fait **Je ne sais pas** " -"Abandonnez la construction. Cela permet à une application avec un chemin de " -"chargement de plugins de compiler et d'exécuter son noyau, avec seulement le " -"chemin de chargement de plugins jeté si exercé." - -#: src/language/limitations.md:74 -msgid "" -"If the runtime specifier names a supported Node builtin, Perry resolves its " -"native namespace. Otherwise, when it has no compiled target, the rejected " -"`Error` retains `code: \"ERR_MODULE_NOT_FOUND\"` for existing optional-" -"dependency handlers. Its message names the requested module, explains that " -"runtime-loaded JavaScript is unavailable in the native build, and suggests " -"the application's Bun/Node distribution or a statically resolvable import " -"followed by recompilation. Deferred sites also retain their source location. " -"Perry does not print a second runtime warning: the application's catch/" -"report path owns the diagnostic." -msgstr "" - -#: src/language/limitations.md:83 -msgid "" -"Resolvable specifiers are unaffected and still compile + load: string " -"literals (`import(\"./mod.js\")`), ternaries of resolvable arms, template " -"literals over `const` locals (``import(`./${KIND}.js`)``), finite string-" -"literal-union parameters, and directory globs." -msgstr "" -"Les spécificateurs résolubles ne sont pas affectés et compilent toujours + " -"charge: littéraux de chaîne (`import(\"./mod.js\")`), ternaires de bras " -"résolubles, littéraux de modèle sur les locaux `const` (``import(`./${KIND}." -"js`)``), paramètres d'union littérale de chaîne finie et globes de " -"répertoires." - -#: src/language/limitations.md:88 -msgid "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" -msgstr "" -"```typescript,no-test\n" -"// Resolvable → compiled + loaded as today.\n" -"const real = await import(\"./real.js\");\n" -"\n" -"// Runtime-computed → deferred (default): throws only if this line runs.\n" -"async function loadPlugin(name: string) {\n" -" return await import(name + \".js\");\n" -"}\n" -"```" - -#: src/language/limitations.md:98 +"Constant `eval` and `Function` bodies can compile ahead of time. Runtime-" +"generated bodies reach Perry’s linked interpreter; they are not " +"automatically deferred errors. Interpreter support does not imply full " +"engine parity or the ability to load arbitrary JavaScript packages from " +"disk." +msgstr "" +"Les corps constants de `eval` et `Function` peuvent être compilés à " +"l’avance. Les corps générés à l’exécution passent par l’interpréteur lié de " +"Perry ; ils ne deviennent pas automatiquement des erreurs différées. Cela ne" +" garantit ni une parité complète ni le chargement arbitraire de paquets " +"JavaScript sur disque." + +#: src/language/limitations.md:33 +msgid "" +"Dynamic `import()` and `require()` can resolve supported builtins and " +"modules included in the compiled graph. Installing a package after " +"compilation does not add it to that graph. Unavailable code targets fail at " +"runtime; resolvable literals and supported finite-specifier forms can be " +"compiled into the executable." +msgstr "" +"`import()` dynamique et `require()` résolvent les builtins pris en charge et" +" les modules du graphe compilé. Installer ensuite un paquet ne l’ajoute pas " +"au graphe. Les cibles absentes échouent à l’exécution ; les littéraux " +"résolubles et formes finies prises en charge peuvent être intégrés à " +"l’exécutable." + +#: src/language/limitations.md:35 msgid "Native OpenCode plugins (#10105)" msgstr "" -#: src/language/limitations.md:100 +#: src/language/limitations.md:37 msgid "" "The first native OpenCode deliverable follows **option A** from [\\#10105]" "(https://github.com/PerryTS/perry/issues/10105), tracked in [\\#10107]" @@ -6325,7 +6251,7 @@ msgid "" "providers reached through static imports remain supported." msgstr "" -#: src/language/limitations.md:108 +#: src/language/limitations.md:45 msgid "" "Use the Bun distribution when those extensions are required. Including " "extensions in a statically reachable import graph and recompiling is a build-" @@ -6336,7 +6262,7 @@ msgid "" "as TOML are a separate feature (#10104)." msgstr "" -#: src/language/limitations.md:115 +#: src/language/limitations.md:52 msgid "" "OpenCode v1.18.30's `PluginLoader.load` catches import rejections, and its " "caller reports a load failure without retrying that stage. The TUI plugin " @@ -6347,58 +6273,58 @@ msgid "" "remains part of the integration acceptance in #10107." msgstr "" -#: src/language/limitations.md:123 +#: src/language/limitations.md:60 msgid "" "Data files can be loaded at runtime using import attributes (#10104). The " "specifier must be an absolute filesystem path or a `file://` URL, and the " "result has a `default` export:" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 msgid "Import attribute `type`" msgstr "" -#: src/language/limitations.md:127 +#: src/language/limitations.md:64 #, fuzzy msgid "Default export" msgstr "Comportement par défaut" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 #, fuzzy msgid "`\"toml\"`" msgstr "`perry.toml`" -#: src/language/limitations.md:129 +#: src/language/limitations.md:66 msgid "Parsed TOML table, using the same parser as `Bun.TOML.parse`" msgstr "" -#: src/language/limitations.md:130 src/native-libraries/manifest-v1.md:225 +#: src/language/limitations.md:67 src/native-libraries/manifest-v1.md:225 msgid "`\"json\"`" msgstr "`\"json\"`" -#: src/language/limitations.md:130 +#: src/language/limitations.md:67 msgid "Parsed JSON value" msgstr "" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 #, fuzzy msgid "`\"text\"`" msgstr "\"text\"" -#: src/language/limitations.md:131 +#: src/language/limitations.md:68 msgid "File contents as a string" msgstr "" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 #, fuzzy msgid "`\"file\"`" msgstr "`filename`" -#: src/language/limitations.md:132 +#: src/language/limitations.md:69 msgid "Filesystem path as a string" msgstr "" -#: src/language/limitations.md:134 +#: src/language/limitations.md:71 msgid "" "```typescript,no-test\n" "import { pathToFileURL } from \"node:url\";\n" @@ -6408,7 +6334,7 @@ msgid "" "```" msgstr "" -#: src/language/limitations.md:141 +#: src/language/limitations.md:78 msgid "" "TOML and JSON parse failures reject with `SyntaxError`. Missing files reject " "with an I/O error. These loaders do not load runtime code modules or resolve " @@ -6416,87 +6342,21 @@ msgid "" "runtime-computed specifiers at compile time as described below." msgstr "" -#: src/language/limitations.md:146 -msgid "Strict mode: refuse at compile time" -msgstr "Mode strict: rejeter au moment de la compilation" - -#: src/language/limitations.md:148 -msgid "" -"To make every runtime-unknown site a hard compile-time error instead, opt " -"into strict-eval mode by any of:" -msgstr "" -"Pour rendre chaque site inconnu en temps d'exécution une erreur de " -"compilation difficile, optez pour le mode strict-eval par l'un des:" - -#: src/language/limitations.md:151 -msgid "the `--strict-eval` flag on `perry compile`," -msgstr "le signal `--strict-eval` sur `perry compile`," - -#: src/language/limitations.md:152 -msgid "" -"`\"perry\": { \"eval\": \"error\" }` (or `\"perry\": { \"strict\": true }`) " -"in `package.json`, or" -msgstr "" -"`\"perry\": { \"eval\": \"error\" }` (ou `\"perry\": { \"strict\": true }`) " -"dans `package.json`, ou" - -#: src/language/limitations.md:154 -msgid "`[perry]` `eval = \"error\"` (or `strict = true`) in `perry.toml`." -msgstr "`[perry]` `eval = \"error\"` (ou `strict = true`) dans `perry.toml`." - -#: src/language/limitations.md:156 +#: src/language/limitations.md:83 msgid "" -"`perry.eval` accepts `\"defer\"` (the default) or `\"error\"`. Precedence is " -"package.json/perry.toml config → `--strict-eval` (opts in). The legacy " -"`PERRY_ALLOW_EVAL=1` environment variable still works: it forces non-strict " -"(defer) mode for a one-off build, overriding any strict flag/config." +"`--strict-eval` remains accepted but does not reject bodies sent to the " +"interpreter. `--strict-dynamic-import` still rejects unresolved asynchronous" +" import specifiers during module collection; synchronous `require()` takes " +"its runtime resolution path. `PERRY_ALLOW_EVAL=1` disables that strict " +"import check." msgstr "" -"`perry.eval` accepte `\"defer\"` (par défaut) ou `\"error\"`. La prééminence " -"est package.json/perry.toml config → `--strict-eval` (opts in). La variable " -"d'environnement `PERRY_ALLOW_EVAL=1` héritée fonctionne toujours: elle force " -"le mode non strict (defer) pour une compilation ponctuelle, remplaçant tout " -"flag/config. strict" +"`--strict-eval` reste accepté mais ne rejette pas les corps envoyés à " +"l’interpréteur. `--strict-dynamic-import` rejette toujours les " +"spécificateurs asynchrones non résolus pendant la collecte des modules ; " +"`require()` synchrone suit sa résolution à l’exécution. `PERRY_ALLOW_EVAL=1`" +" désactive ce contrôle strict d’import." -#: src/language/limitations.md:161 -msgid "" -"The same strict controls apply to runtime-computed dynamic `import()` " -"(#5230). The broad `perry.strict = true` covers both eval and dynamic " -"import. For a knob scoped to dynamic imports only, use `--strict-dynamic-" -"import` or `\"perry\": { \"dynamicImport\": \"error\" }` (accepts " -"`\"defer\"` (default) or `\"error\"`); the dedicated knob overrides the " -"broad `perry.strict` for import sites. `PERRY_ALLOW_EVAL=1` is the shared " -"AOT escape hatch — it forces defer for both eval and dynamic import." -msgstr "" -"Les mêmes contrôles stricts s'appliquent à `import()` (#5230) dynamique " -"calculé en temps d'exécution. Le large `perry.strict = true` couvre à la " -"fois eval et importation dynamique. Pour un bouton destiné uniquement aux " -"importations dynamiques, utilisez `--strict-dynamic-import` ou `\"perry\": " -"{ \"dynamicImport\": \"error\" }` (accepte `\"defer\"` (par défaut) ou " -"`\"error\"`); le bouton dédié remplace le large `perry.strict` pour les " -"sites d'importation. `PERRY_ALLOW_EVAL=1` est la trappe d'échappement AOT " -"partagée elle force à reporter à la fois l'évaluation et l'importation " -"dynamique." - -#: src/language/limitations.md:169 -msgid "" -"Test262 rows that only observe parsing or executing a code string remain " -"intentional AOT exclusions, not runtime dynamic-code work. This includes the " -"`language/white-space/comment-{multi,single}-{form-feed,horizontal-tab,nbsp," -"space,vertical-tab}.js` rows and the direct-eval reference row `language/" -"types/reference/8.7.2-1-s.js`; they map to the AOT eval tracker (#1677), " -"eval classifier diagnostics (#1678), and the limited literal `Function` " -"folding work (#1679)." -msgstr "" -"Les lignes Test262 qui observent uniquement l'analyse ou l'exécution d'une " -"chaîne de code restent des exclusions AOT intentionnelles, et non des " -"travaux de code dynamique à l'exécution. Cela inclut les lignes `language/" -"white-space/comment-{multi,single}-{form-feed,horizontal-tab,nbsp,space," -"vertical-tab}.js` ainsi que la ligne de référence d'eval direct `language/" -"types/reference/8.7.2-1-s.js` ; elles correspondent au suivi AOT de eval " -"(#1677), aux diagnostics de classification d'eval (#1678) et aux travaux " -"limités de pliage littéral de `Function` (#1679)." - -#: src/language/limitations.md:178 +#: src/language/limitations.md:87 msgid "" "Perry parses decorator syntax, supports compile-time-only transforms (see " "the bundled `@log` example), and has a reduced legacy TypeScript " @@ -6520,7 +6380,7 @@ msgstr "" "getMetadataKeys`, `Reflect.getOwnMetadataKeys`, `Reflect.deleteMetadata` et " "`@Reflect.metadata(...)`." -#: src/language/limitations.md:190 +#: src/language/limitations.md:99 msgid "" "Accessor decorators, descriptor replacement, general `Reflect.metadata(...)` " "calls outside decorator syntax, `Symbol` metadata keys, and full Angular / " @@ -6534,11 +6394,11 @@ msgstr "" "[Décorateurs](decorators.md) pour les détails et une recette de migration " "pratique." -#: src/language/limitations.md:196 +#: src/language/limitations.md:105 msgid "No Runtime Metadata Reflection" msgstr "Pas de réflexion de métadonnées à l'exécution" -#: src/language/limitations.md:198 +#: src/language/limitations.md:107 msgid "" "Perry implements a small metadata subset for legacy decorators. General " "runtime reflection is not supported:" @@ -6546,7 +6406,7 @@ msgstr "" "Perry implémente un petit sous-ensemble de métadonnées pour les décorateurs " "legacy. La réflexion générale à l'exécution n'est pas prise en charge :" -#: src/language/limitations.md:202 +#: src/language/limitations.md:111 msgid "" "```text\n" "Reflect.getMetadata(\"design:type\", target, key);\n" @@ -6562,71 +6422,33 @@ msgstr "" "Reflect.metadata(\"design:type\", String)(target, key);\n" "```" -#: src/language/limitations.md:209 -msgid "No User-Space CommonJS require()" -msgstr "Aucun espace utilisateur CommonJS require (()" - -#: src/language/limitations.md:211 -msgid "Use static ESM imports in Perry source:" -msgstr "Utilisez des imports ESM statiques dans le code source Perry :" - -#: src/language/limitations.md:214 -msgid "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" -msgstr "" -"```text\n" -"// Supported\n" -"import { foo } from \"./module\";\n" -"\n" -"// Not supported\n" -"const mod = require(\"./module\");\n" -"const mod = await import(\"./module\");\n" -"```" - -#: src/language/limitations.md:223 -msgid "" -"Perry has internal CommonJS compatibility paths for some npm package " -"wrappers, but user-written modules should use static `import` declarations." -msgstr "" -"Perry dispose de chemins de compatibilité CommonJS internes pour certains " -"wrappers de packages npm, mais les modules écrits par l'utilisateur doivent " -"utiliser des déclarations `import` statiques." +#: src/language/limitations.md:118 +msgid "JavaScript and CommonJS" +msgstr "JavaScript et CommonJS" -#: src/language/limitations.md:226 +#: src/language/limitations.md:120 msgid "" -"**JavaScript source compiles too.** Perry accepts `.js`, `.cjs`, `.mjs`, and " -"`.jsx` files as compiler input — they are parsed as JavaScript and lowered " -"through the same native pipeline as TypeScript, so no type annotations are " -"required. The limitations on this page still apply (no `eval`, no general " -"dynamic `require()`, etc.), but plain JavaScript projects compile and run in " -"most cases." +"Perry accepts `.js`, `.cjs`, `.mjs` and `.jsx` through the native pipeline. " +"CommonJS `require()` is supported for compiled modules and supported native " +"modules. It does not turn the executable into a general filesystem " +"JavaScript loader." msgstr "" -"**La source JavaScript est aussi compilée.** Perry accepte les fichiers `." -"js`, `.cjs`, `.mjs` et `.jsx` comme entrée de compilateur ils sont analysés " -"comme JavaScript et descendus par le même pipeline natif que TypeScript, " -"donc aucune annotation de type n'est requise. Les limitations de cette page " -"s'appliquent toujours (pas de `eval`, pas de `require()` dynamique général, " -"etc.), mais les projets JavaScript simples sont compilés et exécutés dans la " -"plupart des cas." +"Perry accepte `.js`, `.cjs`, `.mjs` et `.jsx` via la chaîne native. CommonJS" +" `require()` prend en charge les modules compilés et natifs compatibles. " +"L’exécutable ne devient pas un chargeur JavaScript général depuis le système" +" de fichiers." -#: src/language/limitations.md:233 +#: src/language/limitations.md:122 msgid "Prototype Manipulation" msgstr "Manipulation du prototype" -#: src/language/limitations.md:235 +#: src/language/limitations.md:124 msgid "Dynamic prototype manipulation is supported for the common patterns:" msgstr "" "La manipulation dynamique des prototypes est prise en charge pour les " "modèles communs:" -#: src/language/limitations.md:237 +#: src/language/limitations.md:126 msgid "" "```typescript,no-test\n" "// Supported\n" @@ -6650,7 +6472,7 @@ msgstr "" "Object.getPrototypeOf(obj); // inspection\n" "```" -#: src/language/limitations.md:245 +#: src/language/limitations.md:134 msgid "" "Prototype-method assignment routes through a dedicated registry " "(`CLASS_PROTOTYPE_METHODS`) and `Object.setPrototypeOf` performs a real, " @@ -6661,11 +6483,11 @@ msgstr "" "mutation de prototype réelle vérifiée par cycle (un cycle authentique lance " "toujours `TypeError`)." -#: src/language/limitations.md:249 +#: src/language/limitations.md:138 msgid "Remaining known gaps:" msgstr "Les lacunes connues restantes:" -#: src/language/limitations.md:251 +#: src/language/limitations.md:140 msgid "" "the assignment form `obj.__proto__ = x` on the dynamic write path stores a " "literal `\"__proto__\"` property instead of setting the prototype (use " @@ -6675,7 +6497,7 @@ msgstr "" "dynamique stocke une propriété littérale `\"__proto__\"` au lieu de définir " "le prototype (utiliser `Object.setPrototypeOf`);" -#: src/language/limitations.md:254 +#: src/language/limitations.md:143 msgid "" "an object-literal `{ __proto__: [] }` sets the prototype without throwing, " "but the result is not yet visible to `instanceof` (`… instanceof Array` " @@ -6685,82 +6507,31 @@ msgstr "" "le résultat n'est pas encore visible par `instanceof` (`… instanceof Array` " "rapporte `false`)." -#: src/language/limitations.md:258 -msgid "Weak References Retain Their Targets" -msgstr "Les références faibles conservent leurs cibles" +#: src/language/limitations.md:147 +msgid "Weak references and finalization" +msgstr "Références faibles et finalisation" -#: src/language/limitations.md:260 +#: src/language/limitations.md:149 msgid "" -"`WeakMap`, `WeakSet`, `WeakRef`, and `FinalizationRegistry` are implemented " -"and their APIs behave as expected — `set` / `get` / `has` / `delete`, `add`, " -"`deref()`, and `register` / `unregister` all work and return the right " -"values. `WeakMap` and `WeakSet` use **reference** equality, so two distinct " -"objects never collide on the same slot." +"WeakMap, WeakSet, WeakRef and FinalizationRegistry participate in weak " +"processing. Weak targets are excluded from the strong trace, forwarded " +"addresses are repaired, dead targets can be cleared and cleanup callbacks " +"can be queued. Collection and callback timing are not deterministic. See [GC" +" weak processing](../internals/garbage-collector.md#barriers-and-weak-" +"references)." msgstr "" -"`WeakMap`, `WeakSet`, `WeakRef` et `FinalizationRegistry` sont implémentés " -"et leurs APIs se comportent comme prévu — `set` / `get` / `has` / `delete`, " -"`add`, `deref()` et `register` / `unregister` fonctionnent tous et " -"retournent les bonnes valeurs. `WeakMap` et `WeakSet` utilisent l'égalité de " -"**référence**, donc deux objets distincts n'entrent jamais en collision sur " -"le même emplacement." +"WeakMap, WeakSet, WeakRef et FinalizationRegistry participent au traitement " +"faible. Leurs cibles sont exclues du traçage fort ; les adresses déplacées " +"sont réparées, les cibles mortes peuvent être effacées et les callbacks de " +"nettoyage mis en file. Le calendrier de collecte et des callbacks n’est pas " +"déterministe. Voir le [traitement faible du GC](../internals/garbage-" +"collector.md#barriers-and-weak-references)." -#: src/language/limitations.md:266 -msgid "" -"The one caveat is that Perry's garbage collector does not yet treat these " -"references as _weak_, so targets are **retained rather than collected**. The " -"current runtime stores `WeakRef` targets and `FinalizationRegistry` " -"registrations in ordinary object/array fields (`crates/perry-runtime/src/" -"weakref.rs`), and the adjacent GC root scanners do not have a weak-slot " -"clearing/finalizer queue hook yet. In practice:" -msgstr "" -"La seule mise en garde est que le ramasse-miettes de Perry ne traite pas " -"encore ces références comme _faibles_, donc les cibles sont **conservées " -"plutôt que collectées**. Le runtime actuel stocke les cibles `WeakRef` et " -"les enregistrements `FinalizationRegistry` dans des champs ordinaires " -"d'objet/tableau (`crates/perry-runtime/src/weakref.rs`), et les scanners de " -"racines GC adjacents ne disposent pas encore d'un hook de file d'attente de " -"nettoyage/finalisation pour les emplacements faibles. En pratique :" - -#: src/language/limitations.md:273 -msgid "" -"`WeakRef.deref()` always returns the original target (it is never reported " -"as collected)." -msgstr "" -"`WeakRef.deref()` retourne toujours la cible d'origine (elle n'est jamais " -"signalée comme collectée)." - -#: src/language/limitations.md:275 -msgid "" -"`FinalizationRegistry` records registrations but never fires its cleanup " -"callback." -msgstr "" -"`FinalizationRegistry` enregistre les inscriptions mais ne déclenche jamais " -"son callback de nettoyage." - -#: src/language/limitations.md:277 -msgid "" -"`WeakMap` / `WeakSet` keep their keys alive (they behave like a reference-" -"keyed `Map` / `Set`)." -msgstr "" -"`WeakMap` / `WeakSet` maintiennent leurs clés en vie (ils se comportent " -"comme un `Map` / `Set` à clés par référence)." - -#: src/language/limitations.md:280 -msgid "" -"This is safe for **correctness** — code that reads through these APIs gets " -"the right values. It only matters if you depend on collection _timing_ to " -"reclaim memory or to run finalizer side effects." -msgstr "" -"Ceci est sûr du point de vue de l'**exactitude** — le code qui lit via ces " -"APIs obtient les bonnes valeurs. Cela n'a d'importance que si vous dépendez " -"du _moment_ de la collecte pour récupérer de la mémoire ou exécuter des " -"effets secondaires de finalisation." - -#: src/language/limitations.md:284 +#: src/language/limitations.md:151 msgid "Limited Proxy Trapping" msgstr "Interception Proxy limitée" -#: src/language/limitations.md:286 +#: src/language/limitations.md:153 msgid "" "Proxy support is not a full engine-level trap layer for every possible " "dynamic object access. Prefer plain objects and explicit APIs unless a " @@ -6771,11 +6542,11 @@ msgstr "" "ordinaires et les APIs explicites, sauf si un package n'a besoin que de la " "surface Proxy prise en charge par Perry." -#: src/language/limitations.md:290 +#: src/language/limitations.md:157 msgid "Threading Model" msgstr "Modèle de threading" -#: src/language/limitations.md:292 +#: src/language/limitations.md:159 msgid "" "Perry supports real multi-threading via `parallelMap` and `spawn` from " "`perry/thread`. See [Multi-Threading](../threading/overview.md)." @@ -6783,7 +6554,7 @@ msgstr "" "Perry prend en charge le véritable multi-threading via `parallelMap` et " "`spawn` de `perry/thread`. Voir [Multi-Threading](../threading/overview.md)." -#: src/language/limitations.md:294 +#: src/language/limitations.md:161 msgid "" "Threads do not share mutable state by default — closures passed to thread " "primitives cannot capture mutable variables (enforced at compile time), and " @@ -6808,11 +6579,11 @@ msgstr "" "même partage une _view_ de matrice tapée capturée directement encore des " "copies profondes, alors construisez la vue par agent à partir du SAB partagé." -#: src/language/limitations.md:305 +#: src/language/limitations.md:172 msgid "WebAssembly (#6558)" msgstr "WebAssembly (#6558)" -#: src/language/limitations.md:307 +#: src/language/limitations.md:174 msgid "" "Perry ships **no WebAssembly engine** in the default build. The adopted " "policy is that **WASM-dependent features degrade gracefully**. Closed issue " @@ -6827,7 +6598,7 @@ msgstr "" "remplacement par module; il ne s'agit pas d'une promesse active d'ajouter un " "moteur complet au binaire par défaut." -#: src/language/limitations.md:313 +#: src/language/limitations.md:180 msgid "" "The `WebAssembly` global is spec-shaped — every standard member exists with " "the correct type and arity — and fails cleanly rather than crashing:" @@ -6836,7 +6607,7 @@ msgstr "" "standard existe avec le type et l'arité corrects et échoue propre plutôt " "que de s'écraser:" -#: src/language/limitations.md:316 +#: src/language/limitations.md:183 msgid "" "`WebAssembly.compile` / `compileStreaming` / `instantiate` / " "`instantiateStreaming` return a Promise **rejected** with a `WebAssembly." @@ -6846,7 +6617,7 @@ msgstr "" "`instantiateStreaming` retourner une Promesse **rejeté** avec un " "`WebAssembly.CompileError` dont le message pointe vers #6558." -#: src/language/limitations.md:319 +#: src/language/limitations.md:186 msgid "" "`WebAssembly.validate(bytes)` returns `false` — the honest answer for \"can " "I run this module here\"." @@ -6854,7 +6625,7 @@ msgstr "" "`WebAssembly.validate(bytes)` renvoie `false` la réponse honnête pour " "\"puis-je exécuter ce module ici\"." -#: src/language/limitations.md:321 +#: src/language/limitations.md:188 msgid "" "`new WebAssembly.Module(...)` throws `CompileError` synchronously; " "`Instance` throws `LinkError`; `Table` / `Global` throw `RuntimeError`." @@ -6862,7 +6633,7 @@ msgstr "" "`new WebAssembly.Module(...)` lance `CompileError` de façon synchrone; " "`Instance` lance `LinkError`; `Table` / `Global` lance `RuntimeError`." -#: src/language/limitations.md:323 +#: src/language/limitations.md:190 msgid "" "`new WebAssembly.Memory({ initial })` genuinely works (a real zero-filled " "`ArrayBuffer` backs it, and `grow()` is supported), so feature-detection " @@ -6873,7 +6644,7 @@ msgstr "" "charge), donc le code de détection de fonctionnalités qui alloue une page " "réussit." -#: src/language/limitations.md:326 +#: src/language/limitations.md:193 msgid "" "`CompileError` / `LinkError` / `RuntimeError` are real error constructors " "(`instanceof Error` and `instanceof WebAssembly.CompileError` both work)." @@ -6882,7 +6653,7 @@ msgstr "" "d'erreur réels (`instanceof Error` et `instanceof WebAssembly.CompileError` " "fonctionnent tous deux)." -#: src/language/limitations.md:329 +#: src/language/limitations.md:196 msgid "" "In practice this means lazy wasm consumers — wasm-bindgen loaders like " "`@silvia-odwyer/photon-node`, `@jsquash/webp` decoders, undici's llhttp " @@ -6895,7 +6666,7 @@ msgstr "" "catch/fallback et l'application continue à fonctionner avec cette " "fonctionnalité dégradée." -#: src/language/limitations.md:334 +#: src/language/limitations.md:201 msgid "" "When the wasmi host is linked, `new WebAssembly.Module(bytes)` and the " "synchronous `new WebAssembly.Instance(module, imports)` constructor execute " @@ -6918,11 +6689,11 @@ msgstr "" "globals, de signatures de fonctions à valeurs de référence générales et de " "streaming reste en dehors de ce sous-ensemble." -#: src/language/limitations.md:344 +#: src/language/limitations.md:211 msgid "Two spellings, two paths:" msgstr "Deux orthographes, deux chemins:" -#: src/language/limitations.md:346 +#: src/language/limitations.md:213 msgid "" "**Namespace access** (`const WA = globalThis.WebAssembly; WA.compile(...)`, " "which is also what minified bundles evaluate through a namespace value) uses " @@ -6933,7 +6704,7 @@ msgstr "" "travers une valeur d'espace de noms) utilise la surface gracieuse ci-dessus " "et n'a besoin de rien au moment de la liaison." -#: src/language/limitations.md:349 +#: src/language/limitations.md:216 #, fuzzy msgid "" "**Literal static calls** (`WebAssembly.instantiate(bytes)` written against " @@ -6951,15 +6722,15 @@ msgstr "" "`libperry_wasm_host.a` (`cargo build --release -p perry-wasm-host`); sans " "cela, la construction échoue avec cette instruction." -#: src/language/limitations.md:356 +#: src/language/limitations.md:223 msgid "npm Package Compatibility" msgstr "Compatibilité des paquets npm" -#: src/language/limitations.md:358 +#: src/language/limitations.md:225 msgid "Not all npm packages work with Perry:" msgstr "Tous les packages npm ne fonctionnent pas avec Perry :" -#: src/language/limitations.md:360 +#: src/language/limitations.md:227 msgid "" "**Natively supported**: ~50 popular packages (fastify, mysql2, redis, etc.) " "— these are compiled natively. See [Standard Library](../stdlib/overview.md)." @@ -6968,7 +6739,7 @@ msgstr "" "redis, etc.) — ceux-ci sont compilés nativement. Voir [Bibliothèque standard]" "(../stdlib/overview.md)." -#: src/language/limitations.md:361 +#: src/language/limitations.md:228 msgid "" "**`compilePackages`**: Pure TS/JS packages can be compiled natively via " "[configuration](../getting-started/project-config.md)." @@ -6977,54 +6748,33 @@ msgstr "" "compilés nativement via la [configuration](../getting-started/project-config." "md)." -#: src/language/limitations.md:362 +#: src/language/limitations.md:229 msgid "" -"**Not supported**: Packages requiring native addons (`.node` files), " -"`eval()`, dynamic `require()`, or Node.js internals." +"Native addons require the supported [Node-API host](../internals/node-api-" +"host.md), explicit authorization and a matching target binary. V8/NAN/Node " +"C++ addons are not interchangeable with Node-API addons. Dynamic loading " +"remains constrained by the compiled graph." msgstr "" -"**Non pris en charge** : Les packages nécessitant des addons natifs " -"(fichiers `.node`), `eval()`, `require()` dynamique ou des modules internes " -"de Node.js." +"Les addons natifs exigent l’[hôte Node-API](../internals/node-api-host.md) " +"pris en charge, une autorisation explicite et un binaire adapté à la cible. " +"Les addons V8/NAN/Node C++ ne sont pas interchangeables avec Node-API. Le " +"chargement dynamique reste limité au graphe compilé." -#: src/language/limitations.md:364 +#: src/language/limitations.md:231 msgid "Workarounds" msgstr "Contournements" -#: src/language/limitations.md:366 -msgid "Dynamic Behavior" -msgstr "Comportement dynamique" - -#: src/language/limitations.md:368 -msgid "" -"For cases where you need dynamic behavior, use the JavaScript runtime " -"fallback:" -msgstr "" -"Pour les cas où vous avez besoin d'un comportement dynamique, utilisez le " -"fallback runtime JavaScript :" - -#: src/language/limitations.md:371 -msgid "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" -msgstr "" -"```text\n" -"import { jsEval } from \"perry/jsruntime\";\n" -"// Routes specific code through QuickJS for dynamic evaluation\n" -"```" - -#: src/language/limitations.md:376 +#: src/language/limitations.md:233 msgid "Type Narrowing" msgstr "Rétrécissement de types" -#: src/language/limitations.md:378 +#: src/language/limitations.md:235 msgid "Since there's no runtime type checking, use explicit checks:" msgstr "" "Puisqu'il n'y a pas de vérification de type à l'exécution, utilisez des " "vérifications explicites :" -#: src/language/limitations.md:380 +#: src/language/limitations.md:237 msgid "" "```typescript\n" "function processValue(value: string | number) {\n" @@ -7052,12 +6802,12 @@ msgstr "" "}\n" "```" -#: src/language/limitations.md:395 +#: src/language/limitations.md:252 msgid "[Supported Features](supported-features.md) — What does work" msgstr "" "[Fonctionnalités prises en charge](supported-features.md) — Ce qui fonctionne" -#: src/language/limitations.md:396 +#: src/language/limitations.md:253 msgid "[Type System](type-system.md) — How types are handled" msgstr "[Système de types](type-system.md) — Comment les types sont gérés" @@ -7214,8 +6964,8 @@ msgstr "" #: src/packages/porting.md:35 msgid "" -"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a " -"`\"gypfile\"` package marker are compiled against Node's native addon ABI. " +"Packages that ship `.node` files, `binding.gyp`, `prebuilds/`, or a `" +"\"gypfile\"` package marker are compiled against Node's native addon ABI. " "The JavaScript entry point is usually just loader glue around a shared " "library that expects Node-API/N-API, NAN, V8, libuv, or Node internals. " "Perry does not host that ABI as part of `compilePackages`. Common examples " @@ -8137,7 +7887,7 @@ msgid "Documented at" msgstr "Documentée dans" #: src/native-libraries/overview.md:186 src/native-libraries/abi.md:82 -#: src/threading/overview.md:342 src/threading/parallel-map.md:57 +#: src/threading/overview.md:327 src/threading/parallel-map.md:57 msgid "Strings" msgstr "Chaînes" @@ -8622,14 +8372,14 @@ msgstr "" msgid "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" msgstr "" "```toml\n" "[dependencies]\n" -"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = " -"\"main\" }\n" +"perry-ffi = { git = \"https://github.com/PerryTS/perry\", branch = \"main" +"\" }\n" "```" #: src/native-libraries/overview.md:351 @@ -8960,7 +8710,7 @@ msgstr "" "`well_known_bindings.toml`. Régénérez cette table avec `python3 scripts/" "binding_governance.py --table`." -#: src/native-libraries/governance.md:99 src/contributing/architecture.md:24 +#: src/native-libraries/governance.md:99 src/contributing/architecture.md:25 msgid "Crate" msgstr "Crate" @@ -8985,7 +8735,7 @@ msgstr "Statut actuel" msgid "`perry-ext-ads`" msgstr "`perry-ext-ads`" -#: src/native-libraries/governance.md:101 src/api/reference.md:2172 +#: src/native-libraries/governance.md:101 src/api/reference.md:2174 msgid "`perry/ads`" msgstr "`perry/ads`" @@ -9005,7 +8755,7 @@ msgstr "En attente de déménagement" msgid "`perry-ext-argon2`" msgstr "`perry-ext-argon2`" -#: src/native-libraries/governance.md:102 src/api/reference.md:214 +#: src/native-libraries/governance.md:102 src/api/reference.md:215 msgid "`argon2`" msgstr "`argon2`" @@ -9045,7 +8795,7 @@ msgstr "En attente de migration" msgid "`perry-ext-bcrypt`" msgstr "`perry-ext-bcrypt`" -#: src/native-libraries/governance.md:103 src/api/reference.md:322 +#: src/native-libraries/governance.md:103 src/api/reference.md:323 msgid "`bcrypt`" msgstr "`bcrypt`" @@ -9053,7 +8803,7 @@ msgstr "`bcrypt`" msgid "`perry-ext-better-sqlite3`" msgstr "`perry-ext-better-sqlite3`" -#: src/native-libraries/governance.md:104 src/api/reference.md:329 +#: src/native-libraries/governance.md:104 src/api/reference.md:330 msgid "`better-sqlite3`" msgstr "`better-sqlite3`" @@ -9061,7 +8811,7 @@ msgstr "`better-sqlite3`" msgid "`perry-ext-cheerio`" msgstr "`perry-ext-cheerio`" -#: src/native-libraries/governance.md:105 src/api/reference.md:489 +#: src/native-libraries/governance.md:105 src/api/reference.md:491 msgid "`cheerio`" msgstr "`cheerio`" @@ -9085,7 +8835,7 @@ msgstr "Compiler la source du paquet en amont" msgid "`perry-ext-ethers`" msgstr "`perry-ext-ethers`" -#: src/native-libraries/governance.md:106 src/api/reference.md:1194 +#: src/native-libraries/governance.md:106 src/api/reference.md:1196 msgid "`ethers`" msgstr "`ethers`" @@ -9122,7 +8872,7 @@ msgstr "En paquet; conservés" msgid "`perry-ext-net`" msgstr "`perry-ext-net`" -#: src/native-libraries/governance.md:108 src/api/reference.md:1857 +#: src/native-libraries/governance.md:108 src/api/reference.md:1859 msgid "`net`" msgstr "`net`" @@ -9130,7 +8880,7 @@ msgstr "`net`" msgid "`perry-ext-nodemailer`" msgstr "`perry-ext-nodemailer`" -#: src/native-libraries/governance.md:109 src/api/reference.md:2015 +#: src/native-libraries/governance.md:109 src/api/reference.md:2017 msgid "`nodemailer`" msgstr "`nodemailer`" @@ -9154,7 +8904,7 @@ msgstr "" msgid "`perry-ext-pdf`" msgstr "`perry-ext-pdf`" -#: src/native-libraries/governance.md:111 src/api/reference.md:192 +#: src/native-libraries/governance.md:111 src/api/reference.md:193 msgid "`@perryts/pdf`" msgstr "`@perryts/pdf`" @@ -9162,7 +8912,7 @@ msgstr "`@perryts/pdf`" msgid "`perry-ext-sharp`" msgstr "`perry-ext-sharp`" -#: src/native-libraries/governance.md:112 src/api/reference.md:2914 +#: src/native-libraries/governance.md:112 src/api/reference.md:2923 msgid "`sharp`" msgstr "`sharp`" @@ -9170,7 +8920,7 @@ msgstr "`sharp`" msgid "`perry-ext-typescript`" msgstr "`perry-ext-typescript`" -#: src/native-libraries/governance.md:113 src/api/reference.md:3363 +#: src/native-libraries/governance.md:113 src/api/reference.md:3383 msgid "`typescript`" msgstr "`typescript`" @@ -9178,7 +8928,7 @@ msgstr "`typescript`" msgid "`perry-ext-undici`" msgstr "`perry-ext-undici`" -#: src/native-libraries/governance.md:114 src/api/reference.md:3376 +#: src/native-libraries/governance.md:114 src/api/reference.md:3396 msgid "`undici`" msgstr "`undici`" @@ -9186,7 +8936,7 @@ msgstr "`undici`" msgid "`perry-ext-ws`" msgstr "`perry-ext-ws`" -#: src/native-libraries/governance.md:115 src/api/reference.md:3681 +#: src/native-libraries/governance.md:115 src/api/reference.md:3701 msgid "`ws`" msgstr "`ws`" @@ -9194,7 +8944,7 @@ msgstr "`ws`" msgid "`perry-ext-zlib`" msgstr "`perry-ext-zlib`" -#: src/native-libraries/governance.md:116 src/api/reference.md:3721 +#: src/native-libraries/governance.md:116 src/api/reference.md:3741 msgid "`zlib`" msgstr "`zlib`" @@ -9430,7 +9180,7 @@ msgstr "\"js_pdf_parse\"" #: src/native-libraries/manifest-v1.md:155 #: src/native-libraries/manifest-v1.md:161 #: src/native-libraries/manifest-v1.md:192 -#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:596 +#: src/native-libraries/manifest-v1.md:386 src/cli/flags.md:618 msgid "\"kind\"" msgstr "\"Bonne\"" @@ -9522,21 +9272,20 @@ msgstr "" #: src/native-libraries/authoring-guide.md:150 msgid "" "Manifest descriptors are native ABI descriptors, not TypeScript surface " -"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and " -"`\"void\"` still work. Use the explicit vocabulary when you need native " -"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, " -"`\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " -"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": " -"\"jsvalue\" }`." +"types. Existing strings such as `\"string\"`, `\"number\"`, `\"i64\"`, and `" +"\"void\"` still work. Use the explicit vocabulary when you need native " +"precision or metadata: `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, `" +"\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " +"\"type\": \"MyThing\" }`, and `{ \"kind\": \"promise\", \"result\": \"jsvalue" +"\" }`." msgstr "" "Les descripteurs de manifeste sont des descripteurs ABI natifs, pas des " "types de surface TypeScript. Les chaînes existantes telles que `\"string\"`, " "`\"number\"`, `\"i64\"` et `\"void\"` fonctionnent toujours. Utilisez le " "vocabulaire explicite lorsque vous avez besoin de précision native ou de " -"métadonnées : `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, " -"`\"buffer_len\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", " -"\"type\": \"MyThing\" }`, et `{ \"kind\": \"promise\", \"result\": " -"\"jsvalue\" }`." +"métadonnées : `\"f32\"`, `\"u32\"`, `\"u64\"`, `\"usize\"`, `\"buffer_len" +"\"`, `{ \"kind\": \"buffer+len\" }`, `{ \"kind\": \"handle\", \"type\": " +"\"MyThing\" }`, et `{ \"kind\": \"promise\", \"result\": \"jsvalue\" }`." #: src/native-libraries/authoring-guide.md:158 msgid "" @@ -10952,7 +10701,7 @@ msgstr "Requis" #: src/tui/widgets.md:55 src/tui/hooks.md:169 src/platforms/tvos.md:50 #: src/platforms/tvos.md:149 src/platforms/watchos.md:137 #: src/platforms/wearos.md:23 src/container/containers.md:133 -#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:96 +#: src/system/media.md:37 src/testing/geisterhand.md:257 src/cli/flags.md:118 #: src/cli/cache-dir.md:42 src/internals/node-api-host.md:588 #: src/internals/node-api-host.md:624 src/internals/node-api-host.md:638 #: src/internals/node-api-host.md:649 @@ -11018,17 +10767,18 @@ msgstr "string" #: src/testing/http2-conformance.md:424 src/testing/http2-conformance.md:425 #: src/testing/http2-conformance.md:426 src/testing/http2-conformance.md:427 #: src/testing/http2-conformance.md:428 src/testing/http2-conformance.md:429 -#: src/testing/ci-tiers.md:39 src/testing/ci-tiers.md:40 -#: src/testing/ci-tiers.md:41 src/testing/ci-tiers.md:42 -#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:45 -#: src/testing/ci-tiers.md:46 src/testing/ci-tiers.md:48 -#: src/testing/ci-tiers.md:49 src/testing/ci-tiers.md:50 -#: src/testing/ci-tiers.md:51 src/testing/ci-tiers.md:52 +#: src/testing/ci-tiers.md:43 src/testing/ci-tiers.md:44 +#: src/testing/ci-tiers.md:45 src/testing/ci-tiers.md:46 +#: src/testing/ci-tiers.md:47 src/testing/ci-tiers.md:49 +#: src/testing/ci-tiers.md:50 src/testing/ci-tiers.md:52 #: src/testing/ci-tiers.md:53 src/testing/ci-tiers.md:54 #: src/testing/ci-tiers.md:55 src/testing/ci-tiers.md:56 #: src/testing/ci-tiers.md:57 src/testing/ci-tiers.md:58 #: src/testing/ci-tiers.md:59 src/testing/ci-tiers.md:60 #: src/testing/ci-tiers.md:61 src/testing/ci-tiers.md:62 +#: src/testing/ci-tiers.md:63 src/testing/ci-tiers.md:64 +#: src/testing/ci-tiers.md:65 src/testing/ci-tiers.md:66 +#: src/testing/ci-tiers.md:67 src/testing/ci-tiers.md:68 #: src/internals/rfc-rooting-by-construction.md:277 #: src/internals/rfc-rooting-by-construction.md:278 #: src/internals/rfc-rooting-by-construction.md:279 @@ -11103,8 +10853,8 @@ msgid "" "Unlike handles and promises, `pod` has no string-only spelling; use object " "form so the field order and scalar ABI types are explicit. `json` is " "parameter-only: it serializes its argument with `JSON.stringify` at the " -"callsite and passes the result in a `string`\\-shaped slot (see \"Param " -"types\")." +"callsite and passes the result in a `string`\\-shaped slot (see \"Param types" +"\")." msgstr "" "`number` est un alias de compatibilité pour `f64`; `js_value` et `boolean` " "sont des alias de compatibilité pour `jsvalue` et `bool`. `handle` nu est le " @@ -11269,7 +11019,7 @@ msgstr "boolean" #: src/testing/http2-conformance.md:289 src/cli/perry-toml.md:178 #: src/cli/perry-toml.md:257 src/cli/perry-toml.md:312 #: src/cli/perry-toml.md:369 src/cli/updates.md:38 -#: src/internals/memory-model.md:19 +#: src/internals/memory-model.md:21 msgid "`false`" msgstr "`false`" @@ -11395,8 +11145,8 @@ msgstr "" "`byte` alias `u8`, `number` alias `f64`, et `buffer_len` est un scalaire de " "longueur d'octet `u32`. `handle_id` est un identifiant entier sans pointeur; " "il n'est pas un `handle` détenu ou emprunté. Les descripteurs dynamiques ou " -"pointerful tels que `jsvalue`, `string`, `json`, `bool`, `ptr`, " -"`buffer+len`, `handle`, `promise` et `void` sont rejetés dans les champs POD." +"pointerful tels que `jsvalue`, `string`, `json`, `bool`, `ptr`, `buffer" +"+len`, `handle`, `promise` et `void` sont rejetés dans les champs POD." #: src/native-libraries/manifest-v1.md:219 msgid "Param types" @@ -11779,7 +11529,7 @@ msgstr "`\"void\"`" msgid "`-> ()`" msgstr "`-> ()`" -#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:17 +#: src/native-libraries/manifest-v1.md:270 src/internals/memory-model.md:19 msgid "`undefined`" msgstr "`undefined`" @@ -11797,8 +11547,8 @@ msgstr "" #: src/native-libraries/manifest-v1.md:276 msgid "" "Note on `\"string\"` vs. `\"i64_str\"`: both produce a string on the " -"TypeScript side, but they differ in how Rust returns the pointer. Use " -"`\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " +"TypeScript side, but they differ in how Rust returns the pointer. Use `" +"\"string\"` / `\"ptr\"` when your `extern \"C\" fn` is declared `-> *const " "u8` (or `*const StringHeader`); use `\"i64_str\"` when it's `-> i64` and the " "value happens to be a `StringHeader` address (closes [\\#222](https://github." "com/PerryTS/perry/issues/222))." @@ -11806,24 +11556,24 @@ msgstr "" "Note sur `\"string\"` vs. `\"i64_str\"` : les deux produisent une chaîne du " "côté TypeScript, mais ils diffèrent dans la façon dont Rust retourne le " "pointeur. Utilisez `\"string\"` / `\"ptr\"` lorsque votre `extern \"C\" fn` " -"est déclarée `-> *const u8` (ou `*const StringHeader`) ; utilisez " -"`\"i64_str\"` lorsqu'elle retourne `-> i64` et que la valeur est une adresse " +"est déclarée `-> *const u8` (ou `*const StringHeader`) ; utilisez `\"i64_str" +"\"` lorsqu'elle retourne `-> i64` et que la valeur est une adresse " "`StringHeader` (ferme [\\#222](https://github.com/PerryTS/perry/issues/222))." #: src/native-libraries/manifest-v1.md:283 msgid "" -"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, " -"`\"json\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " +"`\"void\"` is valid only as a return descriptor. `\"buffer+len\"`, `\"json" +"\"`, and `{ \"kind\": \"pod\", ... }` are valid only as parameter " "descriptors: `\"buffer+len\"` expands one JavaScript argument into two " "native ABI slots, while `pod` lowers one object-shaped argument to a pointer " "to verifier-backed C-layout storage." msgstr "" -"`\"void\"` est valable uniquement en tant que descripteur de retour. " -"`\"buffer+len\"`, `\"json\"` et `{ \"kind\": \"pod\", ... }` ne sont " -"valables que comme descripteurs de paramètres: `\"buffer+len\"` étend un " -"argument JavaScript en deux emplacements ABI natifs, tandis que `pod` " -"abaisse un argument en forme d'objet à un pointeur vers le stockage de mise " -"en page C pris en charge par le vérificateur." +"`\"void\"` est valable uniquement en tant que descripteur de retour. `" +"\"buffer+len\"`, `\"json\"` et `{ \"kind\": \"pod\", ... }` ne sont valables " +"que comme descripteurs de paramètres: `\"buffer+len\"` étend un argument " +"JavaScript en deux emplacements ABI natifs, tandis que `pod` abaisse un " +"argument en forme d'objet à un pointeur vers le stockage de mise en page C " +"pris en charge par le vérificateur." #: src/native-libraries/manifest-v1.md:289 msgid "" @@ -12206,7 +11956,7 @@ msgid "`d3d12`" msgstr "`d3d12`" #: src/native-libraries/manifest-v1.md:350 src/testing/geisterhand.md:780 -#: src/cli/flags.md:121 +#: src/cli/flags.md:143 msgid "`windows`" msgstr "`windows`" @@ -13090,13 +12840,11 @@ msgstr "" #: src/threading/overview.md:3 msgid "" -"Perry gives you real OS threads with a one-line API. No worker setup, no " -"message ports, no structured clone overhead. Just `parallelMap`, " -"`parallelFilter`, and `spawn`." +"Use `parallelMap`, `parallelFilter` and `spawn` to run native work on OS " +"threads." msgstr "" -"Perry vous donne de vrais threads OS avec une API d'une ligne. Pas de " -"configuration de worker, pas de ports de messages, pas de surcharge de " -"clonage structuré. Juste `parallelMap`, `parallelFilter` et `spawn`." +"Utilisez `parallelMap`, `parallelFilter` et `spawn` pour exécuter du travail" +" natif sur des threads OS." #: src/threading/overview.md:5 msgid "" @@ -13158,130 +12906,29 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:32 +#: src/threading/overview.md:32 src/threading/overview.md:300 msgid "" -"This is something **no JavaScript runtime can do**. V8, Bun, and Deno are " -"all locked to one thread per isolate. Perry compiles to native code — there " -"are no isolates, no GIL, no structural limitations. Your code runs on real " -"OS threads with the full power of every CPU core." +"Perry workers run native code on OS threads, each with its own arena and " +"collector. Ordinary captures and results are serialized between heaps; this " +"has a cost proportional to the transferred data. SharedArrayBuffer and " +"supported ownership transfers are explicit exceptions, not permission to " +"share arbitrary heap pointers." msgstr "" -"C'est quelque chose **qu'aucun runtime JavaScript ne peut faire**. V8, Bun " -"et Deno sont tous limités à un seul thread par isolate. Perry compile en " -"code natif — il n'y a pas d'isolates, pas de GIL, pas de limitations " -"structurelles. Votre code s'exécute sur de vrais threads OS avec toute la " -"puissance de chaque cœur de processeur." +"Les workers Perry exécutent du code natif sur des threads OS, chacun avec " +"son arène et son collecteur. Captures et résultats ordinaires sont " +"sérialisés entre tas, à un coût proportionnel aux données. SharedArrayBuffer" +" et les transferts de propriété pris en charge sont des exceptions " +"explicites, pas une autorisation de partager des pointeurs quelconques." #: src/threading/overview.md:34 -msgid "Why This Matters" -msgstr "Pourquoi c'est important" - -#: src/threading/overview.md:36 -msgid "" -"JavaScript's single-threaded model is its biggest performance bottleneck. " -"Here's how runtimes try to work around it:" -msgstr "" -"Le modèle mono-thread de JavaScript est son plus grand goulet d'étranglement " -"en termes de performances. Voici comment les runtimes tentent de contourner " -"ce problème :" - -#: src/threading/overview.md:38 -msgid "Runtime" -msgstr "Runtime" - -#: src/threading/overview.md:38 -msgid "\"Multi-threading\"" -msgstr "« Multi-threading »" - -#: src/threading/overview.md:38 -msgid "Reality" -msgstr "Réalité" - -#: src/threading/overview.md:40 -msgid "**Node.js**" -msgstr "**Node.js**" - -#: src/threading/overview.md:40 src/api/reference.md:3633 -msgid "`worker_threads`" -msgstr "`worker_threads`" - -#: src/threading/overview.md:40 -msgid "" -"Separate V8 isolates. Data copied via structured clone. ~2MB RAM per worker. " -"Complex API." -msgstr "" -"Isolates V8 séparés. Données copiées via structured clone. ~2 Mo de RAM par " -"worker. API complexe." - -#: src/threading/overview.md:41 -msgid "**Deno**" -msgstr "**Deno**" - -#: src/threading/overview.md:41 src/threading/overview.md:42 -#: src/api/reference.md:532 src/api/reference.md:3640 -msgid "`Worker`" -msgstr "`Worker`" - -#: src/threading/overview.md:41 -msgid "Same as Node — isolated heaps, message passing only." -msgstr "Identique à Node — tas isolés, passage de messages uniquement." - -#: src/threading/overview.md:42 -msgid "**Bun**" -msgstr "**Bun**" - -#: src/threading/overview.md:42 -msgid "Same architecture. Faster structured clone, still isolated." -msgstr "Même architecture. Structured clone plus rapide, toujours isolé." - -#: src/threading/overview.md:43 -msgid "**Perry**" -msgstr "**Perry**" - -#: src/threading/overview.md:43 -msgid "`parallelMap` / `spawn`" -msgstr "`parallelMap` / `spawn`" - -#: src/threading/overview.md:43 -msgid "" -"Real OS threads. Lightweight (8MB stack). One-line API. Compile-time safety." -msgstr "" -"Vrais threads OS. Léger (pile de 8 Mo). API en une ligne. Sécurité à la " -"compilation." - -#: src/threading/overview.md:45 -msgid "" -"The fundamental problem: V8 uses a garbage-collected heap that **cannot be " -"shared** between threads. Every \"worker\" is an entirely separate " -"JavaScript engine instance with its own heap, its own GC, and its own copy " -"of your data." -msgstr "" -"Le problème fondamental : V8 utilise un tas géré par un garbage collector " -"qui **ne peut pas être partagé** entre les threads. Chaque « worker » est " -"une instance entièrement séparée du moteur JavaScript avec son propre tas, " -"son propre GC et sa propre copie de vos données." - -#: src/threading/overview.md:47 -msgid "" -"Perry doesn't have this limitation. It compiles TypeScript to native machine " -"code. Values are transferred between threads using zero-cost copies for " -"numbers and efficient serialization for objects — no separate engine " -"instances, no multi-megabyte overhead per thread." -msgstr "" -"Perry n'a pas cette limitation. Il compile TypeScript en code machine natif. " -"Les valeurs sont transférées entre les threads par des copies à coût nul " -"pour les nombres et une sérialisation efficace pour les objets — pas " -"d'instances de moteur séparées, pas de surcharge de plusieurs mégaoctets par " -"thread." - -#: src/threading/overview.md:49 msgid "Three Primitives" msgstr "Trois primitives" -#: src/threading/overview.md:51 +#: src/threading/overview.md:36 msgid "`parallelMap` — Data-Parallel Processing" msgstr "`parallelMap` — Traitement parallèle de données" -#: src/threading/overview.md:53 +#: src/threading/overview.md:38 msgid "" "Split an array across all CPU cores. Each element is processed " "independently. Results are collected in order." @@ -13289,7 +12936,7 @@ msgstr "" "Répartit un tableau sur tous les cœurs du processeur. Chaque élément est " "traité indépendamment. Les résultats sont collectés dans l'ordre." -#: src/threading/overview.md:55 +#: src/threading/overview.md:40 msgid "" "```typescript\n" "async function overviewParallelMap(): Promise {\n" @@ -13323,31 +12970,31 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:71 src/plugins/creating-plugins.md:37 +#: src/threading/overview.md:56 src/plugins/creating-plugins.md:37 msgid "Perry automatically:" msgstr "Perry effectue automatiquement :" -#: src/threading/overview.md:72 +#: src/threading/overview.md:57 msgid "Detects the number of CPU cores" msgstr "La détection du nombre de cœurs du processeur" -#: src/threading/overview.md:73 +#: src/threading/overview.md:58 msgid "Splits the array into chunks (one per core)" msgstr "La division du tableau en blocs (un par cœur)" -#: src/threading/overview.md:74 +#: src/threading/overview.md:59 msgid "Spawns OS threads to process each chunk" msgstr "Le lancement de threads OS pour traiter chaque bloc" -#: src/threading/overview.md:75 +#: src/threading/overview.md:60 msgid "Collects results in the original order" msgstr "La collecte des résultats dans l'ordre d'origine" -#: src/threading/overview.md:76 +#: src/threading/overview.md:61 msgid "Returns a new array" msgstr "Le retour d'un nouveau tableau" -#: src/threading/overview.md:78 +#: src/threading/overview.md:63 msgid "" "For small arrays, Perry skips threading entirely and processes inline — no " "overhead for trivial cases." @@ -13355,18 +13002,18 @@ msgstr "" "Pour les petits tableaux, Perry ignore complètement le threading et traite " "en ligne — aucune surcharge pour les cas triviaux." -#: src/threading/overview.md:80 +#: src/threading/overview.md:65 msgid "`parallelFilter` — Data-Parallel Filtering" msgstr "`parallelFilter` — Filtrage parallèle de données" -#: src/threading/overview.md:82 +#: src/threading/overview.md:67 msgid "" "Filter a large array across all CPU cores. Like `.filter()` but parallel:" msgstr "" "Filtre un grand tableau sur tous les cœurs du processeur. Comme `.filter()` " "mais en parallèle :" -#: src/threading/overview.md:84 +#: src/threading/overview.md:69 msgid "" "```typescript\n" "async function overviewParallelFilter(): Promise {\n" @@ -13406,7 +13053,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:102 +#: src/threading/overview.md:87 msgid "" "Same rules as `parallelMap`: closures cannot capture mutable variables " "(compile-time enforced), and values are deep-copied between threads." @@ -13415,11 +13062,11 @@ msgstr "" "variables mutables (vérifié à la compilation), et les valeurs sont copiées " "en profondeur entre les threads." -#: src/threading/overview.md:104 +#: src/threading/overview.md:89 msgid "`spawn` — Background Threads" msgstr "`spawn` — Threads en arrière-plan" -#: src/threading/overview.md:106 +#: src/threading/overview.md:91 msgid "" "Run any computation in the background and get a Promise back. The main " "thread continues immediately." @@ -13427,7 +13074,7 @@ msgstr "" "Exécute n'importe quel calcul en arrière-plan et retourne une Promise. Le " "thread principal continue immédiatement." -#: src/threading/overview.md:108 +#: src/threading/overview.md:93 msgid "" "```typescript\n" "async function overviewSpawnBg(): Promise {\n" @@ -13469,7 +13116,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:128 +#: src/threading/overview.md:113 msgid "" "`spawn` returns a standard Promise. You can `await` it, pass it to `Promise." "all`, or chain `.then()` — it works exactly like any other async operation." @@ -13478,15 +13125,15 @@ msgstr "" "`Promise.all`, ou chaîner `.then()` — elle fonctionne exactement comme " "n'importe quelle autre opération asynchrone." -#: src/threading/overview.md:130 +#: src/threading/overview.md:115 msgid "Practical Examples" msgstr "Exemples pratiques" -#: src/threading/overview.md:132 +#: src/threading/overview.md:117 msgid "Parallel Image Processing" msgstr "Traitement d'images en parallèle" -#: src/threading/overview.md:134 +#: src/threading/overview.md:119 msgid "" "```typescript\n" "async function overviewImage(): Promise {\n" @@ -13530,11 +13177,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:154 +#: src/threading/overview.md:139 msgid "Parallel Cryptographic Hashing" msgstr "Hachage cryptographique en parallèle" -#: src/threading/overview.md:156 +#: src/threading/overview.md:141 msgid "" "```typescript\n" "async function overviewCrypto(): Promise {\n" @@ -13572,11 +13219,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:174 +#: src/threading/overview.md:159 msgid "Multiple Independent Computations" msgstr "Plusieurs calculs indépendants" -#: src/threading/overview.md:176 +#: src/threading/overview.md:161 msgid "" "```typescript\n" "async function overviewMultiple(): Promise {\n" @@ -13638,11 +13285,11 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:205 +#: src/threading/overview.md:190 msgid "Keeping UI Responsive" msgstr "Maintenir la réactivité de l'interface utilisateur" -#: src/threading/overview.md:207 +#: src/threading/overview.md:192 msgid "" "```typescript\n" "const responsiveButton = Button(\"Start Analysis\", async () => {\n" @@ -13680,11 +13327,11 @@ msgstr "" "const responsiveText = Text(`Status: ${status.value}`)\n" "```" -#: src/threading/overview.md:225 +#: src/threading/overview.md:210 msgid "Captured Variables" msgstr "Variables capturées" -#: src/threading/overview.md:227 +#: src/threading/overview.md:212 msgid "" "Closures can capture outer variables. Captured values are automatically deep-" "copied to each worker thread:" @@ -13693,7 +13340,7 @@ msgstr "" "capturées sont automatiquement copiées en profondeur vers chaque thread " "worker :" -#: src/threading/overview.md:229 +#: src/threading/overview.md:214 msgid "" "```typescript\n" "async function overviewCaptured(): Promise {\n" @@ -13727,7 +13374,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:245 +#: src/threading/overview.md:230 msgid "" "Numbers and booleans are zero-cost copies (just 64-bit values). Strings, " "arrays, and objects are deep-copied automatically." @@ -13736,24 +13383,26 @@ msgstr "" "bits). Les chaînes, tableaux et objets sont copiés en profondeur " "automatiquement." -#: src/threading/overview.md:247 +#: src/threading/overview.md:232 msgid "Safety" msgstr "Sécurité" -#: src/threading/overview.md:249 +#: src/threading/overview.md:234 msgid "" -"Perry enforces thread safety **at compile time**. You don't need to think " -"about race conditions, mutexes, or data corruption." +"Compile-time admission checks restrict worker closures and module-global " +"access. Shared memory still requires synchronization, and native " +"integrations must respect thread ownership." msgstr "" -"Perry garantit la sécurité des threads **à la compilation**. Vous n'avez pas " -"à vous soucier des conditions de course, des mutex ou de la corruption des " -"données." +"Les contrôles d’admission à la compilation restreignent closures de workers " +"et accès aux globales de module. La mémoire partagée exige toujours une " +"synchronisation ; les intégrations natives doivent respecter la propriété " +"des threads." -#: src/threading/overview.md:251 +#: src/threading/overview.md:236 msgid "No Shared Mutable State" msgstr "Absence d'état mutable partagé" -#: src/threading/overview.md:253 +#: src/threading/overview.md:238 msgid "" "Closures passed to `parallelMap` and `spawn` **cannot capture mutable " "variables**. The compiler rejects this:" @@ -13761,7 +13410,7 @@ msgstr "" "Les fermetures passées à `parallelMap` et `spawn` **ne peuvent pas capturer " "de variables mutables**. Le compilateur rejette ceci :" -#: src/threading/overview.md:255 +#: src/threading/overview.md:240 msgid "" "```text\n" "// Reject example — Perry rejects this at compile time:\n" @@ -13789,7 +13438,7 @@ msgstr "" "});\n" "```" -#: src/threading/overview.md:268 +#: src/threading/overview.md:253 msgid "" "This eliminates data races by design. If you need to aggregate results, use " "the return values:" @@ -13797,7 +13446,7 @@ msgstr "" "Cela élimine les courses aux données par conception. Si vous avez besoin " "d'agréger des résultats, utilisez les valeurs de retour :" -#: src/threading/overview.md:270 +#: src/threading/overview.md:255 msgid "" "```typescript\n" "async function overviewReduceInstead(): Promise {\n" @@ -13823,7 +13472,7 @@ msgstr "" "}\n" "```" -#: src/threading/overview.md:282 +#: src/threading/overview.md:267 msgid "" "The one explicit shared-state escape hatch is `SharedArrayBuffer`: a SAB " "captured into a `spawn` / `parallelMap` closure aliases the same physical " @@ -13842,11 +13491,11 @@ msgstr "" "n'importe quelle vue d'arrayés typés sur elle par agent plutôt que de " "capturer la vue directement." -#: src/threading/overview.md:289 +#: src/threading/overview.md:274 msgid "Workers Are Synchronous" msgstr "Les ouvriers sont synchrones" -#: src/threading/overview.md:291 +#: src/threading/overview.md:276 msgid "" "The closure passed to `parallelMap`, `parallelFilter`, or `spawn` cannot be " "`async`, contain `await` (directly or in a nested closure), or call another " @@ -13865,11 +13514,11 @@ msgstr "" "tas. Faites la partie asynchrone sur le fil principal et `await` le résultat " "`spawn` là le modèle standard montré ci-dessus n'est pas affecté." -#: src/threading/overview.md:299 +#: src/threading/overview.md:284 msgid "Module-Scope Objects Stay Home" msgstr "Les objets du module restent chez eux" -#: src/threading/overview.md:301 +#: src/threading/overview.md:286 msgid "" "Worker closures may read module-scope **primitives** (numbers, strings, " "booleans), but not module-scope bindings that hold heap objects — object and " @@ -13898,23 +13547,11 @@ msgstr "" "toujours bon à appeler d'un travailleur). Les modules globaux de " "`SharedArrayBuffer` sont exemptés: leur but est le partage croisé de fils." -#: src/threading/overview.md:313 +#: src/threading/overview.md:298 msgid "Independent Thread Arenas" msgstr "Arènes mémoire indépendantes par thread" -#: src/threading/overview.md:315 -msgid "" -"Each worker thread has its own memory arena. Objects created on one thread " -"can never be accessed from another thread. Values cross thread boundaries " -"only through deep-copy serialization, which Perry handles automatically and " -"invisibly." -msgstr "" -"Chaque thread worker dispose de sa propre arène mémoire. Les objets créés " -"sur un thread ne sont jamais accessibles depuis un autre thread. Les valeurs " -"traversent les frontières des threads uniquement par sérialisation en copie " -"profonde, que Perry gère automatiquement et de manière transparente." - -#: src/threading/overview.md:317 +#: src/threading/overview.md:302 msgid "" "File-system descriptors are also thread-affine. Numeric fds from `fs." "openSync` are just copied numbers in another thread, where the fd registry " @@ -13932,43 +13569,42 @@ msgstr "" "rouvrez les fichiers dans le worker lorsqu'il a besoin d'entrées/sorties " "fichier." -#: src/threading/overview.md:325 +#: src/threading/overview.md:310 msgid "Perry's threading model is built on three pillars:" msgstr "Le modèle de threading de Perry repose sur trois piliers :" -#: src/threading/overview.md:327 -msgid "**1. Native Code, Not Interpreted**" -msgstr "**1. Code natif, non interprété**" +#: src/threading/overview.md:312 +msgid "**1. Native Code**" +msgstr "**1. Code natif**" -#: src/threading/overview.md:329 +#: src/threading/overview.md:314 msgid "" -"Perry compiles TypeScript to native machine code via LLVM. There's no " -"interpreter, no VM, no isolate. A function pointer is just a function " -"pointer — it's valid on any thread." +"Perry compiles TypeScript to native machine code via LLVM. Native worker " +"execution still requires the ownership and transfer rules described above." msgstr "" -"Perry compile TypeScript en code machine natif via LLVM. Il n'y a pas " -"d'interpréteur, pas de VM, pas d'isolat. Un pointeur de fonction est " -"simplement un pointeur de fonction — il est valide sur n'importe quel thread." +"Perry compile TypeScript en code machine natif via LLVM. Les workers natifs " +"doivent toujours respecter les règles de propriété et de transfert décrites " +"plus haut." -#: src/threading/overview.md:331 +#: src/threading/overview.md:316 msgid "**2. Thread-Local Memory**" msgstr "**2. Mémoire locale au thread**" -#: src/threading/overview.md:333 +#: src/threading/overview.md:318 msgid "" -"Each thread gets its own memory arena (bump allocator) and garbage " -"collector. No synchronization overhead during computation. When a thread " -"finishes, its arena is freed automatically." +"Each worker has its own arena and collector. This avoids a shared JavaScript" +" heap; explicit shared memory and runtime coordination still require " +"synchronization." msgstr "" -"Chaque thread dispose de sa propre arène mémoire (allocateur par incrément) " -"et d'un ramasse-miettes. Aucune surcharge de synchronisation pendant le " -"calcul. Lorsqu'un thread se termine, son arène est libérée automatiquement." +"Chaque worker possède son arène et son collecteur. Cela évite un tas " +"JavaScript commun ; la mémoire explicitement partagée et la coordination du " +"runtime exigent toujours une synchronisation." -#: src/threading/overview.md:335 +#: src/threading/overview.md:320 msgid "**3. Serialized Transfer**" msgstr "**3. Transfert sérialisé**" -#: src/threading/overview.md:337 +#: src/threading/overview.md:322 msgid "" "Values crossing thread boundaries are serialized to a thread-safe " "intermediate format and deserialized on the target thread. The cost depends " @@ -13978,47 +13614,47 @@ msgstr "" "un format intermédiaire thread-safe, puis désérialisées sur le thread cible. " "Le coût dépend du type de valeur :" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Value Type" msgstr "Type de valeur" -#: src/threading/overview.md:339 +#: src/threading/overview.md:324 msgid "Transfer Cost" msgstr "Coût de transfert" -#: src/threading/overview.md:341 +#: src/threading/overview.md:326 msgid "Numbers, booleans, null, undefined" msgstr "Nombres, booléens, null, undefined" -#: src/threading/overview.md:341 src/threading/parallel-map.md:55 +#: src/threading/overview.md:326 src/threading/parallel-map.md:55 msgid "Zero-cost (64-bit copy)" msgstr "Sans surcoût (copie 64 bits)" -#: src/threading/overview.md:342 +#: src/threading/overview.md:327 msgid "O(n) byte copy" msgstr "Copie O(n) en octets" -#: src/threading/overview.md:343 +#: src/threading/overview.md:328 msgid "O(n) deep copy of elements" msgstr "Copie profonde O(n) des éléments" -#: src/threading/overview.md:344 +#: src/threading/overview.md:329 msgid "O(n) deep copy of fields" msgstr "Copie profonde O(n) des champs" -#: src/threading/overview.md:345 +#: src/threading/overview.md:330 msgid "Pointer + captured values" msgstr "Pointeur + valeurs capturées" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "`fs` numeric fds / `FileHandle`" msgstr "`fs` fd numériques / `FileHandle`" -#: src/threading/overview.md:346 +#: src/threading/overview.md:331 msgid "Thread-affine; reopen by path" msgstr "Lié au thread ; rouvrir par chemin" -#: src/threading/overview.md:348 +#: src/threading/overview.md:333 msgid "" "For numeric workloads — the most common parallelizable tasks — the threading " "overhead is negligible." @@ -14026,21 +13662,89 @@ msgstr "" "Pour les charges de travail numériques — les tâches parallélisables les plus " "courantes — la surcharge liée au threading est négligeable." -#: src/threading/overview.md:352 +#: src/threading/overview.md:335 +#, fuzzy +msgid "Node.js Worker Constructor Compatibility" +msgstr "Node.js Compatibility" + +#: src/threading/overview.md:337 +msgid "" +"For `node:worker_threads`, a truthy `Worker` `name` must be a string; " +"invalid names throw `TypeError` with `ERR_INVALID_ARG_TYPE` before " +"transferring data. Falsy names use the default. `transferList` is validated " +"even when `workerData` is undefined, and listed ArrayBuffers are detached " +"after a successful clone whether or not the data references them." +msgstr "" + +#: src/threading/overview.md:343 +msgid "" +"`setEnvironmentData` retains the caller's value until it is replaced or " +"deleted by setting `undefined`. A new worker receives a creation-time clone " +"of that map alongside `workerData`; shared references and cycles are " +"preserved within this snapshot, while later parent changes remain local. " +"Captured namespace methods and CommonJS imports use the same storage as " +"named imports. If cloning either data source fails, construction throws " +"before transferring any buffers. Clone and transfer marks are private, " +"thread-local identity metadata, not object properties. Primitive marks are " +"ignored. Clone marks reject ordinary objects and Errors in both global " +"`structuredClone` and worker messages; builtin clone kinds such as arrays, " +"Maps, Sets and buffers retain their own serialization behavior. Marked " +"transfer buffers are rejected without detachment. The marker table does not " +"keep its targets alive. Inside a Node-compatible Worker, process-wide " +"mutations such as `abort`, `chdir`, credential setters, IPC send/disconnect " +"and setting `umask` are rejected before OS effects. IPC members remain " +"absent unless enabled by the Worker startup environment. Disabled process " +"callables expose `disabled === true`; reading `umask` remains allowed. " +"Worker title writes remain local to the existing title value, and mutable " +"CommonJS/default reads observe the updated title. Named ESM title imports " +"remain a known parity limitation. Worker eval source must be a primitive " +"string. A URL with truthy `eval` throws before reading workerData or " +"detaching buffers; ordinary file URLs use the same registered entry table as " +"dynamically selected files. Literal string eval remains compiled ahead of " +"time; dynamically generated source is still unsupported. Worker `argv` " +"values are converted in constructor order and installed in the child's " +"existing process-argv authority, after execPath and the Worker entry name. " +"MessagePort and BroadcastChannel property reads return stored values without " +"invoking handlers; method calls retain their receiver through ordinary " +"property lookup. Message methods accept transfer iterables directly or " +"through `options.transfer`. The complete sequence is consumed before " +"transfer entries are validated; iterator errors retain their identity, and " +"rejection leaves ArrayBuffers attached. BroadcastChannel ignores its extra " +"argument. Worker constructor transfer admission remains separate; " +"MessagePort endpoint movement and direct-message routing/timeout parity are " +"not provided by this normalization. Main-thread `postMessageToThread` " +"rejects missing or stopped destinations before cloning the payload or " +"detaching transfer buffers. Worker-origin messages retain Node's forwarding " +"order: cloning precedes destination rejection. Admission does not guarantee " +"rollback if a destination exits concurrently after it was admitted. Core-" +"module messaging constructor exports retain the original thread-realm " +"MessageChannel, MessagePort and BroadcastChannel objects even after global " +"reassignment. Explicit CommonJS export overrides and named-export " +"synchronization remain separate; vm/eval global population does not replace " +"these thread-core values. MessagePort core methods (`postMessage`, `start`, " +"`close`, `ref`, `unref`, `hasRef`) operate on their actual receiver, " +"including calls borrowed from another port or its prototype. A private ID " +"supplies the brand without observable getters or proxy traps; closing a port " +"preserves its brand without retaining a closed runtime port in the delivery " +"table. Listener methods and realm movement remain separate parity work. " +"Other worker API parity gaps remain." +msgstr "" + +#: src/threading/overview.md:394 msgid "" "[parallelMap Reference](parallel-map.md) — detailed API and performance tips" msgstr "" "[Référence parallelMap](parallel-map.md) — API détaillée et conseils de " "performance" -#: src/threading/overview.md:353 +#: src/threading/overview.md:395 msgid "" "[parallelFilter Reference](parallel-filter.md) — parallel array filtering" msgstr "" "[Référence parallelFilter](parallel-filter.md) — filtrage parallèle de " "tableaux" -#: src/threading/overview.md:354 +#: src/threading/overview.md:396 msgid "" "[spawn Reference](spawn.md) — background threads and Promise integration" msgstr "" @@ -14793,8 +14497,8 @@ msgid "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" msgstr "" @@ -14818,8 +14522,8 @@ msgstr "" " score: u.score * 2,\n" " }))\n" "\n" -" console.log(`parallel-filter-combined active=${active.length} " -"profiles=${profiles.length}`)\n" +" console.log(`parallel-filter-combined active=${active.length} profiles=" +"${profiles.length}`)\n" "}\n" "```" @@ -15333,8 +15037,8 @@ msgid "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" msgstr "" @@ -15351,8 +15055,8 @@ msgstr "" " return { total, count: rawData.items.length }\n" " })\n" "\n" -" console.log(`spawn-api-then-process total=${processed.total} " -"count=${processed.count}`)\n" +" console.log(`spawn-api-then-process total=${processed.total} count=" +"${processed.count}`)\n" "}\n" "```" @@ -15592,7 +15296,7 @@ msgstr "" msgid "Property" msgstr "Propriété" -#: src/ui/overview.md:79 src/api/reference.md:2799 +#: src/ui/overview.md:79 src/api/reference.md:2804 msgid "`title`" msgstr "`title`" @@ -15649,8 +15353,8 @@ msgstr "`windowState`" msgid "" "Initial state: `\"normal\"`, `\"maximized\"`, `\"fullscreen\"` (optional)" msgstr "" -"L'état initial est le suivant: `\"normal\"`, `\"maximized\"`, " -"`\"fullscreen\"` (facultatif)" +"L'état initial est le suivant: `\"normal\"`, `\"maximized\"`, `\"fullscreen" +"\"` (facultatif)" #: src/ui/overview.md:85 msgid "`frameAutosaveName`" @@ -16126,7 +15830,7 @@ msgstr "NSMenu" #: src/cli/perry-toml.md:389 src/cli/perry-toml.md:418 #: src/cli/app-updates.md:56 src/cli/app-updates.md:57 #: src/cli/app-updates.md:58 src/cli/app-updates.md:60 -#: src/internals/memory-model.md:325 src/internals/memory-model.md:326 +#: src/internals/memory-model.md:309 src/internals/memory-model.md:310 #: src/contributing/building.md:55 msgid "—" msgstr "—" @@ -16725,8 +16429,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -16751,8 +16455,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/" -"ui\"\n" +"import { App, VStack, Text, Picker, State, pickerAddItem } from \"perry/ui" +"\"\n" "\n" "const selected = State(0)\n" "const picker = Picker((index: number) => selected.set(index))\n" @@ -18169,8 +17873,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18205,8 +17909,8 @@ msgstr "" "// platforms: macos, linux, windows\n" "// targets: ios-simulator, web, wasm\n" "\n" -"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/" -"ui\"\n" +"import { App, VStack, HStack, Text, Button, Spacer, Divider } from \"perry/ui" +"\"\n" "\n" "App({\n" " title: \"Layout Example\",\n" @@ -18363,7 +18067,7 @@ msgstr "Nom" #: src/container/overview.md:112 src/container/containers.md:46 #: src/container/compose.md:249 src/container/networking.md:25 #: src/container/volumes.md:37 src/container/security.md:14 -#: src/internals/memory-model.md:168 src/internals/memory-model.md:185 +#: src/internals/memory-model.md:153 src/internals/memory-model.md:170 #: src/internals/weakmap-owned-index-validation.md:78 #: src/internals/weakmap-owned-index-validation.md:100 msgid "Effect" @@ -18527,7 +18231,7 @@ msgstr "Tous les enfants ont la même taille" #: src/ui/styling-matrix.md:97 src/tui/widgets.md:60 #: src/platforms/windows-7.md:26 src/container/overview.md:72 #: src/container/determinism.md:60 src/testing/geisterhand.md:88 -#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:190 +#: src/testing/geisterhand.md:102 src/testing/ci-tiers.md:197 #: src/cli/perry-toml.md:430 src/internals/gc-rooting-invariant.md:358 #: src/internals/gc-rooting-invariant.md:359 #: src/internals/rfc-rooting-by-construction.md:355 @@ -18617,8 +18321,8 @@ msgid "" "// platforms: macos, linux, windows\n" "// targets: web, wasm\n" "\n" -"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/" -"ui\";\n" +"import { App, VStack, Text, widgetSetMaxWidth, setPadding } from \"perry/ui" +"\";\n" "\n" "const column = VStack(12, [Text(\"Responsive content\")]);\n" "setPadding(column, 32);\n" @@ -23615,17 +23319,15 @@ msgstr "" #: src/ui/multi-window.md:118 msgid "" -"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**macOS materials:** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `\"menu" +"\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground\"`, `" +"\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground\"`, `" +"\"underWindowBackground\"`, `\"underPageBackground\"`" msgstr "" -"**Matériaux macOS :** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, " -"`\"menu\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, " -"`\"windowBackground\"`, `\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, " -"`\"contentBackground\"`, `\"underWindowBackground\"`, " -"`\"underPageBackground\"`" +"**Matériaux macOS :** `\"sidebar\"`, `\"titlebar\"`, `\"selection\"`, `\"menu" +"\"`, `\"popover\"`, `\"headerView\"`, `\"sheet\"`, `\"windowBackground\"`, `" +"\"hudWindow\"`, `\"fullScreenUI\"`, `\"tooltip\"`, `\"contentBackground\"`, `" +"\"underWindowBackground\"`, `\"underPageBackground\"`" #: src/ui/multi-window.md:125 msgid "`NSVisualEffectView` with the specified material" @@ -24506,9 +24208,9 @@ msgstr "" "`WebKitGTK 6.0` sur Linux, `android.webkit.WebView` sur Android, et un " "`