Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/6917-size-optimized-binaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
**Binary size — small hello-world binaries (5.9 MB → 2.4 MB class):** independent levers, all default-off or behavior-preserving.

- `PERRY_SIZE_OPT=z|s` rebuilds the auto-optimized runtime/stdlib archives at `-C opt-level=z`/`s` (cache-keyed alongside the existing feature/panic keys), and `PERRY_SIZE_LTO=fat` layers whole-archive fat LTO on top via `CARGO_PROFILE_RELEASE_LTO`. Explicitly opt-in: trades runtime speed for size.
- The ~490 `#[used]` keep-alive anchor statics in perry-runtime now sit behind a new default-on `keepalive-anchors` feature. They exist for the whole-program bitcode-LTO link (`PERRY_LLVM_BITCODE_LINK=1`), which re-enables them; the classic link keeps every reachable symbol via real undefined references, so the normal auto-optimize rebuild omits them. Measured: one anchored chain (`KEEP_JS_MODULE_DYNAMIC_IMPORT_DEFERRED` → dynamic-import fallback → `js_nm_install_all` → every dispatch bucket) was pinning the entire fs/child_process/node_vm/node_submodules surface into a `console.log` hello world. Differential probe (dynamic `import()`, `createRequire`, `getBuiltinModule`, fs sync/promises, streams, `child_process`) stays byte-identical to Node.
- The mimalloc global allocator is now the default-on `alloc-mimalloc` feature. Every auto-optimize rebuild (including size mode) force-adds it, so nothing changes at runtime; the gate exists for a future system-allocator option, which today is blocked on `value/addr_class.rs`'s heap-address window ([2 TB, 128 TB)) assuming mimalloc's mapping range — a system-malloc build misclassifies every allocation as a non-pointer (silent output loss / spurious TypeErrors).

Also adds `PERRY_EXTRA_LINK_ARGS` (verbatim extra link args — diagnostic escape hatch, e.g. `-Wl,-why_live,_sym`).
27 changes: 24 additions & 3 deletions crates/perry-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,28 @@ crate-type = ["rlib"]
# actually needs (see optimized_libs.rs), so the heavy subsystems below
# (regex engine, Temporal, URL/IDNA, normalize, segmenter) are *opt-in per
# app* and absent from binaries that never use them.
default = ["full", "regex-engine", "temporal", "url-engine", "string-normalize", "intl-segmenter", "intl-locale", "intl-datetime", "diagnostics", "mod-dgram", "mod-http2-constants", "dyn-eval"]
default = ["full", "regex-engine", "temporal", "url-engine", "string-normalize", "intl-segmenter", "intl-locale", "intl-datetime", "diagnostics", "mod-dgram", "mod-http2-constants", "dyn-eval", "keepalive-anchors", "alloc-mimalloc"]
# Compile the ~490 `#[used]` keep-alive anchor statics (`KEEP_*`, `keep!`,
# `K*`/`G*` fn-pointer statics) that pin codegen-facing `#[no_mangle]` entry
# points as dead-strip roots. They exist for the whole-program bitcode-LTO
# link (`PERRY_LLVM_BITCODE_LINK=1`), where the merged module could otherwise
# internalize + drop symbols only referenced from the user's separate `.o`.
# The CLASSIC link path never needs them: every reachable runtime symbol is
# kept by a real undefined reference from the program's objects, and the
# anchors only defeat `-dead_strip` (measured: a hello-world binary carries
# the entire fs/child_process/vm/node_submodules surface through ONE anchored
# chain — KEEP_JS_MODULE_DYNAMIC_IMPORT_DEFERRED → dynamic-import fallback →
# js_nm_install_all → every dispatch bucket). Kept in `default` so the
# prebuilt/full archives and `cargo test` are unchanged; the auto-optimize
# rebuild omits it unless the bitcode link was requested.
keepalive-anchors = []
# Route every Rust heap allocation through mimalloc (#62) — ~3-5× faster
# per-alloc than macOS `malloc` on hot paths, at ~140 KB of binary. In
# `default` (and force-added by the auto-optimize rebuild) so behavior is
# unchanged everywhere; a size-optimized rebuild (`PERRY_SIZE_OPT=z|s`)
# leaves it off and falls back to the system allocator. The #6882 VM-tag
# retag rides the same gate (it only exists to label mimalloc's mappings).
alloc-mimalloc = ["dep:mimalloc", "dep:libmimalloc-sys"]
# Per-module Node-API gate (binary-size): compiles `node:dgram`'s UDP-socket
# implementation (`crate::dgram` + `crate::dgram_reactor`, ~2.2k LOC, incl. the
# `js_dgram_*` externs codegen emits direct calls to) + its dispatch arm only
Expand Down Expand Up @@ -243,7 +264,7 @@ socket2 = { version = "0.6", features = ["all"] }
# a corruption suspect, so lib.rs falls back to std::alloc::System there and we
# avoid compiling mimalloc's C on that target entirely.
[target.'cfg(target_pointer_width = "64")'.dependencies]
mimalloc = { version = "0.1", default-features = false }
mimalloc = { version = "0.1", default-features = false, optional = true }

# #6882: mimalloc tags its OS mappings with VM tag 100, which macOS tooling
# (vmmap, Instruments' VM Tracker, `footprint`) decodes as `IOAccelerator` —
Expand All @@ -252,7 +273,7 @@ mimalloc = { version = "0.1", default-features = false }
# which lives behind libmimalloc-sys's `extended` API. Apple-only: the tag is
# only consulted by Apple kernels, and mimalloc itself is 64-bit-only (above).
[target.'cfg(all(target_pointer_width = "64", target_vendor = "apple"))'.dependencies]
libmimalloc-sys = { version = "0.1", features = ["extended"] }
libmimalloc-sys = { version = "0.1", features = ["extended"], optional = true }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
Expand Down
6 changes: 3 additions & 3 deletions crates/perry-runtime/src/array/from_concat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ pub extern "C" fn js_array_from_value(boxed: f64) -> *mut ArrayHeader {
js_array_clone(ptr_bits as *const ArrayHeader)
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FROM_VALUE: extern "C" fn(f64) -> *mut ArrayHeader = js_array_from_value;

/// `Array.from(source, mapFn, thisArg)` — the mapped form. Throws for nullish
Expand All @@ -165,7 +165,7 @@ pub extern "C" fn js_array_from_mapped(
as *mut ArrayHeader
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FROM_MAPPED: extern "C" fn(f64, f64, f64) -> *mut ArrayHeader =
js_array_from_mapped;

Expand Down Expand Up @@ -282,7 +282,7 @@ pub extern "C" fn js_array_concat_variadic(
result
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_CONCAT_VARIADIC: extern "C" fn(
*const ArrayHeader,
*const f64,
Expand Down
16 changes: 8 additions & 8 deletions crates/perry-runtime/src/array/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ fn clamp_index(v: f64, len: i64) -> i64 {
// Keep the generic entry points anchored against dead-strip in the default
// (codegen-only reference) compile path (see #3320 — `#[no_mangle]` alone is
// not enough once the bitcode is re-linked).
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_CB: [extern "C" fn(f64, f64, f64) -> f64; 9] = [
js_arraylike_forEach,
js_arraylike_map,
Expand All @@ -1132,20 +1132,20 @@ static KEEP_ARRAYLIKE_CB: [extern "C" fn(f64, f64, f64) -> f64; 9] = [
js_arraylike_findLast,
js_arraylike_findLastIndex,
];
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_REDUCE: [extern "C" fn(f64, f64, i32, f64) -> f64; 2] =
[js_arraylike_reduce, js_arraylike_reduceRight];
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_SEARCH: [extern "C" fn(f64, f64, f64, i32) -> f64; 3] = [
js_arraylike_indexOf,
js_arraylike_lastIndexOf,
js_arraylike_includes,
];
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_AT: extern "C" fn(f64, f64) -> f64 = js_arraylike_at;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_JOIN: extern "C" fn(f64, f64) -> f64 = js_arraylike_join;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_SLICE: extern "C" fn(f64, f64, i32, f64, i32) -> f64 = js_arraylike_slice;

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1636,9 +1636,9 @@ pub extern "C" fn js_arraylike_splice(recv: f64, args_ptr: *const f64, count: i3
object_splice(o, args_ptr, count)
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_SORT: extern "C" fn(f64, f64) -> f64 = js_arraylike_sort;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAYLIKE_VARIADIC: [extern "C" fn(f64, *const f64, i32) -> f64; 2] =
[js_arraylike_concat, js_arraylike_splice];

Expand Down
14 changes: 7 additions & 7 deletions crates/perry-runtime/src/array/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ pub extern "C" fn js_tagged_template_get_or_init(
cooked_handle.get_raw_mut_ptr::<ArrayHeader>()
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_TAGGED_TEMPLATE_GET_OR_INIT: extern "C" fn(
u64,
*mut ArrayHeader,
Expand Down Expand Up @@ -1484,22 +1484,22 @@ pub extern "C" fn js_array_is_numeric_f64_layout(arr: *const ArrayHeader) -> i32

// These raw numeric-array helpers are called from generated code, so release/LTO
// builds may otherwise internalize and strip the `#[no_mangle]` exports.
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_NUMERIC_VALUE_TO_RAW_F64: extern "C" fn(f64) -> f64 =
js_array_numeric_value_to_raw_f64;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_MARK_NUMERIC_F64_LAYOUT: extern "C" fn(*mut ArrayHeader) -> i32 =
js_array_mark_numeric_f64_layout;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_CLEAR_NUMERIC_LAYOUT: extern "C" fn(*mut ArrayHeader) =
js_array_clear_numeric_layout;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_NOTE_NUMERIC_WRITE: extern "C" fn(*mut ArrayHeader, u64) =
js_array_note_numeric_write;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_IS_NUMERIC_F64_LAYOUT: extern "C" fn(*const ArrayHeader) -> i32 =
js_array_is_numeric_f64_layout;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_REFRESH_LOCAL_HEAD: extern "C" fn(f64) -> f64 = js_array_refresh_local_head;

/// Calculate the byte size for an array with N elements capacity
Expand Down
18 changes: 9 additions & 9 deletions crates/perry-runtime/src/array/indexing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ pub(crate) unsafe fn keys_array_len_capped_to_capacity(arr: *const ArrayHeader)
/// native-region wrappers (`__perry_wrap_*`) and elsewhere, so it must be a
/// `#[no_mangle]` C export AND survive dead-stripping even when no Rust caller
/// keeps it referenced — mirroring the neighbouring `js_array_push`.
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_LENGTH: extern "C" fn(*const ArrayHeader) -> u32 = js_array_length;

#[no_mangle]
Expand Down Expand Up @@ -852,10 +852,10 @@ pub extern "C" fn js_array_numeric_set_f64_unboxed(

// These raw numeric-array helpers are called from generated code, so release/LTO
// builds may otherwise internalize and strip the `#[no_mangle]` exports.
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_NUMERIC_GET_F64_UNBOXED: extern "C" fn(*mut ArrayHeader, u32) -> f64 =
js_array_numeric_get_f64_unboxed;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_NUMERIC_SET_F64_UNBOXED: extern "C" fn(*mut ArrayHeader, u32, f64) -> i32 =
js_array_numeric_set_f64_unboxed;

Expand Down Expand Up @@ -1450,27 +1450,27 @@ pub extern "C" fn js_array_numeric_range_add_len(receiver: f64, start: f64, delt
array_numeric_range_add_impl(receiver, start, None, delta)
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FILL_F64_CONST_EXTEND: extern "C" fn(
*mut ArrayHeader,
u32,
f64,
) -> *mut ArrayHeader = js_array_fill_f64_const_extend;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FILL_F64_IOTA_EXTEND: extern "C" fn(*mut ArrayHeader, u32) -> *mut ArrayHeader =
js_array_fill_f64_iota_extend;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FILL_F64_CONST_LEN_EXTEND: extern "C" fn(
*mut ArrayHeader,
f64,
) -> *mut ArrayHeader = js_array_fill_f64_const_len_extend;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_FILL_F64_IOTA_LEN_EXTEND: extern "C" fn(*mut ArrayHeader) -> *mut ArrayHeader =
js_array_fill_f64_iota_len_extend;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_NUMERIC_RANGE_ADD: extern "C" fn(f64, f64, f64, f64) -> i64 =
js_array_numeric_range_add;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_NUMERIC_RANGE_ADD_LEN: extern "C" fn(f64, f64, f64) -> i64 =
js_array_numeric_range_add_len;

Expand Down
15 changes: 8 additions & 7 deletions crates/perry-runtime/src/array/iter_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,10 +1040,11 @@ pub extern "C" fn js_array_join_value(
// Symbol retention: codegen lowers `arr.join(sep)` to a call to
// `js_array_join_value`, but its only in-crate caller sits behind a dispatch
// path the auto-optimize whole-program-bitcode build can prove unreachable and
// dead-strip — which broke the default `perry file.ts -o out` link with
// `undefined _js_array_join_value`. The `#[used]` static pins the symbol so it
// survives every link mode. Same pattern as `node_stream_keepalive.rs`.
#[used]
// dead-strip — which broke that link with `undefined _js_array_join_value`.
// The feature-gated `#[used]` static pins the symbol for the bitcode-LTO
// link (`keepalive-anchors`); the classic link keeps it via the program's
// own undefined reference. Same pattern as `node_stream_keepalive.rs`.
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_JOIN_VALUE: extern "C" fn(
*const ArrayHeader,
f64,
Expand Down Expand Up @@ -1108,7 +1109,7 @@ pub extern "C" fn js_array_to_locale_string(
crate::string::js_string_from_bytes(out.as_ptr(), out.len() as u32)
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_ARRAY_TO_LOCALE_STRING: extern "C" fn(
*const ArrayHeader,
f64,
Expand Down Expand Up @@ -1231,7 +1232,7 @@ pub extern "C" fn js_validate_array_callback(cb_boxed: f64) -> i64 {
throw_not_a_function(render_callback_typeof(cb_boxed));
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_VALIDATE_ARRAY_CALLBACK: extern "C" fn(f64) -> i64 = js_validate_array_callback;

/// Validate a `map` callback (#4091). Identical to
Expand All @@ -1252,6 +1253,6 @@ pub extern "C" fn js_validate_array_map_callback(arr: i64, cb_boxed: f64) -> i64
throw_not_a_function(rendered);
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_VALIDATE_ARRAY_MAP_CALLBACK: extern "C" fn(i64, f64) -> i64 =
js_validate_array_map_callback;
4 changes: 2 additions & 2 deletions crates/perry-runtime/src/array/push_pop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ pub extern "C" fn js_array_numeric_push_f64_unboxed(

// This raw numeric-array helper is called from generated code, so release/LTO
// builds may otherwise internalize and strip the `#[no_mangle]` export.
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_ARRAY_NUMERIC_PUSH_F64_UNBOXED: extern "C" fn(
*mut ArrayHeader,
f64,
Expand Down Expand Up @@ -820,6 +820,6 @@ pub extern "C" fn js_array_unshift_variadic(
}
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_UNSHIFT_VARIADIC: extern "C" fn(*mut ArrayHeader, *const f64, u32) -> *mut ArrayHeader =
js_array_unshift_variadic;
2 changes: 1 addition & 1 deletion crates/perry-runtime/src/array/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ pub extern "C" fn js_validate_array_comparator(cmp_boxed: f64) -> i64 {
throw_invalid_comparator(cmp_boxed);
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_VALIDATE_ARRAY_COMPARATOR: extern "C" fn(f64) -> i64 = js_validate_array_comparator;

#[cold]
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-runtime/src/bigint/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub extern "C" fn js_bigint_from_i128_parts(lo: u64, hi: i64) -> *mut BigIntHead
bigint_alloc_with_limbs(limbs)
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BIGINT_FROM_I128_PARTS: extern "C" fn(u64, i64) -> *mut BigIntHeader =
js_bigint_from_i128_parts;

Expand Down
28 changes: 14 additions & 14 deletions crates/perry-runtime/src/box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ pub extern "C" fn js_tdz_suppress_end() {

/// Keepalive anchors for the auto-optimize whole-program build (generated-code-
/// only callees — without these the symbols dead-strip and the app link fails).
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_TDZ_SUPPRESS_BEGIN: extern "C" fn() = js_tdz_suppress_begin;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_TDZ_SUPPRESS_END: extern "C" fn() = js_tdz_suppress_end;

/// Compatibility wrapper for legacy f64-lowered boxed locals.
Expand Down Expand Up @@ -483,29 +483,29 @@ fn is_registered_bool_box_ptr(ptr: *mut BoolBox) -> bool {
BOOL_BOX_REGISTRY.with(|r| r.borrow().contains(&(ptr as usize)))
}

#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_ALLOC_BITS: extern "C" fn(i64) -> *mut Box = js_box_alloc_bits;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_GET_BITS: extern "C" fn(*mut Box) -> i64 = js_box_get_bits;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_SET_BITS: extern "C" fn(*mut Box, i64) = js_box_set_bits;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_ALLOC: extern "C" fn(f64) -> *mut Box = js_box_alloc;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_GET: extern "C" fn(*mut Box) -> f64 = js_box_get;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOX_SET: extern "C" fn(*mut Box, f64) = js_box_set;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_I32_BOX_ALLOC: extern "C" fn(i32) -> *mut I32Box = js_i32_box_alloc;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_I32_BOX_GET: extern "C" fn(*mut I32Box) -> i32 = js_i32_box_get;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_I32_BOX_SET: extern "C" fn(*mut I32Box, i32) = js_i32_box_set;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOOL_BOX_ALLOC: extern "C" fn(i32) -> *mut BoolBox = js_bool_box_alloc;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOOL_BOX_GET: extern "C" fn(*mut BoolBox) -> i32 = js_bool_box_get;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BOOL_BOX_SET: extern "C" fn(*mut BoolBox, i32) = js_bool_box_set;

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions crates/perry-runtime/src/buffer/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ pub extern "C" fn js_buffer_index_get_value(buf_ptr: *const BufferHeader, index:
// #6088: force-keep the JS-value buffer index getter under LTO /
// auto-optimize. It has zero internal Rust callers — codegen emits the only
// call (in `perry-codegen/src/expr/index_get.rs`), so a whole-program bitcode
// link is otherwise free to internalize and dead-strip it. The `#[used]`
// link is otherwise free to internalize and dead-strip it. The `#[cfg_attr(feature = "keepalive-anchors", used)]`
// anchor pins it (mirrors `KEEP_JS_TYPED_ARRAY_INDEX_GET_DYNAMIC`).
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_BUFFER_INDEX_GET_VALUE: extern "C" fn(*const BufferHeader, i32) -> f64 =
js_buffer_index_get_value;

Expand Down
4 changes: 2 additions & 2 deletions crates/perry-runtime/src/buffer/dataview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,10 @@ fn data_view_method_name<'a>(buf: &'a mut [u8; 16], prefix: &str, kind: DataView
}

// Called from generated code — keep the exports alive under release/LTO.
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_DATA_VIEW_GET_DIRECT: extern "C" fn(f64, f64, f64, i32, i32) -> f64 =
js_data_view_get_direct;
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_JS_DATA_VIEW_SET_DIRECT: extern "C" fn(f64, f64, f64, f64, i32, i32) -> f64 =
js_data_view_set_direct;

Expand Down
4 changes: 2 additions & 2 deletions crates/perry-runtime/src/buffer/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ pub extern "C" fn js_value_to_string_with_encoding_or_radix(

/// Keepalive anchor: `js_value_to_string_with_encoding_or_radix` is emitted
/// only from generated `.o`, so the auto-optimize whole-program LLVM rebuild
/// would internalize + dead-strip it without a `#[used]` reference (see
/// would internalize + dead-strip it without a `#[cfg_attr(feature = "keepalive-anchors", used)]` reference (see
/// project_auto_optimize_keepalive_3320).
#[used]
#[cfg_attr(feature = "keepalive-anchors", used)]
static KEEP_VALUE_TO_STRING_ENCODING_OR_RADIX: extern "C" fn(f64, i32, f64) -> *mut StringHeader =
js_value_to_string_with_encoding_or_radix;

Expand Down
Loading
Loading