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
4 changes: 4 additions & 0 deletions changelog.d/9233-release-ci-blockers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Budgeted garbage-collection cycles now build the lazy native stack-map index
before their first root scan, preventing traced workloads from aborting. The
`getDeviceModel` string-ABI regression also remains covered without requiring
a platform UI archive in runtime-only integration shards.
20 changes: 20 additions & 0 deletions crates/perry-dispatch/src/system_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,23 @@ pub static PERRY_SYSTEM_TABLE: &[MethodRow] = &[
ret: ReturnKind::Str,
},
];

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn device_identity_apis_return_boxed_strings() {
for method in ["getDeviceModel", "getOSVersion"] {
let row = PERRY_SYSTEM_TABLE
.iter()
.find(|row| row.method == method)
.unwrap_or_else(|| panic!("missing perry/system dispatch row for {method}"));
assert_eq!(
row.ret,
ReturnKind::Str,
"{method} returns a StringHeader pointer and must be string-boxed"
);
}
}
}
8 changes: 8 additions & 0 deletions crates/perry-runtime/src/gc/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2961,6 +2961,12 @@ fn gc_start_budgeted_full_cycle(
rebaseline: BudgetedGcRebaseline,
progress_kind: GcProgressKind,
) -> BudgetedGcCycle {
// #9231: budgeted cycles construct GcCycleState directly instead of
// entering through gc_collect_{minor,full}_with_trigger, so the lazy
// stack-map index must be built here while allocation is still legal.
// Otherwise the first root-scan step reaches #9182's fail-closed guard
// with an owed index and aborts.
super::roots::ensure_stack_maps_built();
let mut state = GcCycleState::new_full(GcTriggerSnapshot::capture(trigger_kind));
state.set_progress_kind(progress_kind);
BudgetedGcCycle {
Expand Down Expand Up @@ -2988,6 +2994,8 @@ fn gc_start_budgeted_minor_fallback_cycle_with_snapshot(
rebaseline: BudgetedGcRebaseline,
progress_kind: GcProgressKind,
) -> BudgetedGcCycle {
// Same direct-constructor path as gc_start_budgeted_full_cycle above.
super::roots::ensure_stack_maps_built();
let prev_in_alloc = GC_FLAGS.with(|f| {
let prev = f.get();
f.set(prev | GC_FLAG_IN_ALLOC);
Expand Down
46 changes: 44 additions & 2 deletions crates/perry/tests/issue_5972_getdevicemodel_object_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,41 @@ fn perry_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_perry"))
}

fn host_ui_archive_available() -> bool {
let archive_name = if cfg!(target_os = "linux") {
"libperry_ui_gtk4.a"
} else if cfg!(target_os = "macos") {
"libperry_ui_macos.a"
} else if cfg!(target_os = "windows") {
"perry_ui_windows.lib"
} else {
return false;
};

let mut search_dirs = Vec::new();
for env_var in ["PERRY_RUNTIME_DIR", "PERRY_LIB_DIR"] {
if let Some(dir) = std::env::var_os(env_var) {
search_dirs.push(PathBuf::from(dir));
}
}

let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
search_dirs.push(workspace_root.join("target/release"));
search_dirs.push(workspace_root.join("target/debug"));

if let Some(bin_dir) = perry_bin().parent() {
search_dirs.push(bin_dir.to_path_buf());
if let Some(target_dir) = bin_dir.parent() {
search_dirs.push(target_dir.join("release"));
search_dirs.push(target_dir.join("debug"));
}
}

search_dirs
.into_iter()
.any(|dir| dir.join(archive_name).is_file())
}

fn compile_and_run(dir: &std::path::Path, source: &str) -> String {
let entry = dir.join("main.ts");
let output = dir.join("main_bin");
Expand Down Expand Up @@ -63,12 +98,20 @@ fn compile_and_run(dir: &std::path::Path, source: &str) -> String {
/// `Record<string, number>` with it works (known key hits, unknown misses).
#[test]
fn get_device_model_is_a_string_usable_as_object_key() {
// The platform implementation lives in libperry_ui_*.a. Generic Perry
// integration shards intentionally build only runtime + stdlib, so keep
// the platform end-to-end coverage when that archive is provisioned and
// rely on perry-dispatch's unit test for the ABI shape everywhere else.
if !host_ui_archive_available() {
eprintln!("skipping platform-backed #5972 test: host UI archive is unavailable");
return;
}

let dir = tempfile::tempdir().expect("tempdir");
let stdout = compile_and_run(
dir.path(),
r#"
import { getDeviceModel } from "perry/system";
import { Text } from "perry/ui"; // pull in the UI backend that defines perry_system_*

const offsets: Record<string, number> = {
"iPhone15,2": 1.0,
Expand All @@ -88,7 +131,6 @@ console.log("TYPE", typeof model);
// WITHOUT crashing — that is the #5972 repro.
console.log("KNOWN", offsetFor("iPhone15,2"));
console.log("HOST", offsetFor(model));
const _t = Text("keep the UI backend linked");
console.log("DONE");
"#,
);
Expand Down
Loading