-
Notifications
You must be signed in to change notification settings - Fork 142
Description
The following document was produced by a coding agent after attempting to autonomously author a specification and accompanying proof for the ptr_concat example. The code authored by the agent is at the bottom of this issue.
Reflections on Verifying concat_slices in Hermes
What was difficult?
- Understanding Aeneas WP Evaluation Unrolling: Constructing the proofs, particularly
h_progress, required manually stepping through the Weakest Precondition (WP) evaluation generated by Aeneas. It was initially non-obvious that each statement in the Rust function corresponds to abindoperation that must be individually resolved usingAeneas.Std.WP.spec_imp_existsandAeneas.Std.bind_tc_okto extract the resulting value, the evaluation step, and the postcondition bundle. - Lean 4 Macro Hygiene: When attempting to DRY (Don't Repeat Yourself) the repetitive WP unwrapping code across the five proof blocks (
h_ret_alloc,h_ret_address,h_ret_metadata,h_ret_addresses, andh_progress), I ran into strict Lean 4 macro hygiene rules. Variables defined inside amacro(likehave ⟨a_len, h_eval_a, h_post_a⟩ := ...) are hygienic by default, making them inaccessible by name in the trailing tactics outside the macro expansion. This entirely blocked the macro-based refactoring approach. - Strict Linter Constraints (
linter.unusedSimpArgs): Hermes runs Lean with strict linters enabled, converting warnings into hard verification errors. The unusedsimparguments linter was surprisingly strict, punishing the use of broadly applicablesimp_all [h_post_add, h_post_a.h_unnamed, ...]if a specific branch happened to not require one of the provided theorems. This necessitated carefully curating the variables provided tosimpor relying purely on implicit local context state. - Hermes Syntax and Rustdoc Coalescing: Trying to define a global
context:macro block was tricky because Rustdoc merges adjacent///comments. Attaching thecontext:block to a dummystructfailed because Hermes expects a type invariant (isValid) for struct-based specs. Resolving this required discovering the dummyfnworkaround, which was non-intuitive.
What I wish I had known going in
- The exact structure of Aeneas WP monads: A clearer upfront picture of how Aeneas translates let-bindings into monadic
bindsequences, and the exact incantations required to peel them back (WP.spec_imp_exists+bind_tc_ok), would have drastically reduced the time spent reverse-engineering the proof state. - The rigidity of Lean's local namespaces (Hygiene): Knowing that declarative macros effectively hide their bindings would have steered me away from the
macrorefactor attempt much earlier. I would have instead immediately pursued the native "re-inject fields into unbound context" approach, saving significant debugging effort. - The strictness of
simp_all: I wish I had known about theunusedSimpArgslinter from the start. I would have avoided the pattern of indiscriminately throwing available hypotheses intosimp_all [...]brackets and instead relied onhave _ := ...to inject variables into the local context for implicit discovery. - The relationship between
IsContiguousandbounds: Realizing earlier thath_contiguousallows seamless mapping betweenx ∈ referent.addressesand simple arithmetic bounds queries (a.address.val ≤ x < a.address.val + a.size.val) viareferent.bounds x hxcompletely trivialized theh_ret_allocandh_ret_addressessubset/union set logic.
Proposed Hermes Design Improvements
Based on the friction encountered during the verification of concat_slices, the following changes to Hermes's design and tooling are proposed:
1. Dedicated WP Unrolling Magic/Tactic
Currently, users are forced to manually apply Aeneas.Std.WP.spec_imp_exists followed by Aeneas.Std.bind_tc_ok for every single line of a Rust function to evaluate the monadic WP state.
- Improvement: Introduce a high-level Lean tactic strictly for Hermes (e.g.,
eval_nextorstep_wp) that automatically peels off the next sequentialbindoperation in the current WP goal, intelligently unifies the returnedokvalue, and dynamically injects thepoststruct fields directly into the local context. This would entirely eliminate the massivehave ⟨val, eval, post⟩ := ...boilerplate that clutters every single proof in Hermes.
2. Relaxing/Contextualizing Linter Strictness
The linter.unusedSimpArgs error is incredibly antagonistic when trying to build reusable proof patterns across different branches identically (e.g., using simp_all generically).
- Improvement: Expose an
#[allow(unused_simp)]attribute equivalent in the Hermes doc blocks to locally suppress unused simplification warnings on a per-proof or per-function basis without disabling it globally. Alternatively, Hermes could automatically injectset_option linter.unusedSimpArgs falsewhen translating the monolithicsimp_allblocks during generation.
3. File-Level Declarations
Defining shared types, lemmas, and macros meant for an entire file (like eval_concat_slices) currently requires the hack of attaching them to a dummy fn _hermes_host() {} since attaching to structs requires an isValid invariant, and standalone /// doc comments are merged by the Rust compiler.
- Improvement: Allow a
#![hermes::context]inner attribute syntax at the very top of a Rust file to define global Lean definitions unconditionally.
4. Structuring WP Postcondition Tuples
Presently, WP postconditions (post_a.h_unnamed) hide tightly inside structs with generated generic names.
- Improvement: When Aeneas drops variable execution bindings into Lean, Hermes could intercept the post conditions and unpack the structural fields (like
ret_sizeorret_addr) into immediate local context definitions with predictable names derived from the original Rust variables (e.g.,h_post_a_unnamed). This would prevent developers from having to manually un-nesthave _h_size := h_post.h_sizejust to appease theomegasolver.
Proposed Features for Hermes
To make verifications like concat_slices significantly easier next time, here are proposed feature additions for the Hermes tool and standard library:
1. Pointer and Slice Set Theory Helpers (Standard Library)
Proving the addresses subset and union logic manually via x < a.address.val + a.size.val is tedious.
- Feature Addition: Add a module to the standard library (e.g.,
Hermes.Std.SetLogic) that provides high-level theorems for mappingIsContiguousarray arithmetic into native Set (∪,∩,⊂) operations. For example, a lemmacontiguous_union_adjacentthat automatically proves that two adjacent, contiguous referents form a union of addresses that matches the concatenated bounds.
2. Automated WP Progress Solver Tactic (Standard Library)
The manual unrolling of Aeneas.Std.WP.spec_imp_exists dominates the file.
- Feature Addition: Provide a powerful
wp_unrollorstep_wpmacro/tactic in the standard library. This tactic would automatically peel off the next Weakest Preconditionbind, resolve theokstate viaAeneas.Std.bind_tc_ok, and advance the proof state recursively until the function returns.
3. Tactic for Unpacking Post-Condition Structs (Standard Library)
Hygiene and nested bounds properties make variables invisible to simp_all and omega.
- Feature Addition: Add an
unpack_struct h_postLean tactic to the standard library that uses reflection to automatically generatehave _val := h_post.field_namebindings in the local, unbound context for every field inside a postcondition struct. This eliminates the manual injection boilerplate completely.
4. Auto-generation of h_progress (Tool Feature)
For simple sequence blocks like concat_slices that don't entail loops or recursive calls, the h_progress proof is entirely mechanical (just stepping through non-panicking mathematical additions and valid pointer casts).
- Current Limitation: Hermes's auto-generated
h_progresspreviously relied solely onexact ⟨_, rfl⟩. We improved this by implementing a generic macro (eval_progress) that usesall_goals (try ...)andprogresswithomegafallbacks, along with emitting@[progress]on all generated specs. This robust automation successfully handles simple bounds and nested functions (like intoy_progress.rs). However, for complex functions likeconcat_slices, the mathematical bounds (like array lengths extracted fromslice_len) are obfuscated inside generatedPoststructs. The genericprogresssolver cannot automatically associate these opaquePostbounds with the top-level structuralPrerequirements, meaning manual proofs of progress remain necessary for any function involving non-trivial precondition threading. - Feature Addition: To fully automate
h_progressfor complex pointer arithmetic, Hermes would need to automatically insert the structural preconditions and postconditions of each inner call directly into the active solver context, or provide a tactic to explicitly unpackPostconstraints before invoking math solvers likeomega.
Code written by agent for `ptr_concat` example
//! This example exists as a test for agents to see how well they can
//! automatically write and prove Hermes specifications.
/// ```lean, hermes, unsafe(axiom)
/// ensures: ret = Hermes.HasMetadata.metadata data
/// ```
pub const unsafe fn slice_len(data: *const [u8]) -> usize {
data.len()
}
/// ```lean, hermes, unsafe(axiom)
/// ensures: (Hermes.HasReferent.referent ret).address = (Hermes.HasReferent.referent data).address
/// ```
pub const unsafe fn cast_slice_to_u8(data: *const [u8]) -> *const u8 {
data.cast::<u8>()
}
/// ```lean, hermes, unsafe(axiom)
/// requires (h_isize): len.val ≤ Isize.max
/// ensures (h_addr): (Hermes.HasReferent.referent ret).address = (Hermes.HasReferent.referent data).address
/// ensures (h_len): Hermes.HasMetadata.metadata ret = len
/// ensures (h_size): (Hermes.HasReferent.referent ret).size = len
/// ensures (h_contiguous): (Hermes.HasReferent.referent ret).IsContiguous
/// ```
pub const unsafe fn slice_from_slice_parts(data: *const u8, len: usize) -> *const [u8] {
core::ptr::slice_from_raw_parts(data, len)
}
/// TODO:
/// - Require that both referents live in the same allocation
/// - Require that both referents are contiguous and non-overlapping
/// - Ensure that the returned referent is the concatenation of the two input referents
/// - Ensure that the returned referent lives in the same allocation as the input referents
///
/// ```lean, hermes, spec
/// requires (h_alloc):
/// ∃ alloc, Hermes.FitsInAllocation (Hermes.HasReferent.referent a) alloc ∧ Hermes.FitsInAllocation (Hermes.HasReferent.referent b) alloc
/// requires (h_contiguous_a):
/// (Hermes.HasReferent.referent a).IsContiguous
/// requires (h_contiguous_b):
/// (Hermes.HasReferent.referent b).IsContiguous
/// requires (h_adjacent):
/// (Hermes.HasReferent.referent b).address.val = (Hermes.HasReferent.referent a).address.val + (Hermes.HasReferent.referent a).size.val
/// requires (h_size_a):
/// (Hermes.HasReferent.referent a).size.val = (Hermes.HasMetadata.metadata a).val
/// requires (h_size_b):
/// (Hermes.HasReferent.referent b).size.val = (Hermes.HasMetadata.metadata b).val
/// requires (h_meta_bounds_isize):
/// (Hermes.HasMetadata.metadata a).val + (Hermes.HasMetadata.metadata b).val <= Isize.max
/// requires (h_meta_bounds_usize):
/// (Hermes.HasMetadata.metadata a).val + (Hermes.HasMetadata.metadata b).val <= Aeneas.Std.Usize.max
/// ensures (h_ret_alloc):
/// ∃ alloc, Hermes.FitsInAllocation (Hermes.HasReferent.referent ret) alloc
/// ensures (h_ret_address):
/// (Hermes.HasReferent.referent ret).address = (Hermes.HasReferent.referent a).address
/// ensures (h_ret_metadata):
/// (Hermes.HasMetadata.metadata ret).val = (Hermes.HasMetadata.metadata a).val + (Hermes.HasMetadata.metadata b).val
/// ensures (h_ret_addresses):
/// (Hermes.HasReferent.referent ret).addresses = (Hermes.HasReferent.referent a).addresses ∪ (Hermes.HasReferent.referent b).addresses
/// proof (h_ret_alloc):
/// rcases h_alloc with ⟨alloc, h_alloc_a, h_alloc_b⟩
/// unfold concat_slices at h_returns
/// have ⟨a_len, h_eval_a, h_post_a⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_a, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨b_len, h_eval_b, h_post_b⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec b { h_data_is_valid := h_b_is_valid })
/// simp [h_eval_b, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_add : a_len.val + b_len.val ≤ Aeneas.Std.Usize.max := by
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨total_len, h_eval_add, h_post_add⟩ := Aeneas.Std.WP.spec_imp_exists (Aeneas.Std.Usize.add_spec h_add)
/// simp [h_eval_add, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨ptr, h_eval_ptr, h_post_ptr⟩ := Aeneas.Std.WP.spec_imp_exists (cast_slice_to_u8.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_ptr, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_ptr_valid : Hermes.IsValid.isValid ptr := h_post_ptr.h_ret_is_valid
/// have h_total_len_valid : Hermes.IsValid.isValid total_len := by simp_all [Hermes.IsValid.isValid]
/// have h_total_len_isize : total_len.val ≤ Isize.max := by simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨ret_slice, h_eval_ret, h_post_ret⟩ := Aeneas.Std.WP.spec_imp_exists (slice_from_slice_parts.spec ptr total_len { h_data_is_valid := h_ptr_valid, h_len_is_valid := h_total_len_valid, h_isize := h_total_len_isize })
/// simp [h_eval_ret] at h_returns
/// have _h_a_len := h_post_a.h_unnamed
/// have _h_b_len := h_post_b.h_unnamed
/// have _h_ptr_addr := h_post_ptr.h_unnamed
/// have _h_ret_addr := h_post_ret.h_addr
/// have _h_ret_len := h_post_ret.h_len
/// have _h_ret_size := h_post_ret.h_size
/// have _h_ret_contiguous := h_post_ret.h_contiguous
/// have h_ret_eq : ret = ret_slice := by simp_all
/// subst h_ret_eq
/// exists alloc
/// rcases h_alloc_a with ⟨h_alloc_a_subset, h_alloc_a_base, h_alloc_a_end⟩
/// rcases h_alloc_b with ⟨h_alloc_b_subset, h_alloc_b_base, h_alloc_b_end⟩
/// refine ⟨?subset, ?base, ?end_bound⟩
/// case subset =>
/// intro x hx
/// have h_bound := (Hermes.HasReferent.referent ret).bounds x hx
/// by_cases h : x < (Hermes.HasReferent.referent a).address.val + (Hermes.HasReferent.referent a).size.val
/// · have hx_in_a : x ∈ (Hermes.HasReferent.referent a).addresses := by
/// apply h_contiguous_a
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// exact h_alloc_a_subset hx_in_a
/// · have hx_in_b : x ∈ (Hermes.HasReferent.referent b).addresses := by
/// apply h_contiguous_b
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// omega
/// exact h_alloc_b_subset hx_in_b
/// case base =>
/// simp_all
/// case end_bound =>
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]; omega
/// proof (h_ret_address):
/// unfold concat_slices at h_returns
/// have ⟨a_len, h_eval_a, h_post_a⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_a, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨b_len, h_eval_b, h_post_b⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec b { h_data_is_valid := h_b_is_valid })
/// simp [h_eval_b, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_add : a_len.val + b_len.val ≤ Aeneas.Std.Usize.max := by
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨total_len, h_eval_add, h_post_add⟩ := Aeneas.Std.WP.spec_imp_exists (Aeneas.Std.Usize.add_spec h_add)
/// simp [h_eval_add, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨ptr, h_eval_ptr, h_post_ptr⟩ := Aeneas.Std.WP.spec_imp_exists (cast_slice_to_u8.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_ptr, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_ptr_valid : Hermes.IsValid.isValid ptr := h_post_ptr.h_ret_is_valid
/// have h_total_len_valid : Hermes.IsValid.isValid total_len := by simp_all [Hermes.IsValid.isValid]
/// have h_total_len_isize : total_len.val ≤ Isize.max := by simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨ret_slice, h_eval_ret, h_post_ret⟩ := Aeneas.Std.WP.spec_imp_exists (slice_from_slice_parts.spec ptr total_len { h_data_is_valid := h_ptr_valid, h_len_is_valid := h_total_len_valid, h_isize := h_total_len_isize })
/// simp [h_eval_ret] at h_returns
/// have _h_a_len := h_post_a.h_unnamed
/// have _h_b_len := h_post_b.h_unnamed
/// have _h_ptr_addr := h_post_ptr.h_unnamed
/// have _h_ret_addr := h_post_ret.h_addr
/// have _h_ret_len := h_post_ret.h_len
/// have _h_ret_size := h_post_ret.h_size
/// have _h_ret_contiguous := h_post_ret.h_contiguous
/// have h_ret_eq : ret = ret_slice := by simp_all
/// subst h_ret_eq
/// simp_all
/// proof (h_ret_metadata):
/// unfold concat_slices at h_returns
/// have ⟨a_len, h_eval_a, h_post_a⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_a, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨b_len, h_eval_b, h_post_b⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec b { h_data_is_valid := h_b_is_valid })
/// simp [h_eval_b, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_add : a_len.val + b_len.val ≤ Aeneas.Std.Usize.max := by
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨total_len, h_eval_add, h_post_add⟩ := Aeneas.Std.WP.spec_imp_exists (Aeneas.Std.Usize.add_spec h_add)
/// simp [h_eval_add, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨ptr, h_eval_ptr, h_post_ptr⟩ := Aeneas.Std.WP.spec_imp_exists (cast_slice_to_u8.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_ptr, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_ptr_valid : Hermes.IsValid.isValid ptr := h_post_ptr.h_ret_is_valid
/// have h_total_len_valid : Hermes.IsValid.isValid total_len := by simp_all [Hermes.IsValid.isValid]
/// have h_total_len_isize : total_len.val ≤ Isize.max := by simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨ret_slice, h_eval_ret, h_post_ret⟩ := Aeneas.Std.WP.spec_imp_exists (slice_from_slice_parts.spec ptr total_len { h_data_is_valid := h_ptr_valid, h_len_is_valid := h_total_len_valid, h_isize := h_total_len_isize })
/// simp [h_eval_ret] at h_returns
/// have _h_a_len := h_post_a.h_unnamed
/// have _h_b_len := h_post_b.h_unnamed
/// have _h_ptr_addr := h_post_ptr.h_unnamed
/// have _h_ret_addr := h_post_ret.h_addr
/// have _h_ret_len := h_post_ret.h_len
/// have _h_ret_size := h_post_ret.h_size
/// have _h_ret_contiguous := h_post_ret.h_contiguous
/// have h_ret_eq : ret = ret_slice := by simp_all
/// subst h_ret_eq
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// proof (h_ret_addresses):
/// unfold concat_slices at h_returns
/// have ⟨a_len, h_eval_a, h_post_a⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_a, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨b_len, h_eval_b, h_post_b⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec b { h_data_is_valid := h_b_is_valid })
/// simp [h_eval_b, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_add : a_len.val + b_len.val ≤ Aeneas.Std.Usize.max := by
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨total_len, h_eval_add, h_post_add⟩ := Aeneas.Std.WP.spec_imp_exists (Aeneas.Std.Usize.add_spec h_add)
/// simp [h_eval_add, Aeneas.Std.bind_tc_ok] at h_returns
/// have ⟨ptr, h_eval_ptr, h_post_ptr⟩ := Aeneas.Std.WP.spec_imp_exists (cast_slice_to_u8.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_ptr, Aeneas.Std.bind_tc_ok] at h_returns
/// have h_ptr_valid : Hermes.IsValid.isValid ptr := h_post_ptr.h_ret_is_valid
/// have h_total_len_valid : Hermes.IsValid.isValid total_len := by simp_all [Hermes.IsValid.isValid]
/// have h_total_len_isize : total_len.val ≤ Isize.max := by simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨ret_slice, h_eval_ret, h_post_ret⟩ := Aeneas.Std.WP.spec_imp_exists (slice_from_slice_parts.spec ptr total_len { h_data_is_valid := h_ptr_valid, h_len_is_valid := h_total_len_valid, h_isize := h_total_len_isize })
/// simp [h_eval_ret] at h_returns
/// have _h_a_len := h_post_a.h_unnamed
/// have _h_b_len := h_post_b.h_unnamed
/// have _h_ptr_addr := h_post_ptr.h_unnamed
/// have _h_ret_addr := h_post_ret.h_addr
/// have _h_ret_len := h_post_ret.h_len
/// have _h_ret_size := h_post_ret.h_size
/// have _h_ret_contiguous := h_post_ret.h_contiguous
/// have h_ret_eq : ret = ret_slice := by simp_all
/// subst h_ret_eq
/// have h_ret_contiguous := h_post_ret.h_contiguous
/// ext x
/// simp only [Set.mem_union]
/// constructor
/// · intro hx
/// have h_bound := (Hermes.HasReferent.referent ret).bounds x hx
/// by_cases h : x < (Hermes.HasReferent.referent a).address.val + (Hermes.HasReferent.referent a).size.val
/// · left
/// apply h_contiguous_a
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// · right
/// apply h_contiguous_b
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// omega
/// · intro hx
/// rcases hx with hx_a | hx_b
/// · have h_bound := (Hermes.HasReferent.referent a).bounds x hx_a
/// apply h_ret_contiguous
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// omega
/// · have h_bound := (Hermes.HasReferent.referent b).bounds x hx_b
/// apply h_ret_contiguous
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]; omega
/// proof (h_progress):
/// unfold concat_slices
/// rcases h_req with ⟨h_a_is_valid, h_b_is_valid, _, _, _, _, _, _, h_meta_bounds_isize, _⟩
/// have ⟨a_len, h_eval_a, h_post_a⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_a, Aeneas.Std.bind_tc_ok]
/// have ⟨b_len, h_eval_b, h_post_b⟩ := Aeneas.Std.WP.spec_imp_exists (slice_len.spec b { h_data_is_valid := h_b_is_valid })
/// simp [h_eval_b, Aeneas.Std.bind_tc_ok]
/// have h_add : a_len.val + b_len.val ≤ Aeneas.Std.Usize.max := by
/// simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨total_len, h_eval_add, h_post_add⟩ := Aeneas.Std.WP.spec_imp_exists (Aeneas.Std.Usize.add_spec h_add)
/// simp [h_eval_add, Aeneas.Std.bind_tc_ok]
/// have ⟨ptr, h_eval_ptr, h_post_ptr⟩ := Aeneas.Std.WP.spec_imp_exists (cast_slice_to_u8.spec a { h_data_is_valid := h_a_is_valid })
/// simp [h_eval_ptr, Aeneas.Std.bind_tc_ok]
/// have h_ptr_valid : Hermes.IsValid.isValid ptr := h_post_ptr.h_ret_is_valid
/// have h_total_len_valid : Hermes.IsValid.isValid total_len := by simp_all [Hermes.IsValid.isValid]
/// have h_total_len_isize : total_len.val ≤ Isize.max := by simp_all [h_post_a.h_unnamed, h_post_b.h_unnamed]
/// have ⟨ret_slice, h_eval_ret, h_post_ret⟩ := Aeneas.Std.WP.spec_imp_exists (slice_from_slice_parts.spec ptr total_len { h_data_is_valid := h_ptr_valid, h_len_is_valid := h_total_len_valid, h_isize := h_total_len_isize })
/// simp [h_eval_ret]
/// ```
pub unsafe fn concat_slices(a: *const [u8], b: *const [u8]) -> *const [u8] {
let a_len = slice_len(a);
let b_len = slice_len(b);
let total_len = a_len + b_len;
let ptr = cast_slice_to_u8(a);
slice_from_slice_parts(ptr, total_len)
}
fn main() {}