Skip to content

Commit 05be09f

Browse files
committed
ZJIT: Keep the profiled shape reachable through guards and dispatch arms
[zjit/min port note] Hand-ported slice of fcb775d ("Specialize `&block` forwarding sends and keep profiled shapes"), af5bc8f ("Keep the profiled shape inside polymorphic dispatch arms") and 7ce7db7 ("Don't re-speculate on the receiver in a type-dispatch fallthrough"), which together are what makes zjit/all's getivar_fallback_no_profile_missing_ic 338,599 where this branch's is 5,204,190. `Type` records a class but not a shape, so once a receiver was guarded or refined to a profiled type, later specializations of the same value could no longer see its shape. Three leaks followed, all ending in a full `rb_ivar_get` call for what should be a direct field load: * Inlined callees. An ISEQ only starts profiling once the interpreter has entered it, so a method reached only from JIT code has no profile of its own; its `self` is the caller's guarded receiver, and the guard was the last place the shape existed. `Function::guarded_profiled_types` now remembers the profiled type behind each guard, and `resolve_receiver_type` prefers it over the static class. * Polymorphic dispatch arms. Each arm refined the receiver to a class and dropped its profile entry outright. The arm now carries the profiled type forward, both as the Snapshot's receiver entry and via `record_profiled_type`. * `opt_send_without_block`, the hottest send opcode, went through a near-clone of `emit_polymorphic_send` (`gen_send_chain`) that did none of this. Deleted, and the opcode routed through `emit_polymorphic_send` like upstream does. Adaptations: `recorded_profiled_type` keeps `union_find.borrow()` because min still has the RefCell around the union-find. Upstream tags an arm's shape with `ProfiledType::as_polymorphic_arm()` and has consumers branch on it rather than guard it (`ShapeMiss`, from 98f1b7a and 2f92871, not ported here); without that machinery an arm hands its shape over only when every bucket profiled for that class agrees on it, so the shape is a real prediction and a consumer may guard it with the same confidence as at a monomorphic site. Rerouting `opt_send_without_block` also required 7ce7db7's fallthrough fix: `copy_entries_except` now takes an optional replacement summary for the receiver, and the fallthrough substitutes a megamorphic one. `gen_send_chain` reused the original Snapshot, so dropping the entry without a replacement would have turned its fallthrough into a `NoProfileSend` side exit instead of the dynamic send it has to be. That also fixes the same latent re-speculation on min's `YARVINSN_send` path, whose fallthrough already took a fresh Snapshot but passed no summary.
1 parent ab05167 commit 05be09f

3 files changed

Lines changed: 378 additions & 332 deletions

File tree

zjit/src/distribution.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,24 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
267267
Self { kind: DistributionKind::Empty, buckets: [Default::default(); N], counts: [0; N], other: 0 }
268268
}
269269

270+
/// Build a summary that claims too many types were seen to be worth specializing. Used for
271+
/// the fallthrough of a type-dispatch chain: every profiled type already has its own branch,
272+
/// so whatever reaches the fallthrough is by construction a type the profile never saw.
273+
pub fn megamorphic() -> Self {
274+
Self { kind: DistributionKind::Megamorphic, buckets: [Default::default(); N], counts: [0; N], other: NumProfiles::MAX }
275+
}
276+
277+
/// Build a summary that claims exactly one type was seen. Used when a polymorphic site has
278+
/// already been split into per-type branches: within a branch, the receiver is known to have
279+
/// one specific profiled type, so downstream specialization can treat it as monomorphic.
280+
pub fn monomorphic(profiled_type: T) -> Self {
281+
let mut buckets = [Default::default(); N];
282+
buckets[0] = profiled_type;
283+
let mut counts = [0; N];
284+
counts[0] = 1;
285+
Self { kind: DistributionKind::Monomorphic, buckets, counts, other: 0 }
286+
}
287+
270288
pub fn new(dist: &Distribution<T, N>) -> Self {
271289
#[cfg(debug_assertions)]
272290
{

0 commit comments

Comments
 (0)