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

Feat: Ability to emit sync-compatible Rust code. #6040

Merged
merged 27 commits into from
Jan 24, 2025

Conversation

MikaelMayer
Copy link
Member

@MikaelMayer MikaelMayer commented Jan 10, 2025

Fixes #5969

What was changed?

  • I added a new Rust-specific option --rust-sync, set to false by default. When turned on, all the generated code uses std::sync::Arc instead of std::rc::Rc. Nothing else is normally needed for generated code.
  • The option --include-runtime=true now emits the runtime when translating to Rust.
  • I added a new feature for the dafny runtime which is feature = "sync", enabling or disabling some functions via #[cfg(feature = "sync")] and #[cfg(not(feature = "sync"))]. Regular tests don't use this feature.
  • I ran cargo fmt on the runtime.

How has this been tested?

  • A test comp/rust/arc/tokiouser.dfy uses this option and the runtime with the sync feature, compiled with --rust-sync

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

@MikaelMayer MikaelMayer added the run-integration-tests Forces running the CI for integration tests even if the deep tests fail label Jan 10, 2025
@MikaelMayer
Copy link
Member Author

TODO: We need to downcast objects as well. Downcast requires send and sync but it's not implemented for objects for some reason.

@MikaelMayer MikaelMayer enabled auto-merge (squash) January 22, 2025 22:42
Copy link
Member

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

LGTM in general, made suggestions to improve the code but I'm not blocking on them, up to you how much to pull in. The comments about customer-facing documentation are the only blockers.

@@ -594,6 +595,8 @@ module {:extern "Defs"} DafnyToRustCompilerDefinitions {
}

function CoerceImpl(
rc: R.Type -> R.Type,
Copy link
Member

Choose a reason for hiding this comment

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

Consider using a term distinct from "RC" throughout for the abstract concept that will be mapped to either Rc or Arc. It's easy to get the concepts confused here, and that might have been a factor in initially missing that IsRc was testing for the wrong thing.

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 considered your proposition, but Arc and Rc are essentially the same concept "rc" that is used throughout the codebase. What prevented us from catch IsRc is that Rc was imported twice into two different names, which is no longer the case, so I think we are fine here.

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough, and I wouldn't want to change the symbols in the runtime at this point since we've already updated smithy-dafny to use Rc too.

pub use ::std::marker::Send;
pub use ::std::marker::Sync;

#[cfg(not(feature = "sync"))]
Copy link
Member

Choose a reason for hiding this comment

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

FYI this would probably be cleaner if you used https://crates.io/crates/cfg-if to group things into just two blocks:

cfg_if::cfg_if! {
    if #[feature = "sync"] {
        pub type DynAny = dyn Any + Send + Sync;
        pub struct UnsafeCell<T: ?Sized> {
          data: ::std::cell::UnsafeCell<T>, // UnsafeCell for interior mutability
        }
        ...
    } else {
        pub type DynAny = dyn Any;
        pub use ::std::cell::UnsafeCell;
        ...
    }
}

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 would like to avoid taking dependencies that are not strictly necessary. I'm ok with repeating the test in front of every specific declaration as well.

data: ::std::cell::UnsafeCell<T>, // UnsafeCell for interior mutability
}
#[cfg(feature = "sync")]
unsafe impl<T: ?Sized> Sync for UnsafeCell<T> where T: Send {}
Copy link
Member

Choose a reason for hiding this comment

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

Why is unsafe necessary here? We should at least document it

Copy link
Member Author

Choose a reason for hiding this comment

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

Documented + removed use of UnsafeCell for sequences to prefer the mutex just in case, and added tests.

@@ -784,29 +822,59 @@ where
left,
right,
} => {
#[cfg(feature = "sync")]
Copy link
Member

Choose a reason for hiding this comment

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

cfg-if even more appealing here IMO

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'm grouping statements together and do some renaming so that using cfg-if will look a bit less appealing :-)

let t: Rc<T> = Rc::downcast::<T>(t).ok()?;
mem::transmute(t)
}

/// A reference counted smart pointer with unrestricted mutability.
pub struct ArcMut<T: ?Sized> {
Copy link
Member

Choose a reason for hiding this comment

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

Now I'm curious, why was this already in here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because I had envisioned since the beginning we might want to have the async version of Rc for objects; however, I had not used it. So removing it because now for sure we are implementing it differently.

Copy link
Member

Choose a reason for hiding this comment

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

Nice test!

};
public override IEnumerable<Option<string>> SupportedOptions => new List<Option<string>> { RustModuleNameOption };
public static readonly Option<bool> RustSyncOption = new("--rust-sync",
@"Ensures that both datatypes and Dafny collections use structures that implement the Sync and Send traits") {
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this essentially all values, not just datatypes and collections?

} else {
args.Add("--bin");
args.Add(Path.GetFileNameWithoutExtension(targetFilename));
private static void ImportRustRuntimeTo(string targetDirectory) {
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to use SinglePassCodeGenerator.EmitRuntimeSource or .ReadRuntimeSystem instead, perhaps with some tweaks to those methods?

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 think this function is the closest to ReadRuntimeSystem than EmitRuntimeSource since we don't emit the run-time inside the generated code. However, I think ReadRuntimeSystem is a terrible name since it essentially copies the runtime into a specific location.
I'll rename both Rust and Java version to ImportRuntimeTo

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 started also looking into merging them but this function does not lies in the same architecture as the SinglePassCompiler. There is another compiler in between and linking them together would be more verbose than necessary. Moreover, I see that the code significantly differ so I'm not sure tweaking the existing ReadRuntimeSystem would actually help anything. I'll skip that part as, even if we end up to want it, would probably require a PR of its own.

@@ -7,3 +7,6 @@ edition = "2021"
once_cell = "1.18.0"
num = "0.4"
itertools = "0.11.0"

[features]
sync = []
Copy link
Member

Choose a reason for hiding this comment

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

Worth a comment, since consumers have to turn on this feature and should be aware of what it's doing?

# Conflicts:
#	Source/DafnyRuntime/DafnyRuntimeRust/src/system/mod.rs
robin-aws
robin-aws previously approved these changes Jan 23, 2025
@MikaelMayer MikaelMayer merged commit 18c538a into master Jan 24, 2025
22 checks passed
@MikaelMayer MikaelMayer deleted the fix-5969-sync-for-rust branch January 24, 2025 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
run-integration-tests Forces running the CI for integration tests even if the deep tests fail
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feat: Dafny-to-Rust to emit Arc instead of Rc
3 participants