You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #289 (issue #277 Phase B) put every FFI entry point behind one generation snapshot, made a retired image keep its identity, and added scripts/lint_generation_snapshot.sh so a new entry point cannot regress. Five rounds of review closed everything that could corrupt memory or lose an error. What follows is the remainder: real, but each one needs a design decision or a build-shape change rather than another patch, and none of them is a regression introduced by #289.
1. A generic struct's constructor and destructor are two artifacts
Each generic instantiation is monomorphized into its own cdylib, so Boxed<i32>::new and Boxed<i32>_free are normally two different images. The object is therefore allocated by one image and freed through another, which crosses an allocator boundary — the contract docs/src/panics.md sets out for every other path.
#289 made this as safe as it can be without changing the build shape: the constructor resolves the destructor in the same step that allocates, preferring its own image and otherwise capturing the flag of the image the destructor lives in, so the liveness flag always describes the image the finalizer will call into (_call_generic_constructor, generic_struct_generation_snapshot). What it does not do is put them in one image.
The fix: give a generic struct's instantiation one artifact identity — one specialization library per (struct, type arguments), exporting the constructor, the methods and <Struct>_free together — so the allocation and the free are the same allocator, and one flag covers both by construction.
Also missing: reload-stress coverage for generic structs. test/test_hot_reload_transaction.jl hammers plain calls, panics, crate-module reads and non-generic constructors against a reload loop; generic structs are covered only deterministically (test/test_generic_struct.jl), because a generic instantiation compiles a fresh cdylib per type argument and a reload loop over that is minutes of rustc, not seconds. Worth doing behind a slow-test flag once the artifact question above is settled.
2. build.rs inputs cannot be enumerated
_crate_build_cfg_text memoizes the #[cfg] probe on the crate's Cargo.toml, build.rs and .cargo/config.toml chain. A build script can emit a different cargo::rustc-cfg from things no digest sees: an environment variable, a generated file, a sibling crate, the clock.
#289's answer is to make a hot reload re-probe unconditionally (memo = false) and keep the memo for first loads, where the crate has not been built under this process before. That is correct but conservative in one direction and permissive in the other: a first load in a long session can still be answered from a stale memo if the caller rebuilds the crate by other means.
The fix: either take the cfg from the build itself (a --message-format=json pass records the build-script-executedcfgs) rather than a second cargo rustc --print cfg, or drop the memo entirely and measure whether the probe cost matters.
3. Closing is still explicit
Replaced and unloaded images are retired, not closed — a call may be inside one and there is no per-call reader pin. The user reclaims them with unload_library(name; close = true) or close_retired_handles!. So a long-lived process that reloads many times accumulates mapped images until it says otherwise.
The fix, if it is worth it: an epoch or a hazard-pointer scheme that can prove no call is inside a retired image and close it automatically. That is two atomics on every FFI call, which is why #289 did not take it; the trade is worth measuring before choosing.
Generated field accessors and destructors have no panic boundary. They are not emitted by generate_wrapper, so a panic in a user Drop impl still aborts. Documented in docs/src/panics.md.
Cargo-path specialization of dependency-backed generics stays fail-closed (body_has_cfg): a lazy specialization is a direct rustc build, and reproducing the Cargo build's configuration for it is its own change.
PR #289 (issue #277 Phase B) put every FFI entry point behind one generation snapshot, made a retired image keep its identity, and added
scripts/lint_generation_snapshot.shso a new entry point cannot regress. Five rounds of review closed everything that could corrupt memory or lose an error. What follows is the remainder: real, but each one needs a design decision or a build-shape change rather than another patch, and none of them is a regression introduced by #289.1. A generic struct's constructor and destructor are two artifacts
Each generic instantiation is monomorphized into its own
cdylib, soBoxed<i32>::newandBoxed<i32>_freeare normally two different images. The object is therefore allocated by one image and freed through another, which crosses an allocator boundary — the contractdocs/src/panics.mdsets out for every other path.#289 made this as safe as it can be without changing the build shape: the constructor resolves the destructor in the same step that allocates, preferring its own image and otherwise capturing the flag of the image the destructor lives in, so the liveness flag always describes the image the finalizer will call into (
_call_generic_constructor,generic_struct_generation_snapshot). What it does not do is put them in one image.The fix: give a generic struct's instantiation one artifact identity — one specialization library per
(struct, type arguments), exporting the constructor, the methods and<Struct>_freetogether — so the allocation and the free are the same allocator, and one flag covers both by construction.Also missing: reload-stress coverage for generic structs.
test/test_hot_reload_transaction.jlhammers plain calls, panics, crate-module reads and non-generic constructors against a reload loop; generic structs are covered only deterministically (test/test_generic_struct.jl), because a generic instantiation compiles a freshcdylibper type argument and a reload loop over that is minutes ofrustc, not seconds. Worth doing behind a slow-test flag once the artifact question above is settled.2.
build.rsinputs cannot be enumerated_crate_build_cfg_textmemoizes the#[cfg]probe on the crate'sCargo.toml,build.rsand.cargo/config.tomlchain. A build script can emit a differentcargo::rustc-cfgfrom things no digest sees: an environment variable, a generated file, a sibling crate, the clock.#289's answer is to make a hot reload re-probe unconditionally (
memo = false) and keep the memo for first loads, where the crate has not been built under this process before. That is correct but conservative in one direction and permissive in the other: a first load in a long session can still be answered from a stale memo if the caller rebuilds the crate by other means.The fix: either take the cfg from the build itself (a
--message-format=jsonpass records thebuild-script-executedcfgs) rather than a secondcargo rustc --print cfg, or drop the memo entirely and measure whether the probe cost matters.3. Closing is still explicit
Replaced and unloaded images are retired, not closed — a call may be inside one and there is no per-call reader pin. The user reclaims them with
unload_library(name; close = true)orclose_retired_handles!. So a long-lived process that reloads many times accumulates mapped images until it says otherwise.The fix, if it is worth it: an epoch or a hazard-pointer scheme that can prove no call is inside a retired image and close it automatically. That is two atomics on every FFI call, which is why #289 did not take it; the trade is worth measuring before choosing.
4. Smaller items
@rust_llvmkeeps its owndlopenand is the single allowlist entry inscripts/lint_load_path.shandscripts/lint_generation_snapshot.sh. Deprecate @rust_llvm and the LLVM IR integration path (rustc/Julia LLVM version mismatch) #265 Phase 2 removes the path; until then it is outside both rules.generate_wrapper, so a panic in a userDropimpl still aborts. Documented indocs/src/panics.md.body_has_cfg): a lazy specialization is a directrustcbuild, and reproducing the Cargo build's configuration for it is its own change.Context: #277, and the review history on #289.