Add support for volatile_copy_memory, volatile_copy_nonoverlapping_memory and volatile_set_memory#4672
Add support for volatile_copy_memory, volatile_copy_nonoverlapping_memory and volatile_set_memory#4672ivmat wants to merge 3 commits into
volatile_copy_memory, volatile_copy_nonoverlapping_memory and volatile_set_memory#4672Conversation
…mory and volatile_set_memory Implements three of the intrinsics still unchecked on tracking issue model-checking#1163. Why the existing placeholders could not simply be un-gated ---------------------------------------------------------- The two copy intrinsics already had arms, but behind `unstable_codegen!`: Intrinsic::VolatileCopyMemory => unstable_codegen!(codegen_intrinsic_copy!(Memmove)), Intrinsic::VolatileCopyNonOverlappingMemory => { unstable_codegen!(codegen_intrinsic_copy!(Memcpy)) } `unstable_codegen!` is declared `($($tt:tt)*)` and never expands its tokens -- it unconditionally emits a codegen_unimplemented_expr. Those bodies were therefore never type-checked, and they have since rotted: codegen_intrinsic_copy! no longer exists anywhere in kani-compiler. Removing the gate alone does not compile, so both arms are rewritten rather than un-gated. `volatile_set_memory` had no Intrinsic variant at all and fell through to the unsupported catch-all. Argument order -------------- The intrinsics take the destination first: volatile_copy_memory<T>(dst: *mut T, src: *const T, count: usize) volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T, count: usize) whereas codegen_copy consumes source first (two successive fargs.remove(0), src then dst) and indexes farg_types[0]/farg_types[1] for the src and dst alignment assertions. Both fargs and farg_types are therefore swapped before delegating. Swapping only one silently applies each alignment check to the wrong pointer -- a defect no test using two equally-aligned buffers would catch. volatile_set_memory(dst, val, count) matches write_bytes(dst, val, count), so no swap is needed there. Soundness of reusing the non-volatile codegen --------------------------------------------- Volatility constrains what the optimizer may do -- it must not elide, duplicate or reorder the access. It is not a memory-safety property: the UB conditions for these three intrinsics are exactly those of copy, copy_nonoverlapping and write_bytes. Kani performs no optimization that volatility is meant to suppress, so there is nothing additional to model. This mirrors the existing treatment of volatile_load/volatile_store. Points-to analysis ------------------ points_to_analysis.rs matches exhaustively over Intrinsic with a terminal unimplemented!() wildcard; write_bytes is protected from it via is_identity_aliasing_intrinsic. Before this change volatile_set_memory reached that pass as Intrinsic::Unimplemented, which is explicitly handled as a no-op. Introducing the new variant would have routed it to the panicking wildcard, so it is listed alongside write_bytes in the same way. The two check_uninit visitors have non-panicking fallbacks and are unchanged. Tests ----- Layout follows model-checking#1347, which added volatile_load support. tests/kani/Intrinsics/Volatile/copy.rs both copy intrinsics move the expected bytes, including an overlapping case for volatile_copy_memory (memmove semantics) that distinguishes it from the non-overlapping variant tests/kani/Intrinsics/Volatile/set.rs volatile_set_memory fills the destination, full and partial tests/expected/intrinsics/volatile_copy/overlapping/ volatile_copy_nonoverlapping_memory on overlapping ranges fails, matching the existing copy-nonoverlapping test for the non-volatile intrinsic tests/expected/intrinsics/volatile_copy/unaligned/ a misaligned pointer fails the alignment check The unaligned pair is deliberately left out: unaligned_volatile_load's gated body is a plain dereference, which does not model the unaligned access itself, and unaligned_volatile_store has no codegen at all. Those need a separate decision about how unaligned accesses should be represented.
`docs/src/rust-feature-support/intrinsics.md` listed all three as `No`. They are now supported, so the table has to move. `Partial` rather than `Yes`, matching the neighbouring `volatile_load` and `volatile_store` rows and keeping the same Concurrency note. The memory-safety semantics are modelled exactly (they are those of the non-volatile counterparts), but the volatile guarantees themselves -- that the access is not elided or reordered, and really does touch memory -- are not, since Kani assumes sequential execution. Claiming `Yes` would overstate that and would disagree with how the two already-supported volatile intrinsics are documented.
…s comment
Two review findings on the previous commit.
1. The compiler matches on `Intrinsic` in three places -- codegen, the points-to
analysis, and the memory-initialization visitor -- and only the first two were
updated. `volatile_set_memory` therefore fell to the visitor's catch-all and
produced a spurious "Kani does not support reasoning about memory
initialization" failure under -Z uninit-checks, while the codegen comment
claimed it behaves exactly like `write_bytes`. It now shares that arm: same
argument shape (dst, val, count), same initialization effect.
2. The comment overclaimed. "The UB conditions are exactly those of the
non-volatile counterpart" is false as a generalisation: the volatile family's
docs additionally permit pointers outside any Rust allocation (the MMIO
carve-out on ptr::read_volatile), which the non-volatile ops do not. The
modelling is still sound, but for a different reason than the comment gave --
reusing codegen_copy/codegen_write_bytes checks AT LEAST the documented UB
conditions, so it is conservative rather than exact: a legal MMIO access can
be rejected by --pointer-check, but real UB is never missed. The comments now
say that, and anchor on what these intrinsics' own docs state ("consistent
with copy_nonoverlapping" / "consistent with write_bytes") rather than on a
blanket claim about volatility.
Also adds tests/expected/intrinsics/volatile_set/out-of-bounds, mirroring the
existing write_bytes test. The happy-path test alone did not pin that reusing
codegen_write_bytes actually carries its safety checks across.
|
Pushed two follow-up commits after a review pass, and corrected one claim in the description above. The comment about volatility overclaimed. It said the UB conditions are exactly those of the non-volatile counterparts. That is not right as a generalisation: the volatile family's docs additionally permit pointers outside any Rust allocation (the MMIO carve-out documented on
Also added Full regression run locally on CBMC 6.10.0: no failures introduced (the only failures are three pre-existing |
Implements three of the intrinsics still unchecked on the tracking issue #1163:
volatile_copy_memory,volatile_copy_nonoverlapping_memoryandvolatile_set_memory.Why the existing placeholders could not simply be un-gated
The two copy intrinsics already had arms, but behind
unstable_codegen!:unstable_codegen!is declared as($($tt:tt)*)and never expands its tokens — it always emits acodegen_unimplemented_expr. The bodies above were therefore never type-checked, and they have sincerotted:
codegen_intrinsic_copy!no longer exists anywhere inkani-compiler. Removing the gate alonedoes not compile, so both arms are rewritten rather than un-gated.
volatile_set_memoryhad noIntrinsicvariant at all and fell through to the unsupported catch-all.Argument order
The Rust intrinsics take the destination first:
whereas
codegen_copyconsumes source first (let src = fargs.remove(0); let dst = fargs.remove(0);)and indexes
farg_types[0]/farg_types[1]for thesrcanddstalignment checks respectively.Both
fargsandfarg_typesare therefore swapped before delegating. Without swapping both, the twoalignment assertions are silently applied to the wrong pointers — a discrepancy that a test using two
equally-aligned buffers would not detect.
volatile_set_memory(dst, val, count)matcheswrite_bytes(dst, val, count), so no swap is needed there.Soundness of reusing the non-volatile codegen
Volatility constrains what the optimizer may do — it must not elide, duplicate or reorder the access.
It is not a memory-safety property: the UB conditions for these three intrinsics are exactly those of
copy,copy_nonoverlappingandwrite_bytes. Kani's codegen performs no optimization that volatilityis meant to suppress, so there is nothing additional to model. This mirrors the existing treatment of
volatile_load/volatile_store, which already delegate to their non-volatile counterparts.Points-to analysis
Adding the
VolatileSetMemoryvariant required one further line.points_to_analysis.rsmatchesexhaustively over
Intrinsicwith a finalunimplemented!()wildcard;WriteBytesis protected from itby being listed in
is_identity_aliasing_intrinsic. Before this changevolatile_set_memoryreached theanalysis as
Intrinsic::Unimplemented { .. }, which is explicitly handled as a no-op. Introducing the newvariant would have routed it to the panicking wildcard instead, so it is listed alongside
WriteBytesinthe same way.
The other two visitors that match on
Intrinsic(
check_uninit/ptr_uninit/uninit_visitor.rs,check_uninit/delayed_ub/initial_target_visitor.rs) havenon-panicking fallbacks, so behaviour there is unchanged.
Tests
tests/kani/Intrinsics/Volatile/copy.rs— both copy intrinsics move the expected bytes, including anoverlapping case for
volatile_copy_memory(memmove semantics) to distinguish it from thenon-overlapping variant.
tests/kani/Intrinsics/Volatile/set.rs—volatile_set_memoryfills the destination, full and partial.tests/expected/intrinsics/volatile_copy/overlapping/—volatile_copy_nonoverlapping_memoryonoverlapping ranges fails, matching the existing
tests/expected/intrinsics/copy-nonoverlapping/copy-overlapping/test for the non-volatile intrinsic.tests/expected/intrinsics/volatile_copy/unaligned/— a misaligned pointer fails the alignment check.Test layout follows #1347, which added
volatile_loadsupport.Related
Towards #1163. The remaining unchecked volatile entries —
unaligned_volatile_loadandunaligned_volatile_store— are deliberately left out of this PR: the gated body forunaligned_volatile_loadis a plain dereference, which does not model the unaligned access itself, sothose two need a separate decision about how unaligned accesses should be represented.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0
and MIT licenses.