Refuse a lift that never filled its layout's fields

This commit is contained in:
Dennis Kobert
2026-09-07 15:56:28 +00:00
parent 77e4e8c894
commit f4f266b1ba
2 changed files with 29 additions and 12 deletions

View File

@@ -111,6 +111,7 @@ impl<'e> Frames<'e> {
inline: RecordValue::zeroed(),
frame,
free: self.reborrow(),
filled_fields: false,
}
}

View File

@@ -46,6 +46,7 @@ impl<'a> SlotRun<'a> {
inline: RecordValue::zeroed(),
frame: (self.layout.frame_bytes() != 0).then_some(frame),
free: frames.reborrow(),
filled_fields: false,
}
}
@@ -82,6 +83,9 @@ pub struct FrameClaim<'e, 'l> {
pub(in crate::record) inline: RecordValue<'static>,
pub(in crate::record) frame: Option<*mut u8>,
pub(in crate::record) free: Frames<'e>,
/// Set by the writes that fill the declared fields, so the safe closers can
/// refuse a field-bearing frame that was never filled.
pub(in crate::record) filled_fields: bool,
}
impl<'e, 'l> FrameClaim<'e, 'l> {
@@ -118,6 +122,7 @@ impl<'e, 'l> FrameClaim<'e, 'l> {
/// must be the wiring-resolved plan of this frame's layout.
pub unsafe fn carry(&mut self, src: Rec<'_>, plan: &[(usize, usize, usize)]) {
unsafe { apply_plan(src, self.dst(), plan) };
self.filled_fields = true;
}
/// Writes a field at its wiring-resolved offset.
@@ -126,6 +131,7 @@ impl<'e, 'l> FrameClaim<'e, 'l> {
/// `offset` must be this layout's resolved offset for a field of `T`.
pub unsafe fn attr_at<T>(&mut self, offset: usize, value: T) {
unsafe { write_field(self.dst(), offset, value) };
self.filled_fields = true;
}
/// Writes the element; `None` reports arena exhaustion for a parked
@@ -140,9 +146,17 @@ impl<'e, 'l> FrameClaim<'e, 'l> {
/// Lifts a kernel's poll into the frame and closes it: the element
/// writes on value polls, every poll keeps the frame claimed, and arena
/// exhaustion of a parked element reports as an error poll. Panics where
/// the element does not match the wired layout.
/// the element does not match the wired layout, or where a field-bearing
/// layout closes on a frame no carry or field write ever filled, since the
/// [`Served`] proof certifies the whole record and the fields would be the
/// prior frame's bytes.
pub fn lift<T: Send + Sync + dyn_any::StaticTypeSized>(mut self, poll: GPoll<T>, arena: &'e crate::arena::Arena) -> GPoll<RecordValue<'e>> {
self.check_element::<T>();
assert!(
self.layout.fields.is_empty() || self.filled_fields,
"a layout with {} fields must carry or write them before lifting",
self.layout.fields.len()
);
let frame_bytes = self.layout.frame_bytes();
let dst = self.dst();
// SAFETY: the frame is this layout's fresh claim, the element check
@@ -158,6 +172,7 @@ impl<'e, 'l> FrameClaim<'e, 'l> {
/// references outlive the serving evaluation.
pub unsafe fn fill_copy(&mut self, src: *const u8) {
unsafe { std::ptr::copy_nonoverlapping(src, self.dst(), self.layout.size) };
self.filled_fields = true;
}
/// The served record. The frame stays claimed for the consumer; the drop
@@ -208,6 +223,7 @@ impl<'e, 'l> FrameClaim<'e, 'l> {
/// translate into this frame's layout.
pub unsafe fn translate(&mut self, src: Rec<'_>, plan: &SourcePlan) {
unsafe { plan.translate(src, self.dst()) };
self.filled_fields = true;
}
}
@@ -320,6 +336,17 @@ mod tests {
frames.claim(&layout).element(1u32, &arena);
}
#[test]
fn a_run_refuses_a_lane_count_whose_stride_product_overflows() {
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[]);
let mut scratch = [std::mem::MaybeUninit::<u64>::uninit(); 4];
let mut frame_arena = FrameArena::new();
frame_arena.reserve(64);
let frames = frame_arena.frames();
let wrapping = usize::MAX / layout.lane_stride() + 1;
assert!(frames.run(&mut scratch, wrapping, &layout).is_none(), "a wrapped capacity product must not pass the check");
}
#[test]
#[should_panic(expected = "serves out of order")]
fn a_run_refuses_a_gapped_serve() {
@@ -338,17 +365,6 @@ mod tests {
run.served(2, &proof);
}
#[test]
fn a_run_refuses_a_lane_count_whose_stride_product_overflows() {
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[]);
let mut scratch = [std::mem::MaybeUninit::<u64>::uninit(); 4];
let mut frame_arena = FrameArena::new();
frame_arena.reserve(64);
let frames = frame_arena.frames();
let wrapping = usize::MAX / layout.lane_stride() + 1;
assert!(frames.run(&mut scratch, wrapping, &layout).is_none(), "a wrapped capacity product must not pass the check");
}
#[test]
fn a_claim_shortens_onto_a_derived_lifetime() {
fn shorten<'long: 'short, 'short, 'l>(claim: FrameClaim<'long, 'l>) -> FrameClaim<'short, 'l> {