diff --git a/changelog.d/9208-raw-handle-debt.md b/changelog.d/9208-raw-handle-debt.md new file mode 100644 index 0000000000..4529d8a1a8 --- /dev/null +++ b/changelog.d/9208-raw-handle-debt.md @@ -0,0 +1,8 @@ +The raw-handle debt ratchet now counts empty +`RuntimeHandle::across_{mut,const,nanbox}(|| ())` wrappers as debt. Those +wrappers refreshed a handle across no work, so they were equivalent to a bare +pointer read while still receiving credit as a conversion. + +All 17 existing no-op wrappers now use scoped handle access or put the real +allocation-capable operation inside `across_*`. The total baseline and every +per-module ceiling remain unchanged. diff --git a/crates/perry-runtime/src/array/iter_object.rs b/crates/perry-runtime/src/array/iter_object.rs index f15c66f964..94b3214552 100644 --- a/crates/perry-runtime/src/array/iter_object.rs +++ b/crates/perry-runtime/src/array/iter_object.rs @@ -83,8 +83,7 @@ unsafe fn alloc_iterator_backing(backing: f64, kind: i32) -> f64 { // `Object.getPrototypeOf(it)` and the inherited `.next` read resolve. obj_h .with_mut_ptr(|obj| crate::object::attach_iterator_prototype(obj, ARRAY_ITERATOR_CLASS_ID)); - let (_, obj) = obj_h.across_mut::(|| ()); - js_nanbox_pointer(obj as i64) + obj_h.with_mut_ptr::(|obj| js_nanbox_pointer(obj as i64)) } unsafe fn alloc_iterator(arr_ptr: *mut ArrayHeader, kind: i32) -> f64 { diff --git a/crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs b/crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs index 5b760d0ea7..08c9a151c4 100644 --- a/crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs +++ b/crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs @@ -383,8 +383,9 @@ pub extern "C" fn js_boxed_symbol_new(value: f64) -> f64 { obj.with_mut_ptr::(|obj| { attach_boxed_primitive_prototype(obj, CLASS_ID_BOXED_SYMBOL) }); - let (_, obj) = obj.across_mut::(|| ()); - crate::value::js_nanbox_pointer(obj as i64) + obj.with_mut_ptr::(|obj| { + crate::value::js_nanbox_pointer(obj as i64) + }) } #[cfg(test)] diff --git a/crates/perry-runtime/src/collection_iter_object.rs b/crates/perry-runtime/src/collection_iter_object.rs index ad18170850..df9f52c848 100644 --- a/crates/perry-runtime/src/collection_iter_object.rs +++ b/crates/perry-runtime/src/collection_iter_object.rs @@ -71,35 +71,40 @@ unsafe fn alloc_iterator(class_id: u32, coll_nanboxed: f64, kind: i32) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let coll_h = scope.root_nanbox_f64(coll_nanboxed); let obj_h = scope.root_raw_mut_ptr(js_object_alloc(class_id, 6)); - let obj = || obj_h.across_mut::(|| ()).1; // Field 0: backing collection (NaN-boxed pointer so the GC scanner keeps it). - js_object_set_field( - obj(), - 0, - JSValue::from_bits(coll_h.get_nanbox_f64().to_bits()), - ); + obj_h.with_mut_ptr::(|obj| { + js_object_set_field( + obj, + 0, + JSValue::from_bits(coll_h.get_nanbox_f64().to_bits()), + ) + }); // Field 1: cursor index (index just past the last-returned entry), starts at 0. - js_object_set_field(obj(), 1, JSValue::number(0.0)); + obj_h.with_mut_ptr::(|obj| js_object_set_field(obj, 1, JSValue::number(0.0))); // Field 2: iterator kind. - js_object_set_field(obj(), 2, JSValue::number(kind as f64)); + obj_h.with_mut_ptr::(|obj| { + js_object_set_field(obj, 2, JSValue::number(kind as f64)) + }); // Field 3: collection size observed at the last `next()`. `-1` sentinel means // "not started" (no entry returned yet). Used to detect a mid-iteration // delete (which compacts the entries array, shifting live entries below the // cursor) so the cursor can be re-derived from the last key (#6075). - js_object_set_field(obj(), 3, JSValue::number(-1.0)); + obj_h.with_mut_ptr::(|obj| js_object_set_field(obj, 3, JSValue::number(-1.0))); // Field 4: the KEY of the last-returned entry (a Map key / Set value), used // to re-derive the cursor after a delete-shift. Undefined until started. - js_object_set_field(obj(), 4, JSValue::undefined()); + obj_h.with_mut_ptr::(|obj| js_object_set_field(obj, 4, JSValue::undefined())); // Field 5: the recycled `{value, done}` result the FUSED for-of driver // mutates in place (one allocation per loop, not per element). Manual // `.next()` calls never touch it — they keep returning fresh objects, so // a caller that retains results observes spec behavior. - js_object_set_field(obj(), 5, JSValue::undefined()); + obj_h.with_mut_ptr::(|obj| js_object_set_field(obj, 5, JSValue::undefined())); // Link `[[Prototype]]` to the shared `%MapIteratorPrototype%` / // `%SetIteratorPrototype%` singleton so `Object.getPrototypeOf(it)` and the // inherited `.next` read resolve. - crate::object::attach_iterator_prototype(obj(), class_id); - js_nanbox_pointer(obj() as i64) + obj_h.with_mut_ptr::(|obj| { + crate::object::attach_iterator_prototype(obj, class_id) + }); + obj_h.with_mut_ptr::(|obj| js_nanbox_pointer(obj as i64)) } /// Build a fresh Map iterator object for `map` (raw pointer) of the given @@ -210,8 +215,7 @@ unsafe fn make_pair_array(a: f64, b: f64) -> f64 { (*pair).length = 2; crate::array::rebuild_array_layout_exact(pair); }); - let (_, pair) = pair.across_mut::(|| ()); - js_nanbox_pointer(pair as i64) + pair.with_mut_ptr::(|pair| js_nanbox_pointer(pair as i64)) } /// Compute the entries-array index to read next, self-correcting for a diff --git a/crates/perry-runtime/src/dyn_eval/tests.rs b/crates/perry-runtime/src/dyn_eval/tests.rs index 6a8425adea..b39418c017 100644 --- a/crates/perry-runtime/src/dyn_eval/tests.rs +++ b/crates/perry-runtime/src/dyn_eval/tests.rs @@ -995,16 +995,12 @@ fn script_literals_use_fresh_populated_realm_prototypes() { let outer_object_prototype = crate::object::builtin_prototype_value("Object"); let scope = crate::gc::RuntimeHandleScope::new(); let intrinsics = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); - crate::object::populate_global_this_builtins( - intrinsics - .across_mut::(|| ()) - .1, - ); - let intrinsics = crate::value::js_nanbox_pointer( - intrinsics - .across_mut::(|| ()) - .1 as i64, - ); + intrinsics.with_mut_ptr::(|intrinsics| { + crate::object::populate_global_this_builtins(intrinsics) + }); + let intrinsics = intrinsics.with_mut_ptr::(|intrinsics| { + crate::value::js_nanbox_pointer(intrinsics as i64) + }); let realm_object_prototype = bridge::intrinsic_prototype(intrinsics, "Object"); assert_ne!( realm_object_prototype.to_bits(), diff --git a/crates/perry-runtime/src/intl/list_relative_plural.rs b/crates/perry-runtime/src/intl/list_relative_plural.rs index ce1b2656bb..bb5dc6ed95 100644 --- a/crates/perry-runtime/src/intl/list_relative_plural.rs +++ b/crates/perry-runtime/src/intl/list_relative_plural.rs @@ -809,15 +809,17 @@ pub(super) fn configure_plural_rules( false, ) }); - obj_handle.with_mut_ptr(|obj| { - install_bound_instance_function( - obj, - "resolvedOptions", - plural_rules_bound_resolved_options_thunk as *const u8, - 0, - ) + let (_, obj) = obj_handle.across_mut::(|| { + obj_handle.with_mut_ptr(|obj| { + install_bound_instance_function( + obj, + "resolvedOptions", + plural_rules_bound_resolved_options_thunk as *const u8, + 0, + ) + }) }); - obj_handle.across_mut::(|| ()).1 + obj } /// en plural-category selection. Cardinal: `i == 1 && v == 0` → "one". Ordinal diff --git a/crates/perry-runtime/src/map.rs b/crates/perry-runtime/src/map.rs index 141c171542..6d2f5beffd 100644 --- a/crates/perry-runtime/src/map.rs +++ b/crates/perry-runtime/src/map.rs @@ -2999,8 +2999,10 @@ pub extern "C" fn js_map_from_iterable(value: f64) -> *mut MapHeader { } } - match constructor_iter(value_handle.get_nanbox_f64()) { - ConstructorIter::Empty => map_handle.across_mut::(|| ()).1, + let (source, map_after_classify) = + map_handle.across_mut::(|| constructor_iter(value_handle.get_nanbox_f64())); + match source { + ConstructorIter::Empty => map_after_classify, ConstructorIter::Array(arr_value) => { let arr_handle = scope.root_nanbox_f64(arr_value); let arr_ptr = crate::value::js_nanbox_get_pointer(arr_handle.get_nanbox_f64()) diff --git a/crates/perry-runtime/src/node_vm.rs b/crates/perry-runtime/src/node_vm.rs index 5428e268aa..92efa9495b 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -17,12 +17,12 @@ use crate::object::{ObjectHeader, PropertyAttrs}; use crate::string::StringHeader; use crate::value::JSValue; -/// Re-read a rooted raw pointer without recording bare-handle debt. -/// Prefer pairing a real allocating call via `across_*` when one is present; -/// this covers final/local reads where the handle is the source of truth. +/// Keep raw pointers borrowed from runtime handles inside the operation that +/// consumes them. Callers that need a post-allocation pointer use `across_*` +/// with that allocation in its closure instead. #[inline] -fn hmut(h: &crate::gc::RuntimeHandle) -> *mut T { - h.across_mut::(|| ()).1 +fn with_hmut(h: &crate::gc::RuntimeHandle, f: impl FnOnce(*mut T) -> R) -> R { + h.with_mut_ptr(f) } /// Feature-gated dyn-eval entry points. Product builds (`perry` → runtime with @@ -343,11 +343,11 @@ fn set_field(obj: *mut ObjectHeader, name: &str, value: f64) { let obj = scope.root_raw_mut_ptr(obj); let value = scope.root_nanbox_f64(value); let key = scope.root_string_ptr(field_key(name)); - crate::object::js_object_set_field_by_name( - hmut::(&obj), - hmut::(&key), - value.get_nanbox_f64(), - ); + obj.with_mut_ptr::(|obj| { + key.with_mut_ptr::(|key| { + crate::object::js_object_set_field_by_name(obj, key, value.get_nanbox_f64()) + }) + }); } fn get_field(obj: *mut ObjectHeader, name: &str) -> f64 { @@ -1073,16 +1073,24 @@ fn fresh_intrinsic_global() -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let intrinsics = crate::object::js_object_alloc(0, 0); let intrinsics = scope.root_raw_mut_ptr(intrinsics); - crate::object::populate_global_this_builtins(hmut::(&intrinsics)); - crate::object::js_object_delete_field(hmut::(&intrinsics), string_ptr("process")); - let intrinsics = hmut::(&intrinsics); - let value = object_value(intrinsics); - VM_INTRINSIC_GLOBAL.with(|slot| { - slot.set(value.to_bits()); - crate::gc::runtime_write_barrier_root_heap_word(intrinsics as u64); - crate::gc::js_gc_register_global_root(slot.as_ptr() as i64); + intrinsics.with_mut_ptr::(|intrinsics| { + crate::object::populate_global_this_builtins(intrinsics) + }); + let process = scope.root_string_ptr(string_ptr("process")); + intrinsics.with_mut_ptr::(|intrinsics| { + process.with_mut_ptr::(|process| { + crate::object::js_object_delete_field(intrinsics, process) + }) }); - value + intrinsics.with_mut_ptr::(|intrinsics| { + let value = object_value(intrinsics); + VM_INTRINSIC_GLOBAL.with(|slot| { + slot.set(value.to_bits()); + crate::gc::runtime_write_barrier_root_heap_word(intrinsics as u64); + crate::gc::js_gc_register_global_root(slot.as_ptr() as i64); + }); + value + }) } fn new_context_state(sandbox: f64, dont_contextify: bool, options: ContextOptions) -> ContextState { @@ -1289,18 +1297,24 @@ fn install_script_method( crate::closure::js_register_closure_arity(func_ptr, 2); let closure = crate::closure::js_closure_alloc(func_ptr, 0); let closure = scope.root_raw_mut_ptr(closure); - crate::object::set_builtin_closure_length(hmut::(&closure) as usize, arity); - let value = crate::value::js_nanbox_pointer(hmut::(&closure) as i64); - crate::object::js_object_set_field_by_name( - hmut::(&obj), - hmut::(&key), - value, - ); - crate::object::set_builtin_property_attrs( - hmut::(&obj) as usize, - name.to_string(), - PropertyAttrs::new(true, false, true), - ); + closure.with_mut_ptr::(|closure| { + crate::object::set_builtin_closure_length(closure as usize, arity) + }); + let value = closure.with_mut_ptr::(|closure| { + crate::value::js_nanbox_pointer(closure as i64) + }); + obj.with_mut_ptr::(|obj| { + key.with_mut_ptr::(|key| { + crate::object::js_object_set_field_by_name(obj, key, value) + }) + }); + obj.with_mut_ptr::(|obj| { + crate::object::set_builtin_property_attrs( + obj as usize, + name.to_string(), + PropertyAttrs::new(true, false, true), + ) + }); } fn script_receiver() -> f64 { @@ -1321,77 +1335,86 @@ pub(crate) fn install_script_prototypes(constructor: f64) { let proto = scope.root_raw_mut_ptr(proto_ptr); let key = scope.root_nanbox_f64(string_value("runInThisContext")); if JSValue::from_bits( - crate::object::js_object_has_own( - object_value(hmut::(&proto)), - key.get_nanbox_f64(), - ) - .to_bits(), + proto + .with_mut_ptr::(|proto| { + crate::object::js_object_has_own(object_value(proto), key.get_nanbox_f64()) + }) + .to_bits(), ) .as_bool() { return; } let old_parent = scope.root_nanbox_u64( - crate::object::prototype_chain::object_static_prototype( - hmut::(&proto) as usize - ) - .unwrap_or(JSValue::null().bits()), + proto + .with_mut_ptr::(|proto| { + crate::object::prototype_chain::object_static_prototype(proto as usize) + }) + .unwrap_or(JSValue::null().bits()), ); let base = crate::object::js_object_alloc(0, 0); let base = scope.root_raw_mut_ptr(base); - crate::object::prototype_chain::object_set_static_prototype( - hmut::(&base) as usize, - old_parent.get_nanbox_u64(), - ); - crate::object::prototype_chain::object_set_static_prototype( - hmut::(&proto) as usize, - object_value(hmut::(&base)).to_bits(), - ); - set_field( - hmut::(&base), - "constructor", - constructor.get_nanbox_f64(), - ); - crate::object::set_builtin_property_attrs( - hmut::(&base) as usize, - "constructor".to_string(), - PropertyAttrs::new(true, false, true), - ); - install_script_method( - hmut::(&proto), - "runInThisContext", - vm_script_run_in_this_context_method, - 1, - ); - install_script_method( - hmut::(&proto), - "runInContext", - vm_script_run_in_context_method, - 2, - ); - install_script_method( - hmut::(&proto), - "runInNewContext", - vm_script_run_in_new_context_method, - 2, - ); - install_script_method( - hmut::(&base), - "runInContext", - vm_script_run_in_context_method, - 2, - ); - install_script_method( - hmut::(&base), - "createCachedData", - vm_script_create_cached_data_method, - 0, - ); - crate::object::set_builtin_property_attrs( - hmut::(&base) as usize, - "createCachedData".to_string(), - PropertyAttrs::new(true, true, true), - ); + base.with_mut_ptr::(|base| { + crate::object::prototype_chain::object_set_static_prototype( + base as usize, + old_parent.get_nanbox_u64(), + ) + }); + proto.with_mut_ptr::(|proto| { + base.with_mut_ptr::(|base| { + crate::object::prototype_chain::object_set_static_prototype( + proto as usize, + object_value(base).to_bits(), + ) + }) + }); + base.with_mut_ptr::(|base| { + set_field(base, "constructor", constructor.get_nanbox_f64()) + }); + base.with_mut_ptr::(|base| { + crate::object::set_builtin_property_attrs( + base as usize, + "constructor".to_string(), + PropertyAttrs::new(true, false, true), + ) + }); + proto.with_mut_ptr::(|proto| { + install_script_method( + proto, + "runInThisContext", + vm_script_run_in_this_context_method, + 1, + ) + }); + proto.with_mut_ptr::(|proto| { + install_script_method(proto, "runInContext", vm_script_run_in_context_method, 2) + }); + proto.with_mut_ptr::(|proto| { + install_script_method( + proto, + "runInNewContext", + vm_script_run_in_new_context_method, + 2, + ) + }); + base.with_mut_ptr::(|base| { + install_script_method(base, "runInContext", vm_script_run_in_context_method, 2) + }); + base.with_mut_ptr::(|base| { + install_script_method( + base, + "createCachedData", + vm_script_create_cached_data_method, + 0, + ) + }); + base.with_mut_ptr::(|base| { + crate::object::set_builtin_property_attrs( + base as usize, + "createCachedData".to_string(), + PropertyAttrs::new(true, true, true), + ) + }); } fn make_script(code: String, options: f64) -> f64 { @@ -1403,41 +1426,37 @@ fn make_script(code: String, options: f64) -> f64 { let source_map_url = extract_source_map_url(&code); let scope = crate::gc::RuntimeHandleScope::new(); let obj = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); - scripts().lock().unwrap().insert( - hmut::(&obj) as usize, - ScriptMetadata { - source: code, - filename: source_options.filename, - line_offset: source_options.line_offset, - column_offset: source_options.column_offset, - }, - ); + obj.with_mut_ptr::(|obj| { + scripts().lock().unwrap().insert( + obj as usize, + ScriptMetadata { + source: code, + filename: source_options.filename, + line_offset: source_options.line_offset, + column_offset: source_options.column_offset, + }, + ) + }); if let Some(url) = source_map_url { - set_field( - hmut::(&obj), - "sourceMapURL", - string_value(&url), - ); + let value = string_value(&url); + obj.with_mut_ptr::(|obj| set_field(obj, "sourceMapURL", value)); } if let Some(bytes) = cached_data { - set_field( - hmut::(&obj), - "cachedDataRejected", - bool_value(!cache_bytes_accepted(&bytes, CACHE_KIND_SCRIPT, hash)), - ); + obj.with_mut_ptr::(|obj| { + set_field( + obj, + "cachedDataRejected", + bool_value(!cache_bytes_accepted(&bytes, CACHE_KIND_SCRIPT, hash)), + ) + }); } else if produce_cached_data { - set_field( - hmut::(&obj), - "cachedData", - cached_data_buffer(CACHE_KIND_SCRIPT, hash), - ); - set_field( - hmut::(&obj), - "cachedDataProduced", - bool_value(true), - ); + let value = cached_data_buffer(CACHE_KIND_SCRIPT, hash); + obj.with_mut_ptr::(|obj| set_field(obj, "cachedData", value)); + obj.with_mut_ptr::(|obj| { + set_field(obj, "cachedDataProduced", bool_value(true)) + }); } - object_value(hmut::(&obj)) + obj.with_mut_ptr::(object_value) } extern "C" fn vm_script_create_cached_data_method( diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index f637288a5f..731856e47f 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -6,7 +6,7 @@ use super::*; fn evaluate_source_module(module: *mut ObjectHeader) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let module = scope.root_raw_mut_ptr(module); - let status = module_status(hmut::(&module)); + let status = with_hmut(&module, module_status); if status != STATUS_LINKED && status != STATUS_EVALUATED { return throw_vm_status("Module status must be linked"); } @@ -14,16 +14,19 @@ fn evaluate_source_module(module: *mut ObjectHeader) -> f64 { return undefined_value(); } - set_status(hmut::(&module), STATUS_EVALUATING); - let Some(namespace) = namespace_for_module(hmut::(&module)) else { - set_status(hmut::(&module), STATUS_ERRORED); + with_hmut(&module, |module| set_status(module, STATUS_EVALUATING)); + let Some(namespace) = with_hmut(&module, namespace_for_module) else { + with_hmut(&module, |module| set_status(module, STATUS_ERRORED)); return throw_vm_status("Module namespace is unavailable"); }; let namespace = scope.root_raw_mut_ptr(namespace); - let source = get_string_field(hmut::(&module), FIELD_SOURCE).unwrap_or_default(); - let context = scope.root_nanbox_f64(get_field(hmut::(&module), FIELD_CONTEXT)); - for (name, value) in build_import_env(hmut::(&module)) { + let source = + with_hmut(&module, |module| get_string_field(module, FIELD_SOURCE)).unwrap_or_default(); + let context = scope.root_nanbox_f64(with_hmut(&module, |module| { + get_field(module, FIELD_CONTEXT) + })); + for (name, value) in with_hmut(&module, build_import_env) { set_object_field(context.get_nanbox_f64(), &name, value); } let executable = split_source_statements(&source) @@ -41,25 +44,24 @@ fn evaluate_source_module(module: *mut ObjectHeader) -> f64 { lexical.get_nanbox_f64(), ) }) { - set_field(hmut::(&module), FIELD_ERROR, error); - set_status(hmut::(&module), STATUS_ERRORED); + with_hmut(&module, |module| set_field(module, FIELD_ERROR, error)); + with_hmut(&module, |module| set_status(module, STATUS_ERRORED)); return error; } - for export in read_exports(hmut::(&module)) { - set_field( - hmut::(&namespace), - &export.name, - de::script_binding(lexical.get_nanbox_f64(), &export.name), - ); + for export in with_hmut(&module, read_exports) { + let value = de::script_binding(lexical.get_nanbox_f64(), &export.name); + with_hmut(&namespace, |namespace| { + set_field(namespace, &export.name, value) + }); } - set_status(hmut::(&module), STATUS_EVALUATED); + with_hmut(&module, |module| set_status(module, STATUS_EVALUATED)); undefined_value() } fn evaluate_synthetic_module(module: *mut ObjectHeader) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let module = scope.root_raw_mut_ptr(module); - let status = module_status(hmut::(&module)); + let status = with_hmut(&module, module_status); if status == STATUS_EVALUATED { return undefined_value(); } @@ -67,25 +69,24 @@ fn evaluate_synthetic_module(module: *mut ObjectHeader) -> f64 { return throw_vm_status("Module status must be linked"); } - set_status(hmut::(&module), STATUS_EVALUATING); - let callback = scope.root_nanbox_f64(get_field( - hmut::(&module), - FIELD_EVALUATE_CALLBACK, - )); + with_hmut(&module, |module| set_status(module, STATUS_EVALUATING)); + let callback = scope.root_nanbox_f64(with_hmut(&module, |module| { + get_field(module, FIELD_EVALUATE_CALLBACK) + })); let js = JSValue::from_bits(callback.get_nanbox_f64().to_bits()); if !js.is_undefined() && !js.is_null() { - let prev = crate::object::js_implicit_this_set(object_value(hmut::(&module))); + let prev = crate::object::js_implicit_this_set(with_hmut(&module, object_value)); let outcome = crate::exception::js_call_catching(|| unsafe { crate::closure::js_native_call_value(callback.get_nanbox_f64(), std::ptr::null(), 0) }); crate::object::js_implicit_this_set(prev); if let Err(error) = outcome { - set_field(hmut::(&module), FIELD_ERROR, error); - set_status(hmut::(&module), STATUS_ERRORED); + with_hmut(&module, |module| set_field(module, FIELD_ERROR, error)); + with_hmut(&module, |module| set_status(module, STATUS_ERRORED)); return error; } } - set_status(hmut::(&module), STATUS_EVALUATED); + with_hmut(&module, |module| set_status(module, STATUS_EVALUATED)); undefined_value() } @@ -131,24 +132,28 @@ fn new_module_base(kind: &str, status: &str, identifier: String) -> *mut ObjectH let module = crate::object::js_object_alloc(0, 16); let module = scope.root_raw_mut_ptr(module); let value = string_value(kind); - set_field(hmut::(&module), FIELD_KIND, value); + with_hmut(&module, |module| set_field(module, FIELD_KIND, value)); let value = string_value(status); - set_field(hmut::(&module), FIELD_STATUS, value); + with_hmut(&module, |module| set_field(module, FIELD_STATUS, value)); let value = string_value(status); - set_field(hmut::(&module), "status", value); + with_hmut(&module, |module| set_field(module, "status", value)); let value = string_value(&identifier); - set_field(hmut::(&module), FIELD_IDENTIFIER, value); + with_hmut(&module, |module| set_field(module, FIELD_IDENTIFIER, value)); let value = string_value(&identifier); - set_field(hmut::(&module), "identifier", value); - set_field( - hmut::(&module), - FIELD_ERROR, - undefined_value(), - ); - set_field(hmut::(&module), "error", undefined_value()); + with_hmut(&module, |module| set_field(module, "identifier", value)); + with_hmut(&module, |module| { + set_field(module, FIELD_ERROR, undefined_value()) + }); + with_hmut(&module, |module| { + set_field(module, "error", undefined_value()) + }); let value = array_value(crate::array::js_array_alloc(0)); - set_field(hmut::(&module), FIELD_LINKED_MODULES, value); - hmut::(&module) + let ((), module) = module.across_mut::(|| { + with_hmut(&module, |module| { + set_field(module, FIELD_LINKED_MODULES, value) + }) + }); + module } extern "C" fn module_namespace_getter(closure: *const ClosureHeader) -> f64 { @@ -169,24 +174,28 @@ fn install_module_accessor( let closure = crate::closure::js_closure_alloc(getter as *const u8, 1); let closure = scope.root_raw_mut_ptr(closure); crate::closure::js_register_closure_arity(getter as *const u8, 0); - crate::closure::js_closure_set_capture_f64( - hmut::(&closure), - 0, - object_value(hmut::(&module)), - ); + with_hmut(&closure, |closure| { + with_hmut(&module, |module| { + crate::closure::js_closure_set_capture_f64(closure, 0, object_value(module)) + }) + }); unsafe { - crate::closure::rebuild_closure_layout_and_barriers(hmut::(&closure), 1); + with_hmut(&closure, |closure| { + crate::closure::rebuild_closure_layout_and_barriers(closure, 1) + }); } - set_field(hmut::(&module), name, undefined_value()); - crate::object::set_builtin_accessor_descriptor( - hmut::(&module) as usize, - name.to_string(), - crate::object::AccessorDescriptor { - get: crate::value::js_nanbox_pointer(hmut::(&closure) as i64).to_bits(), - set: 0, - }, - PropertyAttrs::new(false, false, false), - ); + with_hmut(&module, |module| set_field(module, name, undefined_value())); + let get = with_hmut(&closure, |closure: *mut ClosureHeader| { + crate::value::js_nanbox_pointer(closure as i64).to_bits() + }); + with_hmut(&module, |module: *mut ObjectHeader| { + crate::object::set_builtin_accessor_descriptor( + module as usize, + name.to_string(), + crate::object::AccessorDescriptor { get, set: 0 }, + PropertyAttrs::new(false, false, false), + ) + }); } fn set_module_namespace_tag(namespace: *mut ObjectHeader) { @@ -199,11 +208,15 @@ fn set_module_namespace_tag(namespace: *mut ObjectHeader) { let symbol = scope.root_raw_mut_ptr(symbol); let tag = scope.root_nanbox_f64(string_value("Module")); unsafe { - crate::symbol::js_object_set_symbol_property( - object_value(hmut::(&namespace)), - crate::value::js_nanbox_pointer(hmut::(&symbol) as i64), - tag.get_nanbox_f64(), - ); + with_hmut(&namespace, |namespace| { + with_hmut(&symbol, |symbol: *mut crate::symbol::SymbolHeader| { + crate::symbol::js_object_set_symbol_property( + object_value(namespace), + crate::value::js_nanbox_pointer(symbol as i64), + tag.get_nanbox_f64(), + ) + }) + }); } } @@ -237,43 +250,40 @@ pub extern "C" fn js_vm_source_text_module_new(code: f64, options: f64) -> f64 { let module = scope.root_raw_mut_ptr(module); let namespace = crate::object::js_object_alloc_null_proto(0, parsed.exports.len() as u32); let namespace = scope.root_raw_mut_ptr(namespace); - set_module_namespace_tag(hmut::(&namespace)); + with_hmut(&namespace, set_module_namespace_tag); for export in &parsed.exports { - set_field( - hmut::(&namespace), - &export.name, - undefined_value(), - ); + with_hmut(&namespace, |namespace| { + set_field(namespace, &export.name, undefined_value()) + }); } - set_field( - hmut::(&module), - FIELD_NAMESPACE, - object_value(hmut::(&namespace)), - ); - install_module_accessor( - hmut::(&module), - "namespace", - module_namespace_getter, - ); - install_module_accessor(hmut::(&module), "error", module_error_getter); - set_field( - hmut::(&module), - FIELD_CONTEXT, - context.get_nanbox_f64(), - ); + let namespace_value = with_hmut(&namespace, object_value); + with_hmut(&module, |module| { + set_field(module, FIELD_NAMESPACE, namespace_value) + }); + with_hmut(&module, |module| { + install_module_accessor(module, "namespace", module_namespace_getter) + }); + with_hmut(&module, |module| { + install_module_accessor(module, "error", module_error_getter) + }); + with_hmut(&module, |module| { + set_field(module, FIELD_CONTEXT, context.get_nanbox_f64()) + }); let value = string_value(&source); - set_field(hmut::(&module), FIELD_SOURCE, value); + with_hmut(&module, |module| set_field(module, FIELD_SOURCE, value)); let value = requests_array(&parsed.requests); - set_field(hmut::(&module), FIELD_REQUESTS, value); + with_hmut(&module, |module| set_field(module, FIELD_REQUESTS, value)); let value = strings_array(&parsed.requests); - set_field(hmut::(&module), "dependencySpecifiers", value); + with_hmut(&module, |module| { + set_field(module, "dependencySpecifiers", value) + }); let value = requests_array(&parsed.requests); - set_field(hmut::(&module), "moduleRequests", value); + with_hmut(&module, |module| set_field(module, "moduleRequests", value)); let value = imports_array(&parsed.imports); - set_field(hmut::(&module), FIELD_IMPORTS, value); + with_hmut(&module, |module| set_field(module, FIELD_IMPORTS, value)); let value = exports_array(&parsed.exports); - set_field(hmut::(&module), FIELD_EXPORTS, value); - object_value(hmut::(&module)) + with_hmut(&module, |module| set_field(module, FIELD_EXPORTS, value)); + with_hmut(&module, object_value) } pub extern "C" fn js_vm_synthetic_module_new( @@ -309,11 +319,15 @@ pub extern "C" fn js_vm_synthetic_module_new( let module = scope.root_raw_mut_ptr(module); let namespace = crate::object::js_object_alloc_null_proto(0, 0); let namespace = scope.root_raw_mut_ptr(namespace); - set_module_namespace_tag(hmut::(&namespace)); - let len = crate::array::js_array_length(hmut::(&export_names)); + with_hmut(&namespace, set_module_namespace_tag); + let len = with_hmut(&export_names, |export_names| { + crate::array::js_array_length(export_names) + }); let mut exports = Vec::new(); for idx in 0..len { - let value = crate::array::js_array_get_f64(hmut::(&export_names), idx); + let value = with_hmut(&export_names, |export_names| { + crate::array::js_array_get_f64(export_names, idx) + }); let Some(name) = string_from_value(value) else { let message = format!( "The \"exportNames[{idx}]\" argument must be of type string. Received {}", @@ -325,36 +339,37 @@ pub extern "C" fn js_vm_synthetic_module_new( name: name.clone(), expr: String::new(), }); - set_field(hmut::(&namespace), &name, undefined_value()); + with_hmut(&namespace, |namespace| { + set_field(namespace, &name, undefined_value()) + }); } - set_field( - hmut::(&module), - FIELD_NAMESPACE, - object_value(hmut::(&namespace)), - ); - install_module_accessor( - hmut::(&module), - "namespace", - module_namespace_getter, - ); - install_module_accessor(hmut::(&module), "error", module_error_getter); - set_field( - hmut::(&module), - FIELD_CONTEXT, - context.get_nanbox_f64(), - ); + let namespace_value = with_hmut(&namespace, object_value); + with_hmut(&module, |module| { + set_field(module, FIELD_NAMESPACE, namespace_value) + }); + with_hmut(&module, |module| { + install_module_accessor(module, "namespace", module_namespace_getter) + }); + with_hmut(&module, |module| { + install_module_accessor(module, "error", module_error_getter) + }); + with_hmut(&module, |module| { + set_field(module, FIELD_CONTEXT, context.get_nanbox_f64()) + }); let value = requests_array(&[]); - set_field(hmut::(&module), FIELD_REQUESTS, value); + with_hmut(&module, |module| set_field(module, FIELD_REQUESTS, value)); let value = imports_array(&[]); - set_field(hmut::(&module), FIELD_IMPORTS, value); + with_hmut(&module, |module| set_field(module, FIELD_IMPORTS, value)); let value = exports_array(&exports); - set_field(hmut::(&module), FIELD_EXPORTS, value); - set_field( - hmut::(&module), - FIELD_EVALUATE_CALLBACK, - evaluate_callback.get_nanbox_f64(), - ); - object_value(hmut::(&module)) + with_hmut(&module, |module| set_field(module, FIELD_EXPORTS, value)); + with_hmut(&module, |module| { + set_field( + module, + FIELD_EVALUATE_CALLBACK, + evaluate_callback.get_nanbox_f64(), + ) + }); + with_hmut(&module, object_value) } pub extern "C" fn js_vm_module_status(module_value: f64) -> f64 { @@ -401,16 +416,16 @@ pub extern "C" fn js_vm_module_link(module_value: f64, linker: f64) -> f64 { let module = scope.root_raw_mut_ptr(module); let module_value = scope.root_nanbox_f64(module_value); let linker = scope.root_nanbox_f64(linker); - if module_kind(hmut::(&module)) == KIND_SYNTHETIC { - set_status(hmut::(&module), STATUS_LINKED); + if with_hmut(&module, module_kind) == KIND_SYNTHETIC { + with_hmut(&module, |module| set_status(module, STATUS_LINKED)); return undefined_value(); } - if module_status(hmut::(&module)) != STATUS_UNLINKED { + if with_hmut(&module, module_status) != STATUS_UNLINKED { return undefined_value(); } - set_status(hmut::(&module), STATUS_LINKING); - let requests = read_requests(hmut::(&module)); + with_hmut(&module, |module| set_status(module, STATUS_LINKING)); + let requests = with_hmut(&module, read_requests); let mut linked = crate::array::js_array_alloc(requests.len() as u32); for specifier in &requests { let args = [ @@ -423,12 +438,10 @@ pub extern "C" fn js_vm_module_link(module_value: f64, linker: f64) -> f64 { }; linked = crate::array::js_array_push_f64(linked, dep); } - set_field( - hmut::(&module), - FIELD_LINKED_MODULES, - array_value(linked), - ); - set_status(hmut::(&module), STATUS_LINKED); + with_hmut(&module, |module| { + set_field(module, FIELD_LINKED_MODULES, array_value(linked)) + }); + with_hmut(&module, |module| set_status(module, STATUS_LINKED)); undefined_value() } @@ -536,11 +549,11 @@ pub extern "C" fn js_vm_synthetic_module_set_export( let Some(name) = string_from_value(name_value) else { return throw_vm_type("SyntheticModule export name must be a string"); }; - let exports = read_exports(hmut::(&module)); + let exports = with_hmut(&module, read_exports); if !exports.iter().any(|export| export.name == name) { return throw_reference_error_no_code(&format!("Export '{name}' is not defined in module")); } - let Some(namespace) = namespace_for_module(hmut::(&module)) else { + let Some(namespace) = with_hmut(&module, namespace_for_module) else { return throw_vm_status("SyntheticModule namespace is unavailable"); }; set_field(namespace, &name, value.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index eb038fb691..84cb811156 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -561,7 +561,7 @@ pub unsafe extern "C-unwind" fn js_spread_tail_fallback_args( let fixed_handles = scope.root_nanbox_f64_slice(fixed); let spread_handle = scope.root_nanbox_f64(spread); - let (_, rooted_spread) = spread_handle.across_nanbox(|| ()); + let rooted_spread = spread_handle.get_nanbox_f64(); // Drive the real iterator protocol on a guard miss. The older // `js_array_like_to_array` shortcut reinterprets an Array Proxy handle or // object-backed Array-subclass instance as an `ArrayHeader`, making both @@ -587,7 +587,7 @@ pub unsafe extern "C-unwind" fn js_spread_tail_fallback_args( let result_handle = scope.root_raw_mut_ptr(crate::array::js_array_alloc(capacity)); for value in &fixed_handles { - let (_, rooted_value) = value.across_nanbox(|| ()); + let rooted_value = value.get_nanbox_f64(); let next = result_handle .with_mut_ptr(|result| crate::array::js_array_push_f64(result, rooted_value)); result_handle.set_raw_mut_ptr(next); @@ -598,7 +598,7 @@ pub unsafe extern "C-unwind" fn js_spread_tail_fallback_args( // The push can collect while `value` is otherwise only a Rust local. let value_scope = crate::gc::RuntimeHandleScope::new(); let value_handle = value_scope.root_nanbox_f64(value); - let (_, rooted_value) = value_handle.across_nanbox(|| ()); + let rooted_value = value_handle.get_nanbox_f64(); let next = result_handle .with_mut_ptr(|result| crate::array::js_array_push_f64(result, rooted_value)); result_handle.set_raw_mut_ptr(next); diff --git a/crates/perry-runtime/src/object/object_ops/define_property.rs b/crates/perry-runtime/src/object/object_ops/define_property.rs index 7f18aaa72e..ea34c8389c 100644 --- a/crates/perry-runtime/src/object/object_ops/define_property.rs +++ b/crates/perry-runtime/src/object/object_ops/define_property.rs @@ -1363,9 +1363,10 @@ pub extern "C" fn js_object_define_property( // the pre-call address to anything that outlives the call. macro_rules! across { ($call:expr) => {{ - let (result, refreshed_obj) = obj_handle.across_mut::(|| $call); - let ((), refreshed_key) = - key_str_handle.across_mut::(|| ()); + let ((result, refreshed_obj), refreshed_key) = key_str_handle + .across_mut::(|| { + obj_handle.across_mut::(|| $call) + }); obj = refreshed_obj; key_str = refreshed_key; obj_value = f64::from_bits(obj_value_handle.get_heap_word_u64()); diff --git a/crates/perry-runtime/src/object/reflect_support.rs b/crates/perry-runtime/src/object/reflect_support.rs index f3ab3c8e84..5e86b75ccf 100644 --- a/crates/perry-runtime/src/object/reflect_support.rs +++ b/crates/perry-runtime/src/object/reflect_support.rs @@ -180,17 +180,11 @@ pub(crate) fn obj_value_has_own_key(value: f64, key: f64) -> bool { if let Some(present) = crate::process::process_env_has_field(obj, key_str) { return present; } - // #7963 (the second half of #6949's deferred scope note): `js_array_get` - // MATERIALIZES a lazy array, so it can allocate and therefore evacuate. - // `keys` and `key_str` were raw Rust locals walked across it — neither - // shadow slots nor temp roots nor reachable from any registered - // scanner, so the collector could neither keep them alive nor rewrite - // them, and the very next iteration compared a from-space string - // against a from-space slot. Root both and re-read each iteration; the - // pre-call addresses are never bound past the call. - let keys_handle = scope.root_raw_mut_ptr(crate::object::object_keys_array(obj)); - let key_handle = scope.root_string_ptr(key_str); - let ((), keys) = keys_handle.across_mut::(|| ()); + // #9190 replaced the allocating per-element `js_array_get` walk with + // the consult-only key index below, so no handle round-trip is needed: + // there is no collection point between reading these pointers and + // consuming them. + let keys = crate::object::object_keys_array(obj); // Defence in depth for the class the buffer arm above closes by // routing: `keys_array` is only an `ArrayHeader` when `obj` really is // an `ObjectHeader`, and a receiver kind with no arm here reaches this @@ -221,9 +215,7 @@ pub(crate) fn obj_value_has_own_key(value: f64, key: f64) -> bool { // // `keys_find_slot_by_key_ptr` allocates nothing (a consult-only shape // probe, then a raw slot compare), so unlike the loop it replaced it - // needs no per-iteration re-rooting — the handles above still cover - // the `js_string_coerce` that produced `key_str`. - let ((), key_str) = key_handle.across_const::(|| ()); + // needs no per-iteration re-rooting. crate::object::keys_find_slot_by_key_ptr(keys, key_count, key_str).is_some() } } diff --git a/crates/perry-runtime/src/string/iter_object.rs b/crates/perry-runtime/src/string/iter_object.rs index 6aa62ebcfa..6b8ccfbf7a 100644 --- a/crates/perry-runtime/src/string/iter_object.rs +++ b/crates/perry-runtime/src/string/iter_object.rs @@ -54,8 +54,7 @@ unsafe fn alloc_iterator(cp_array: *mut ArrayHeader) -> f64 { obj_h.with_mut_ptr(|obj| { crate::object::attach_iterator_prototype(obj, STRING_ITERATOR_CLASS_ID) }); - let (_, obj) = obj_h.across_mut::(|| ()); - js_nanbox_pointer(obj as i64) + obj_h.with_mut_ptr::(|obj| js_nanbox_pointer(obj as i64)) } /// `''[Symbol.iterator]()` — build a String iterator over `s`'s code points. diff --git a/crates/perry-runtime/src/typedarray/construct.rs b/crates/perry-runtime/src/typedarray/construct.rs index 212b5c8193..7187406b47 100644 --- a/crates/perry-runtime/src/typedarray/construct.rs +++ b/crates/perry-runtime/src/typedarray/construct.rs @@ -326,12 +326,21 @@ unsafe fn typed_array_from_rooted_snapshot( raw: &[crate::gc::RuntimeHandle<'_>], ) -> *mut TypedArrayHeader { let scope = crate::gc::RuntimeHandleScope::new(); - let ta = scope.root_raw_mut_ptr(typed_array_alloc(kind, raw.len() as u32)); + let allocated = typed_array_alloc(kind, raw.len() as u32); + if raw.is_empty() { + return allocated; + } + let ta = scope.root_raw_mut_ptr(allocated); for (i, value) in raw.iter().enumerate() { - let coerced = bigint::coerce_for_kind(kind, value.get_nanbox_f64()); - ta.with_mut_ptr::(|ta| store_at(ta, i, coerced)); + let (coerced, ta) = ta.across_mut::(|| { + bigint::coerce_for_kind(kind, value.get_nanbox_f64()) + }); + store_at(ta, i, coerced); + if i + 1 == raw.len() { + return ta; + } } - ta.across_mut::(|| ()).1 + unreachable!("non-empty snapshot loop must return its typed array") } /// `Get(obj, name)` for a plain-object or function source value. diff --git a/scripts/raw_handle_debt.py b/scripts/raw_handle_debt.py index 278e3566ed..5f9aefceb5 100755 --- a/scripts/raw_handle_debt.py +++ b/scripts/raw_handle_debt.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Ratchet the number of bare raw-pointer reads out of GC root handles. +"""Ratchet raw-pointer custody debt around GC root handles. A `RuntimeHandleScope` gives an object liveness -- the collector marks it and rewrites the slot. It does nothing for a raw pointer already read out of that @@ -8,11 +8,13 @@ ALREADY; what was missing was ordering the re-read against the collection point. `RuntimeHandle::across_{mut,const,nanbox}` expresses that ordering in one call -and never binds the pre-call address. `with_{mut,const}_ptr` covers the other +and never binds the pre-call address, provided the call that may allocate is +inside its closure. An empty `across_*(|| ())` refreshes across nothing and is +therefore the same debt as a bare read. `with_{mut,const}_ptr` covers the other legitimate shape: passing the current pointer directly to a non-allocating operation or to an entry point that establishes its own root before it can -allocate. Each bare `get_raw_*_ptr` is a site where those contracts are a -review question instead of a shape. +allocate. Each bare `get_raw_*_ptr` or empty `across_*` is a site where those +contracts are a review question instead of a shape. This is a DEBT COUNTER, not a soundness proof. Rust has no effect system to mark "this call may allocate", so no signature can reject holding a stale copy. Not @@ -23,7 +25,7 @@ ======================================= `--update` refuses to raise the baseline, but nothing made CI *run* `--update`. -A pull request could add bare reads, raise `raw_handle_debt_baseline.txt` and +A pull request could add debt sites, raise `raw_handle_debt_baseline.txt` and the per-module ceilings to match, and the plain check would compare the new count against the new baseline and pass. The ratchet measured the diff against a number the same diff was allowed to move (#7659). @@ -43,7 +45,12 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent SRC = ROOT / "crates" / "perry-runtime" / "src" BASELINE = ROOT / "scripts" / "raw_handle_debt_baseline.txt" -PAT = re.compile(r"\.get_raw_(?:mut|const)_ptr\b") +PAT = re.compile( + r"\.get_raw_(?:mut|const)_ptr\b" + r"|\.across_(?:mut|const|nanbox)" + r"(?:\s*::\s*<[^;{}]*>)?" + r"\s*\(\s*(?:move\s+)?\|\|\s*(?:\(\s*\)|\{\s*\})\s*\)" +) # The accessors and scoped-pointer combinators are DEFINED here and call each # other; counting this file would make the ratchet count its own implementation @@ -91,12 +98,15 @@ def check_per_module(per_file): for path, n in sorted(per_file.items()): if path not in ceilings: bad.append( - f"{path}: {n} bare read(s) in a module with no ceiling. New code must " - f"use RuntimeHandle::across_{{mut,const,nanbox}} or " + f"{path}: {n} raw-handle debt site(s) in a module with no ceiling. " + f"New code must put real work inside RuntimeHandle::" + f"across_{{mut,const,nanbox}} or use " f"with_{{mut,const}}_ptr; see #7341." ) elif n > ceilings[path]: - bad.append(f"{path}: {n} bare reads exceeds its ceiling of {ceilings[path]}") + bad.append( + f"{path}: raw-handle debt count {n} exceeds its ceiling of {ceilings[path]}" + ) for path, ceiling in sorted(ceilings.items()): if path not in per_file: bad.append( @@ -209,10 +219,15 @@ def self_test(): must_match = [ "let obj = obj_h.get_raw_mut_ptr::();", "src_h.get_raw_const_ptr::()", + "h.across_mut::(|| ())", + "h.across_const::(\n || ( )\n)", + "h.across_nanbox(|| ())", + "h.across_mut::(move || {})", ] must_not_match = [ "let (found, obj) = h.across_mut::(|| f());", "h.across_const::(|| g())", + "h.across_mut::(|| { mutate(); })", "h.with_mut_ptr::(|obj| consume(obj))", "h.with_const_ptr::(|key| lookup(key))", "h.get_nanbox_f64()", @@ -310,7 +325,7 @@ def main(): prev = int(BASELINE.read_text().split()[0]) if BASELINE.exists() else None if prev is not None and total > prev: print(f"refusing to raise the baseline: {prev} -> {total}") - print("the ratchet only goes down; convert sites to across_*/with_* instead") + print("the ratchet only goes down; use across_* with a real call or with_* instead") return 1 BASELINE.write_text(f"{total}\n") # Rewrite the per-module ceilings too, preserving the header. Entries @@ -332,7 +347,7 @@ def main(): print(f"no baseline; run --update. current={total}") return 1 prev = int(BASELINE.read_text().split()[0]) - print(f"bare raw-handle reads: {total} (baseline {prev})") + print(f"raw-handle debt sites: {total} (baseline {prev})") # Per-module rules run FIRST and unconditionally. They are strictly more # specific than the total -- "symbol.rs exceeds its ceiling of 1" names the @@ -344,16 +359,18 @@ def main(): print(f"::error::per-module raw-handle rules: {len(module_violations)} violation(s)") for b in module_violations: print(f" {b}") - print("Use RuntimeHandle::across_{mut,const,nanbox} for a post-call") - print("reload, or with_{mut,const}_ptr for a scoped argument to a") + print("Put the allocating call inside RuntimeHandle::across_{mut,const,nanbox}") + print("for a post-call reload; empty `|| ()` closures are still debt.") + print("Use with_{mut,const}_ptr for a scoped argument to a") print("non-allocating operation / self-rooting runtime entry point.") print("See #7341 and scripts/raw_handle_debt_files.txt.") return 1 if total > prev: print(f"::error::raw-handle debt rose {prev} -> {total}") - print("Use RuntimeHandle::across_{mut,const,nanbox} for a post-call") - print("reload, or with_{mut,const}_ptr for a scoped argument to a") + print("Put the allocating call inside RuntimeHandle::across_{mut,const,nanbox}") + print("for a post-call reload; empty `|| ()` closures are still debt.") + print("Use with_{mut,const}_ptr for a scoped argument to a") print("non-allocating operation / self-rooting runtime entry point.") print("See #7341.") for path, n in sorted(per_file.items(), key=lambda kv: -kv[1])[:10]: