Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A model for Rust slices (and C array pointers) #225

Merged
merged 38 commits into from
Oct 9, 2024
Merged

Conversation

tahina-pro
Copy link
Member

This PR proposes:

  • Pulse.Lib.ArrayPtr, a library of C array pointers with subarray arithmetic, along with C extraction rules.
  • Pulse.Lib.Slice, a library of Rust slices.

Since Pulse.Lib.Slice is implemented on top of Pulse.Lib.ArrayPtr, slices can extract to C, with their lengths becoming explicit.

Pulse.Lib.Slice.fsti is meant to model the following slice operations:

I intend those operations to be extracted by adding rules in Pulse2Rust (which is why they are not marked inline_for_extraction, thus C extraction will produce explicit functions monomorphized by Karamel.) However, at this point, I have no idea how to do that, so this PR does not provide anything to extract slices to Rust. If anyone has any idea, please rise up! Thanks in advance!

NOTE: Currently, this PR is done on top of Pulse commit c003b97, corresponding to the last known good Everest at project-everest/everest@064cc21

: Lemma (is_slprop2 (is_split s p i left right))
[SMTPat (is_slprop2 (is_split s p i left right))]

noeq type slice_pair (t: Type) = | SlicePair: (fst: slice t) -> (snd: slice t) -> slice_pair t
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this type because Karamel refuses to extract functions returning F* pairs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only an issue with the Rust backend, right? In my experience, C-karamel does pairs just fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you could file a bug upstream with a precise description, that would be helpful, thank you

Copy link
Contributor

@gebner gebner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, I'm happy to refactor this a bit and clean it up (copyright headers).

@@ -0,0 +1,269 @@
module Pulse.Lib.ArrayPtr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files are missing copyright headers.


[@@erasable]
noeq
type footprint (t: Type0) = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the extra footprint necessary here? Both fields are determined by the pts_to_range slprop.

ghost
fn to_array (#t: Type) (s: ptr t) (a: array t) (#p: perm) (#fp: footprint t) (#v: Seq.seq t)
requires pts_to s #p fp v ** is_from_array p fp a ** pure (
Seq.length v == A.length a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already implied by is_from_array, right?

[SMTPat (adjacent fp1 fp2); SMTPat (adjacent fp2 fp3)];
]]

let split_postcond
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Pulse it's usually better to write the pre/postconditions directly without definitions because you need to manually call fold/unfold otherwise (and often spell out the arguments twice!).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Randomly noticed this comment, but see #143. I've had good experience with using aliases like this one and marking them @@pulse_unfold. This seems to be a pure fact though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this one is pure; there are other definitions here though that are slprops.

I didn't know about @@pulse_unfold, does it also fold automatically when it is part of a precondition?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it also fold automatically when it is part of a precondition?

Yes, any top-level name marked with @@pulse_unfold is unfolded in the context eagerly and in goals (like a precondition, but also assert) before we attempt to prove them.

(requires pts_to arr #p0 fp s0 ** pts_to arr #p1 fp s1)
(ensures fun _ -> pts_to arr #(p0 +. p1) fp s0 ** pure (s0 == s1))

val adjacent (#t: Type): footprint t -> footprint t -> prop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be significantly more ergonomic (and result in a better SMT encoding) to expose base/offset:

val base #t : ptr t -> GTot (A.array t)
val offset #t : ptr t -> GTot nat

let adjacent #t (a: ptr t) (sz: nat) (b: ptr t) : prop =
  base a == base b /\ offset a + sz == offset b

Then merge_assoc becomes unnecessary as well because Z3 can prove it directly.

pure (SZ.v s.len == A.length a)

```pulse
fn from_array (#t: Type) (mutb: bool) (a: array t) (#p: perm) (#v: Ghost.erased (Seq.seq t)) (alen: SZ.t {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of the mutb arguments here and in split?

Was your intention to encode the Rust split/split_mut distinction this way? I don't think that works. The main challenge with the Rust extraction is figuring out whether the type should be &[t] or &mut[t]; once you know the type it's easy to call the right split(_mut) version, but the flag in this function is not enough to infer the Rust type.

@gebner
Copy link
Contributor

gebner commented Oct 8, 2024

I have added support for slices in pulse2rust and added test cases too.

@tahina-pro There are some small changes in the API:

  • The footprint arguments are gone. You can just remove the fps everywhere and everything should work fine.
  • The pts_to definitions are instances now. You can just replace S.pts_to by pts_to.
  • The mutb flag is gone too. You can just remove it.
  • I've renamed AP.blit to AP.memcpy (for consistency with Array.memcpy)
  • You probably need to remove lots of fold/unfold statements. (The postconditions are now more consistent with the array API, and don't use extra definitions.)

Unless somebody complains, I'll merge this by tomorrow.

@gebner gebner merged commit 330ab36 into main Oct 9, 2024
1 check passed
@tahina-pro
Copy link
Member Author

Thanks a lot Gabriel! Your changes greatly improved my initial proposal!
And, last but not least, many thanks also for adding Rust extraction support!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants