diff --git a/Cargo.toml b/Cargo.toml index db37e0e..e0adc57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] +edition = "2021" members = ["chorus_lib", "chorus_derive"] resolver = "2" [workspace.package] -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["Shun Kashiwa "] homepage = "https://lsd-ucsc.github.io/ChoRus/" diff --git a/chorus_book/book.toml b/chorus_book/book.toml index 61adcbf..83c4c93 100644 --- a/chorus_book/book.toml +++ b/chorus_book/book.toml @@ -5,6 +5,9 @@ multilingual = false src = "src" title = "ChoRus" +[rust] +edition = "2021" + [output.html] git-repository-url = "https://github.com/lsd-ucsc/Chorus" git-repository-icon = "fa-github" diff --git a/chorus_book/src/SUMMARY.md b/chorus_book/src/SUMMARY.md index 222c1b0..a2b1f9e 100644 --- a/chorus_book/src/SUMMARY.md +++ b/chorus_book/src/SUMMARY.md @@ -10,8 +10,8 @@ - [Transport](./guide-transport.md) - [Projector](./guide-projector.md) - [Input and Output](./guide-input-and-output.md) + - [Runner](./guide-runner.md) - [Higher-order Choreography](./guide-higher-order-choreography.md) - [Location Polymorphism](./guide-location-polymorphism.md) - - [Choreographic Enclave and Efficient Conditional](./guide-enclave.md) - - [Runner](./guide-runner.md) + - [Efficient Conditionals with Enclaves and MLVs](./guide-efficient-conditionals.md) - [Links](./links.md) diff --git a/chorus_book/src/guide-choreography.md b/chorus_book/src/guide-choreography.md index 1480948..22e0738 100644 --- a/chorus_book/src/guide-choreography.md +++ b/chorus_book/src/guide-choreography.md @@ -161,6 +161,43 @@ if num == 42 { } ``` +### Multicast + +The `multicast` operator is similar to the `broadcast` operator, but it allows you to manually specify the recipients instead of sending the value to all locations. The operator returns a `MultiplyLocated` value that is available at the all recipient locations. + +```rust +{{#include ./header.txt}} +# +# struct HelloWorldChoreography; +# impl Choreography for HelloWorldChoreography { +# type L = LocationSet!(Alice, Bob, Carol); +# fn run(self, op: &impl ChoreoOp) { +// This value is only available at Alice +let num_at_alice: Located = op.locally(Alice, |_| { + 42 +}); + +// Send the value from Alice to Bob and Carol +let num_at_bob_and_carol: MultiplyLocated = + op.multicast(Alice, ::new(), &num_at_alice); + +// Bob and Carol can now access the value +op.locally(Bob, |un| { + let num_at_bob: &i32 = un.unwrap(&num_at_bob_and_carol); + println!("The number at Bob is {}", num_at_bob); +}); +op.locally(Carol, |un| { + let num_at_carol: &i32 = un.unwrap(&num_at_bob_and_carol); + println!("The number at Carol is {}", num_at_carol); +}); +# } +# } +``` + +The second parameter of the `multicast` is a value of the `LocationSet` type. You can use the `new()` method of the `LocationSet` type to obtain a value representation of the location set. + +Both `Bob` and `Carol` can access the value sent from `Alice` inside their local computation using the same `unwrap` method. + ### Note on invalid values for Choreography::L You'll get a compile error if you try to work with a `ChoreographyLocation` that is not a member of `L`. diff --git a/chorus_book/src/guide-efficient-conditionals.md b/chorus_book/src/guide-efficient-conditionals.md new file mode 100644 index 0000000..ab56880 --- /dev/null +++ b/chorus_book/src/guide-efficient-conditionals.md @@ -0,0 +1,302 @@ +# Efficient Conditionals with Enclaves and MLVs + +## `broadcast` incurs unnecessary communication + +In [the previous section](./guide-choreography.html#broadcast), we discussed how the `broadcast` operator can be used to implement a conditional behavior in a choreography. In short, the `broadcast` operator sends a located value from a source location to all other locations, making the value available at all locations. The resulting value is a normal (not `Located`) value and it can be used to make a branch. + +However, the `broadcast` operator can incur unnecessary communication when not all locations need to receive the value. Consider a simple key-value store where a *client* sends either a `Get` or `Put` request to a *primary* server, and the primary server forwards the request to a *backup* server if the request is a `Put`. The backup server does not need to receive the request if the request is a `Get`. + +Using the `broadcast` operator, this protocol can be implemented as follows: + +```rust +{{#include ./header.txt}} +# +# fn read_request() -> Request { +# Request::Put("key".to_string(), "value".to_string()) +# } +# fn get_value(key: &Key) -> Option { +# Some("value".to_string()) +# } +# fn set_value(key: &Key, value: &Value) { +# println!("Saved key: {} and value: {}", key, value); +# } +# +#[derive(ChoreographyLocation)] +struct Client; + +#[derive(ChoreographyLocation)] +struct Primary; + +#[derive(ChoreographyLocation)] +struct Backup; + +type Key = String; +type Value = String; + +#[derive(Serialize, Deserialize)] +enum Request { + Get(Key), + Put(Key, Value), +} + +#[derive(Serialize, Deserialize)] +enum Response { + GetOk(Option), + PutOk, +} + +struct KeyValueStoreChoreography; + +impl Choreography> for KeyValueStoreChoreography { + type L = LocationSet!(Client, Primary, Backup); + fn run(self, op: &impl ChoreoOp) -> Located { + // Read the request from the client + let request_at_client: Located = op.locally(Client, |_| read_request()); + // Send the request to the primary server + let request_at_primary: Located = + op.comm(Client, Primary, &request_at_client); + // Check if the request is a `Put` + let is_put_at_primary: Located = op.locally(Primary, |un| { + matches!(un.unwrap(&request_at_primary), Request::Put(_, _)) + }); + // Broadcast the `is_put_at_primary` to all locations so it can be used for branching + let is_put: bool = op.broadcast(Primary, is_put_at_primary); // <-- Incurs unnecessary communication + // Depending on the request, set or get the value + let response_at_primary = if is_put { + let request_at_backup: Located = + op.comm(Primary, Backup, &request_at_primary); + op.locally(Backup, |un| match un.unwrap(&request_at_backup) { + Request::Put(key, value) => set_value(key, value), + _ => (), + }); + op.locally(Primary, |_| Response::PutOk) + } else { + op.locally(Primary, |un| { + let key = match un.unwrap(&request_at_primary) { + Request::Get(key) => key, + _ => &"".to_string(), + }; + Response::GetOk(get_value(key)) + }) + }; + // Send the response from the primary to the client + let response_at_client = op.comm(Primary, Client, &response_at_primary); + response_at_client + } +} +``` + +While this implementation works, it incurs unnecessary communication. When we branch on `is_put`, we broadcast the value to all locations. This is necessary to make sure that the value is available at all locations so it can be used as a normal, non-located value. However, notice that the client does not need to receive the value. Regardless of whether the request is a `Put` or `Get`, the client should wait for the response from the primary server. + +## Changing the census with `enclave` + +To avoid unnecessary communication, we can use the `enclave` operator. The `enclave` operator is similar to [the `call` operator](./guide-higher-order-choreography.html) but executes a sub-choreography only at locations that are included in its location set. Inside the sub-choreography, `broadcast` only sends the value to the locations that are included in the location set. This allows us to avoid unnecessary communication. + +Let's refactor the previous example using the `enclave` operator. We define a sub-choreography `HandleRequestChoreography` that describes how the primary and backup servers (but not the client) handle the request and use the `enclave` operator to execute the sub-choreography. + +```rust +{{#include ./header.txt}} +# +# fn read_request() -> Request { +# Request::Put("key".to_string(), "value".to_string()) +# } +# fn get_value(key: &Key) -> Option { +# Some("value".to_string()) +# } +# fn set_value(key: &Key, value: &Value) { +# println!("Saved key: {} and value: {}", key, value); +# } +# +# #[derive(ChoreographyLocation)] +# struct Client; +# +# #[derive(ChoreographyLocation)] +# struct Primary; +# +# #[derive(ChoreographyLocation)] +# struct Backup; +# +# type Key = String; +# type Value = String; +# +# #[derive(Serialize, Deserialize)] +# enum Request { +# Get(Key), +# Put(Key, Value), +# } +# +# #[derive(Serialize, Deserialize)] +# enum Response { +# GetOk(Option), +# PutOk, +# } +# +struct HandleRequestChoreography { + request: Located, +} + +// This sub-choreography describes how the primary and backup servers handle the request +impl Choreography> for HandleRequestChoreography { + type L = LocationSet!(Primary, Backup); + fn run(self, op: &impl ChoreoOp) -> Located { + let is_put_request: Located = op.locally(Primary, |un| { + matches!(un.unwrap(&self.request), Request::Put(_, _)) + }); + let is_put: bool = op.broadcast(Primary, is_put_request); + let response_at_primary = if is_put { + let request_at_backup: Located = + op.comm(Primary, Backup, &self.request); + op.locally(Backup, |un| match un.unwrap(&request_at_backup) { + Request::Put(key, value) => set_value(key, value), + _ => (), + }); + op.locally(Primary, |_| Response::PutOk) + } else { + op.locally(Primary, |un| { + let key = match un.unwrap(&self.request) { + Request::Get(key) => key, + _ => &"".to_string(), + }; + Response::GetOk(get_value(key)) + }) + }; + response_at_primary + } +} + +struct KeyValueStoreChoreography; + +impl Choreography> for KeyValueStoreChoreography { + type L = LocationSet!(Client, Primary, Backup); + fn run(self, op: &impl ChoreoOp) -> Located { + let request_at_client: Located = op.locally(Client, |_| read_request()); + let request_at_primary: Located = + op.comm(Client, Primary, &request_at_client); + // Execute the sub-choreography only at the primary and backup servers + let response: MultiplyLocated, LocationSet!(Primary, Backup)> = + op.enclave(HandleRequestChoreography { + request: request_at_primary, + }); + let response_at_primary: Located = response.flatten(); + let response_at_client = op.comm(Primary, Client, &response_at_primary); + response_at_client + } +} +``` + +In this refactored version, the `HandleRequestChoreography` sub-choreography describes how the primary and backup servers handle the request. The `enclave` operator executes the sub-choreography only at the primary and backup servers. The `broadcast` operator inside the sub-choreography sends the value only to the primary and backup servers, avoiding unnecessary communication. + +The `enclave` operator returns a return value of the sub-choreography wrapped as a `MultiplyLocated` value. Since `HandleRequestChoreography` returns a `Located`, the return value of the `enclave` operator is a `MultiplyLocated, LocationSet!(Primary, Backup)>`. To get the located value at the primary server, we can use the `locally` operator to unwrap the `MultiplyLocated` value on the primary. Since this is a common pattern, we provide the `flatten` method on `MultiplyLocated` to simplify this operation. + +With the `enclave` operator, we can avoid unnecessary communication and improve the efficiency of the choreography. + +## Reusing Knowledge of Choice in Enclaves + +The key idea behind the `enclave` operator is that a normal value inside a choreography is equivalent to a (multiply) located value at all locations executing the choreography. This is why a normal value in a sub-choreography becomes a multiply located value at all locations executing the sub-choreography when returned from the `enclave` operator. + +It is possible to perform this conversion in the opposite direction as well. If we have a multiply located value at some locations, and those are the only locations executing the choreography, then we can obtain a normal value out of the multiply located value. This is useful when we want to reuse the already known information about a choice in an enclave. + +Inside a choreography, we can use the `naked` operator to convert a multiply located value at locations `S` to a normal value if the census of the choreography is a subset of `S`. + +For example, the above choreography can be written as follows: + +```rust +{{#include ./header.txt}} +# +# fn read_request() -> Request { +# Request::Put("key".to_string(), "value".to_string()) +# } +# fn get_value(key: &Key) -> Option { +# Some("value".to_string()) +# } +# fn set_value(key: &Key, value: &Value) { +# println!("Saved key: {} and value: {}", key, value); +# } +# +# #[derive(ChoreographyLocation)] +# struct Client; +# +# #[derive(ChoreographyLocation)] +# struct Primary; +# +# #[derive(ChoreographyLocation)] +# struct Backup; +# +# type Key = String; +# type Value = String; +# +# #[derive(Serialize, Deserialize)] +# enum Request { +# Get(Key), +# Put(Key, Value), +# } +# +# #[derive(Serialize, Deserialize)] +# enum Response { +# GetOk(Option), +# PutOk, +# } +# +struct HandleRequestChoreography { + request: Located, + is_put: MultiplyLocated, +} + +impl Choreography> for HandleRequestChoreography { + type L = LocationSet!(Primary, Backup); + fn run(self, op: &impl ChoreoOp) -> Located { + // obtain a normal boolean because {Primary, Backup} is the census of the choreography + let is_put: bool = op.naked(self.is_put); + let response_at_primary = if is_put { + // ... +# let request_at_backup: Located = +# op.comm(Primary, Backup, &self.request); +# op.locally(Backup, |un| match un.unwrap(&request_at_backup) { +# Request::Put(key, value) => set_value(key, value), +# _ => (), +# }); +# op.locally(Primary, |_| Response::PutOk) + } else { + // ... +# op.locally(Primary, |un| { +# let key = match un.unwrap(&self.request) { +# Request::Get(key) => key, +# _ => &"".to_string(), +# }; +# Response::GetOk(get_value(key)) +# }) + }; + response_at_primary + } +} + +struct KeyValueStoreChoreography; + +impl Choreography> for KeyValueStoreChoreography { + type L = LocationSet!(Client, Primary, Backup); + fn run(self, op: &impl ChoreoOp) -> Located { + let request_at_client: Located = op.locally(Client, |_| read_request()); + let request_at_primary: Located = + op.comm(Client, Primary, &request_at_client); + let is_put_at_primary: Located = op.locally(Primary, |un| { + matches!(un.unwrap(&request_at_primary), Request::Put(_, _)) + }); + // get a MLV by multicasting the boolean to the census of the sub-choreography + let is_put: MultiplyLocated = op.multicast( + Primary, + ::new(), + &is_put_at_primary, + ); + let response: MultiplyLocated, LocationSet!(Primary, Backup)> = + op.enclave(HandleRequestChoreography { + is_put, + request: request_at_primary, + }); + let response_at_primary: Located = response.flatten(); + let response_at_client = op.comm(Primary, Client, &response_at_primary); + response_at_client + } +} +``` + +In this version, we first `multicast` the boolean value to the census of the sub-choreography (`Primary` and `Client`) and we pass the MLV to the sub-choreography. Inside the sub-choreography, we use the `naked` operator to obtain a normal boolean value. This allows us to reuse the already known information about the choice in the sub-choreography. diff --git a/chorus_book/src/guide-enclave.md b/chorus_book/src/guide-enclave.md deleted file mode 100644 index 2ff895c..0000000 --- a/chorus_book/src/guide-enclave.md +++ /dev/null @@ -1,177 +0,0 @@ -# Choreographic Enclave and Efficient Conditional - -ChoRus supports the `enclave` operator to achieve efficient conditional execution. - -## Conditional with Broadcast - -Consider the following protocol: - -1. Alice generates a random number `x` and sends it to Bob. -2. Bob checks if `x` is even. If it is even, Bob sends `x` to Carol. Otherwise, Bob terminates. - -This protocol can be implemented as follows: - -```rust -{{#include ./header.txt}} -# fn get_random_number() -> u32 { -# 42 // for presentation purpose -# } -# -struct DemoChoreography; - -impl Choreography for DemoChoreography { - type L = LocationSet!(Alice, Bob, Carol); - fn run(self, op: &impl ChoreoOp) { - let x_at_alice = op.locally(Alice, |_| { - get_random_number() - }); - let x_at_bob = op.comm(Alice, Bob, &x_at_alice); - let is_even_at_bob: Located = op.locally(Bob, |un| { - let x = un.unwrap(&x_at_bob); - x % 2 == 0 - }); - let is_even: bool = op.broadcast(Bob, is_even_at_bob); - if is_even { - let x_at_carol = op.comm(Bob, Carol, &x_at_bob); - op.locally(Carol, |un| { - let x = un.unwrap(&x_at_carol); - println!("x is even: {}", x); - }); - } - } -} -``` - -While this code correctly implements the protocol, it is inefficient. The `is_even` value is broadcasted to all locations, but Alice does not need to receive the value. Ideally, we want to send `is_even_at_bob` only to Carol and branch only on Bob and Carol. - -In ChoRus, we can achieve this using the `enclave` operator. First, let us define a sub-choreography that describes the communication between Bob and Carol: - -```rust -{{#include ./header.txt}} -struct BobCarolChoreography { - x_at_bob: Located, -}; -impl Choreography for BobCarolChoreography { - type L = LocationSet!(Bob, Carol); - fn run(self, op: &impl ChoreoOp) { - let is_even_at_bob: Located = op.locally(Bob, |un| { - let x = un.unwrap(&self.x_at_bob); - x % 2 == 0 - }); - let is_even: bool = op.broadcast(Bob, is_even_at_bob); - if is_even { - let x_at_carol = op.comm(Bob, Carol, &self.x_at_bob); - op.locally(Carol, |un| { - let x = un.unwrap(&x_at_carol); - println!("x is even: {}", x); - }); - } - } -} -``` - -Notice that `BobCarolChoreography` only describes the behavior of Bob and Carol (see its location set `L`). `enclave` is an operator to execute a choreography only at locations that is included in the location set. In this case, if we invoke `BobCarolChoreography` with `enclave` in the main choreography, it will only be executed at Bob and Carol and not at Alice. - -```rust -{{#include ./header.txt}} -# fn get_random_number() -> u32 { -# 42 // for presentation purpose -# } -# struct BobCarolChoreography { -# x_at_bob: Located, -# }; -# impl Choreography for BobCarolChoreography { -# type L = LocationSet!(Bob, Carol); -# fn run(self, op: &impl ChoreoOp) { -# let is_even_at_bob: Located = op.locally(Bob, |un| { -# let x = un.unwrap(&self.x_at_bob); -# x % 2 == 0 -# }); -# let is_even: bool = op.broadcast(Bob, is_even_at_bob); -# if is_even { -# let x_at_carol = op.comm(Bob, Carol, &self.x_at_bob); -# op.locally(Carol, |un| { -# let x = un.unwrap(&x_at_carol); -# println!("x is even: {}", x); -# }); -# } -# } -# } -struct MainChoreography; -impl Choreography for MainChoreography { - type L = LocationSet!(Alice, Bob, Carol); - fn run(self, op: &impl ChoreoOp) { - let x_at_alice = op.locally(Alice, |_| { - get_random_number() - }); - let x_at_bob = op.comm(Alice, Bob, &x_at_alice); - op.enclave(BobCarolChoreography { - x_at_bob, - }); - } -} -``` - -## Returning Values from Enclave - -Just like the `call` operator, the `enclave` operator can return a value. However, the type of the returned value must implement the `Superposition` trait. `Superposition` provides a way for ChoRus to construct a value on locations that are not specified in the `enclave` operator. - -In general, `Superposition` is either a located value or a struct consisting only of located values. The `Located` struct implements the `Superposition` trait, so you can return located values without any code. If you wish to return a struct of located values, you need to derive the `Superposition` trait using the derive macro. - -```rust -{{#include ./header.txt}} -# fn get_random_number() -> u32 { -# 42 // for presentation purpose -# } -# -#[derive(Superposition)] -struct BobCarolResult { - is_even_at_bob: Located, - is_even_at_carol: Located, -} - -struct BobCarolChoreography { - x_at_bob: Located, -}; - -impl Choreography for BobCarolChoreography { - type L = LocationSet!(Bob, Carol); - fn run(self, op: &impl ChoreoOp) -> BobCarolResult { - let is_even_at_bob: Located = op.locally(Bob, |un| { - let x = un.unwrap(&self.x_at_bob); - x % 2 == 0 - }); - let is_even: bool = op.broadcast(Bob, is_even_at_bob.clone()); - if is_even { - let x_at_carol = op.comm(Bob, Carol, &self.x_at_bob); - op.locally(Carol, |un| { - let x = un.unwrap(&x_at_carol); - println!("x is even: {}", x); - }); - } - BobCarolResult { - is_even_at_bob, - is_even_at_carol: op.locally(Carol, |_| is_even), - } - } -} - -struct MainChoreography; - -impl Choreography for MainChoreography { - type L = LocationSet!(Alice, Bob, Carol); - fn run(self, op: &impl ChoreoOp) { - let x_at_alice = op.locally(Alice, |_| { - get_random_number() - }); - let x_at_bob = op.comm(Alice, Bob, &x_at_alice); - let BobCarolResult { - is_even_at_bob, - is_even_at_carol, - } = op.enclave(BobCarolChoreography { - x_at_bob, - }); - // can access is_even_at_bob and is_even_at_carol using `locally` on Bob and Carol - } -} -``` diff --git a/chorus_book/src/guide-located-values.md b/chorus_book/src/guide-located-values.md index c6258e6..ed6cdba 100644 --- a/chorus_book/src/guide-located-values.md +++ b/chorus_book/src/guide-located-values.md @@ -2,20 +2,28 @@ As we have seen in the [Choreography](./guide-choreography.md) section, a located value is a value that is available only at a specific location. In this section, we will discuss located values in more detail. -## `Located` struct +## `MultiplyLocated` struct -The `Located` struct represents a located value. It is a generic struct that takes two type parameters: a type parameter `V` that represents the type of the value, and a type parameter `L1` that represents the location where the value is available. +The `MultiplyLocated` struct represents a multiply located value (MLV) that is available at multiple locations. It is a generic struct that takes two type parameters: a type parameter `V` that represents the type of the value, and a type parameter `L` that represents the location set where the value is available. ```rust,ignore -pub struct Located +pub struct MultiplyLocated where - L1: ChoreographyLocation, + L: LocationSet, { // ... } ``` -The `Located` struct can be in one of the two states: `Local` and `Remote`. The `Local` state represents a located value that is available at the current location. The `Remote` state represents a located value that is available at a different location. +The `MultiplyLocated` struct can be in one of the two states: `Local` and `Remote`. The `Local` state represents a located value that is available at the current location (that is, the current location is a member of the location set `L`). The `Remote` state represents a located value that is available at a different location. + +## `Located` type + +In some cases, we may want to represent a located value that is available at a single location. For example, the return value of the `locally` operator is a (singly) located value that is available at the location given as an argument to the operator. The `Located` is used to represent such located values. It is a type alias for the `MultiplyLocated` struct with the location set containing only one location. + +```rust,ignore +type Located = MultiplyLocated; +``` ## `Portable` trait diff --git a/chorus_book/src/guide-locations.md b/chorus_book/src/guide-locations.md index f26ffe9..c70e037 100644 --- a/chorus_book/src/guide-locations.md +++ b/chorus_book/src/guide-locations.md @@ -46,3 +46,18 @@ use chorus_lib::core::LocationSet; type L = LocationSet!(Alice, Bob); ``` + +Some operators, such as `multicast`, requires a value of `LocationSet` to be passed as an argument. You can obtain a value of `LocationSet` by using the `new` method. + +```rust +# extern crate chorus_lib; +# use chorus_lib::core::ChoreographyLocation; +# #[derive(ChoreographyLocation)] +# struct Alice; +# +# #[derive(ChoreographyLocation)] +# struct Bob; +# use chorus_lib::core::LocationSet; +# +let alice_and_bob = ::new(); +``` diff --git a/chorus_book/src/header.txt b/chorus_book/src/header.txt index f24bfe6..1dbae7c 100644 --- a/chorus_book/src/header.txt +++ b/chorus_book/src/header.txt @@ -1,5 +1,5 @@ # extern crate chorus_lib; -# use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, Superposition, Runner, LocationSet}; +# use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize}; # use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; # #[derive(ChoreographyLocation)] # struct Alice; diff --git a/chorus_derive/src/lib.rs b/chorus_derive/src/lib.rs index 371427e..e97f4cb 100644 --- a/chorus_derive/src/lib.rs +++ b/chorus_derive/src/lib.rs @@ -1,12 +1,15 @@ use proc_macro::{self, TokenStream}; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; +use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(ChoreographyLocation)] pub fn derive_choreography_location(input: TokenStream) -> TokenStream { let DeriveInput { ident, .. } = parse_macro_input!(input); let output = quote! { impl ChoreographyLocation for #ident { + fn new() -> Self { + Self + } fn name() -> &'static str { stringify!(#ident) } @@ -20,68 +23,3 @@ pub fn derive_choreography_location(input: TokenStream) -> TokenStream { }; output.into() } - -#[proc_macro_derive(Superposition)] -pub fn derive_superposition(input: TokenStream) -> TokenStream { - // Parse the input tokens into a syntax tree - let input = syn::parse_macro_input!(input as DeriveInput); - - // Get the name of the struct or enum - let name = &input.ident; - - // Generate the implementation of the Superposition trait - let expanded = match input.data { - Data::Struct(data) => match data.fields { - Fields::Named(fields) => { - let field_names = fields.named.iter().map(|field| &field.ident); - quote! { - impl Superposition for #name { - fn remote() -> Self { - #name { - #( #field_names: <_ as Superposition>::remote(), )* - } - } - } - } - } - Fields::Unnamed(fields) => { - let fields = (0..fields.unnamed.len()).map(|_| { - quote! { - <_ as Superposition>::remote() - } - }); - quote! { - impl Superposition for #name { - fn remote() -> Self { - #name( - #(#fields),* - ) - } - } - } - } - Fields::Unit => { - quote! { - impl Superposition for #name { - fn remote() -> Self { - #name - } - } - } - } - }, - Data::Enum(_) => { - quote! { - compile_error!("Superposition cannot be derived automatically for enums"); - } - } - Data::Union(_) => { - quote! { - compile_error!("Superposition cannot be derived automatically for unions"); - } - } - }; - - // Convert the generated tokens back into a TokenStream - TokenStream::from(expanded) -} diff --git a/chorus_lib/examples/bookseller.rs b/chorus_lib/examples/bookseller.rs index 2237be3..8eefa4b 100644 --- a/chorus_lib/examples/bookseller.rs +++ b/chorus_lib/examples/bookseller.rs @@ -3,7 +3,7 @@ extern crate chorus_lib; use std::io; use std::thread; -use chorus_lib::transport::local::LocalTransportChannelBuilder; +use chorus_lib::{core::Located, transport::local::LocalTransportChannelBuilder}; use chrono::NaiveDate; use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, LocationSet, Projector}; @@ -25,17 +25,14 @@ struct Seller; #[derive(ChoreographyLocation)] struct Buyer; -struct BooksellerChoreography; -impl Choreography for BooksellerChoreography { +struct BooksellerChoreography { + title: Located, + budget: Located, +} +impl Choreography for BooksellerChoreography { type L = LocationSet!(Seller, Buyer); - fn run(self, op: &impl ChoreoOp) { - let title_at_buyer = op.locally(Buyer, |_| { - println!("Enter the title of the book to buy (TAPL or HoTT)"); - let mut title = String::new(); - io::stdin().read_line(&mut title).unwrap(); - title - }); - let title_at_seller = op.comm(Buyer, Seller, &title_at_buyer); + fn run(self, op: &impl ChoreoOp) -> bool { + let title_at_seller = op.comm(Buyer, Seller, &self.title); let price_at_seller = op.locally(Seller, |un| { let title = un.unwrap(&title_at_seller); if let Some((price, _)) = get_book(&title) { @@ -47,7 +44,7 @@ impl Choreography for BooksellerChoreography { let decision_at_buyer = op.locally(Buyer, |un| { if let Some(price) = un.unwrap(&price_at_buyer) { println!("Price is {}", price); - return *price < BUDGET; + return *price < *un.unwrap(&self.budget); } println!("The book does not exist"); return false; @@ -69,10 +66,15 @@ impl Choreography for BooksellerChoreography { println!("The buyer cannot buy the book"); }); } + decision } } fn main() { + println!("Enter the title of the book to buy (TAPL or HoTT)"); + let mut title = String::new(); + io::stdin().read_line(&mut title).unwrap(); + let transport_channel = LocalTransportChannelBuilder::new() .with(Seller) .with(Buyer) @@ -85,12 +87,55 @@ fn main() { let mut handles: Vec> = Vec::new(); handles.push(thread::spawn(move || { - seller_projector.epp_and_run(BooksellerChoreography); + seller_projector.epp_and_run(BooksellerChoreography { + title: seller_projector.remote(Buyer), + budget: seller_projector.remote(Buyer), + }); })); handles.push(thread::spawn(move || { - buyer_projector.epp_and_run(BooksellerChoreography); + buyer_projector.epp_and_run(BooksellerChoreography { + title: buyer_projector.local(title), + budget: buyer_projector.local(BUDGET), + }); })); for h in handles { h.join().unwrap(); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn distributed_tapl() { + let title = String::from("TAPL"); + + let transport_channel = LocalTransportChannelBuilder::new() + .with(Seller) + .with(Buyer) + .build(); + let transport_seller = LocalTransport::new(Seller, transport_channel.clone()); + let transport_buyer = LocalTransport::new(Buyer, transport_channel.clone()); + + let seller_projector = Projector::new(Seller, transport_seller); + let buyer_projector = Projector::new(Buyer, transport_buyer); + + let mut handles: Vec> = Vec::new(); + handles.push(thread::spawn(move || { + seller_projector.epp_and_run(BooksellerChoreography { + title: seller_projector.remote(Buyer), + budget: seller_projector.remote(Buyer), + }); + })); + handles.push(thread::spawn(move || { + buyer_projector.epp_and_run(BooksellerChoreography { + title: buyer_projector.local(title), + budget: buyer_projector.local(BUDGET), + }); + })); + for h in handles { + h.join().unwrap(); + } + } +} diff --git a/chorus_lib/examples/bookseller2.rs b/chorus_lib/examples/bookseller2.rs index 8c7894a..0f97107 100644 --- a/chorus_lib/examples/bookseller2.rs +++ b/chorus_lib/examples/bookseller2.rs @@ -5,7 +5,10 @@ use std::sync::Arc; use std::thread; use chorus_lib::{ - core::{ChoreoOp, Choreography, ChoreographyLocation, Located, LocationSet, Projector}, + core::{ + ChoreoOp, Choreography, ChoreographyLocation, Located, LocationSet, MultiplyLocated, + Projector, + }, transport::local::{LocalTransport, LocalTransportChannelBuilder}, }; use chrono::NaiveDate; @@ -22,22 +25,22 @@ struct Seller; type Inventory = HashMap; trait Decider { - fn new(price: Located) -> Self; + fn new(price: MultiplyLocated) -> Self; } struct OneBuyerDecider { - price: Located, + price: MultiplyLocated, } impl Decider for OneBuyerDecider { - fn new(price: Located) -> Self { + fn new(price: MultiplyLocated) -> Self { Self { price } } } -impl Choreography> for OneBuyerDecider { +impl Choreography> for OneBuyerDecider { type L = LocationSet!(Buyer1, Buyer2); - fn run(self, op: &impl ChoreoOp) -> Located { + fn run(self, op: &impl ChoreoOp) -> MultiplyLocated { let price = op.broadcast(Buyer1, self.price); return op.locally(Buyer1, |_| { const BUYER1_BUDGET: i32 = 100; @@ -47,18 +50,18 @@ impl Choreography> for OneBuyerDecider { } struct TwoBuyerDecider { - price: Located, + price: MultiplyLocated, } impl Decider for TwoBuyerDecider { - fn new(price: Located) -> Self { + fn new(price: MultiplyLocated) -> Self { Self { price } } } -impl Choreography> for TwoBuyerDecider { +impl Choreography> for TwoBuyerDecider { type L = LocationSet!(Buyer1, Buyer2); - fn run(self, op: &impl ChoreoOp) -> Located { + fn run(self, op: &impl ChoreoOp) -> MultiplyLocated { let remaining = op.locally(Buyer1, |un| { const BUYER1_BUDGET: i32 = 100; return un.unwrap(&self.price) - BUYER1_BUDGET; @@ -94,7 +97,7 @@ impl, L = LocationSet!(Buyer1, Buyer2)> + return i32::MAX; }); let price_at_buyer1 = op.comm(Seller, Buyer1, &price_at_seller); - let decision_at_buyer1 = op.enclave(D::new(price_at_buyer1)); + let decision_at_buyer1 = op.enclave(D::new(price_at_buyer1)).flatten(); struct GetDeliveryDateChoreography { inventory: Located, @@ -120,11 +123,13 @@ impl, L = LocationSet!(Buyer1, Buyer2)> + } } - return op.enclave(GetDeliveryDateChoreography { - inventory: self.inventory.clone(), - title_at_seller: title_at_seller.clone(), - decision_at_buyer1, - }); + return op + .enclave(GetDeliveryDateChoreography { + inventory: self.inventory.clone(), + title_at_seller: title_at_seller.clone(), + decision_at_buyer1, + }) + .flatten(); } } diff --git a/chorus_lib/examples/cardgame.rs b/chorus_lib/examples/cardgame.rs new file mode 100644 index 0000000..a5a6746 --- /dev/null +++ b/chorus_lib/examples/cardgame.rs @@ -0,0 +1,310 @@ +extern crate chorus_lib; + +use std::marker::PhantomData; + +use chorus_lib::core::{ + ChoreoOp, Choreography, ChoreographyLocation, Faceted, FanInChoreography, FanOutChoreography, + HCons, Located, LocationSet, LocationSetFoldable, Member, MultiplyLocated, Projector, Quire, + Subset, +}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +#[derive(ChoreographyLocation)] +struct Dealer; + +#[derive(ChoreographyLocation)] +struct Player1; +#[derive(ChoreographyLocation)] +struct Player2; +#[derive(ChoreographyLocation)] +struct Player3; + +fn read_i32() -> i32 { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + input.trim().parse::().expect("Failed to parse input") +} + +struct Game< + Players: LocationSet + + Subset, PlayersSubsetAll> + + LocationSetFoldable, Players, PlayersFoldable>, + PlayersSubsetAll, + PlayersFoldable, +> { + phantom: PhantomData<(Players, PlayersSubsetAll, PlayersFoldable)>, +} + +impl< + Players: LocationSet + + Subset, PlayersSubsetAll> + + LocationSetFoldable, Players, PlayersFoldable>, + PlayersSubsetAll, + PlayersFoldable, + > Game +{ + fn new(_: Players) -> Self + where + Players: Subset, PlayerSubsetAll>, + { + Self { + phantom: PhantomData {}, + } + } +} + +impl< + Players: LocationSet + + Subset, PlayersSubsetAll> + + LocationSetFoldable, Players, PlayersFoldable>, + PlayersSubsetAll, + PlayersFoldable, + > Choreography for Game +{ + type L = HCons; + + fn run(self, op: &impl ChoreoOp) -> () { + struct Collect(PhantomData); + impl FanOutChoreography for Collect { + type L = HCons; + type QS = Players; + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let card1 = op.locally(Dealer, |_| { + println!("Enter the first card for {:?}", Q::name()); + read_i32() + }); + op.comm(Dealer, Q::new(), &card1) + } + } + let hand1 = op.fanout(Players::new(), Collect(PhantomData)); + + struct Gather< + 'a, + Players: LocationSet + Subset, PlayersSubset>, + PlayersSubset, + > { + hand1: &'a Faceted, + phantom: PhantomData, + } + impl< + 'a, + Players: LocationSet + Subset, PlayersSubset>, + PlayersSubset, + > FanInChoreography for Gather<'a, Players, PlayersSubset> + { + type L = HCons; + type QS = Players; + type RS = Players; + fn run( + &self, + op: &impl ChoreoOp, + ) -> MultiplyLocated + where + Self::QS: Subset, + Self::RS: Subset, + Q: Member, + Q: Member, + { + let x = op.locally(Q::new(), |un| *un.unwrap(self.hand1)); + let x = op.multicast::( + Q::new(), + ::new(), + &x, + ); + x + } + } + let on_the_table = op.fanin( + Players::new(), + Gather { + hand1: &hand1, + phantom: PhantomData, + }, + ); + + struct Choice<'a, Players: LocationSet> { + hand1: &'a Faceted, + on_the_table: &'a MultiplyLocated, Players>, + } + impl<'a, Players: LocationSet> FanOutChoreography for Choice<'a, Players> { + type L = HCons; + type QS = Players; + + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + op.locally(Q::new(), |un| { + let hand1 = *un.unwrap(self.hand1); + let on_the_table = un.unwrap(self.on_the_table); + println!("My first card is: {}", hand1); + println!("On the table: {:?}", on_the_table); + println!("I'll ask for another? [True/False]"); + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + let input = input.trim(); + if input == "True" { + true + } else { + false + } + }) + } + } + + let wants_next_card = op.fanout( + Players::new(), + Choice { + hand1: &hand1, + on_the_table: &on_the_table, + }, + ); + + struct Collect2<'a, Players: LocationSet> { + hand1: &'a Faceted, + wants_next_card: &'a Faceted, + } + impl<'a, Players: LocationSet> FanOutChoreography> for Collect2<'a, Players> { + type L = HCons; + type QS = Players; + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located, Q> + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + struct Enclave { + hand1: Located, + wants_next_card: Located, + } + impl Choreography, Player>> for Enclave { + type L = LocationSet!(Dealer, Player); + + fn run(self, op: &impl ChoreoOp) -> Located, Player> { + let choice = op.broadcast(Player::new(), self.wants_next_card.clone()); + if choice { + let card2 = op.locally(Dealer, |_| { + println!("Player {:?} wants another card", Player::name()); + println!("Enter the second card for {:?}", Player::name()); + read_i32() + }); + let card2 = op.comm(Dealer, Player::new(), &card2); + op.locally(Player::new(), |un| { + vec![*un.unwrap(&self.hand1), *un.unwrap(&card2)] + }) + } else { + op.locally(Player::new(), |un| vec![*un.unwrap(&self.hand1)]) + } + } + } + let hand1 = op.locally(Q::new(), |un| *un.unwrap(self.hand1)); + let wants_next_card = op.locally(Q::new(), |un| *un.unwrap(self.wants_next_card)); + op.enclave(Enclave:: { + hand1, + wants_next_card, + }) + .flatten() + } + } + let hand2 = op.fanout( + Players::new(), + Collect2 { + hand1: &hand1, + wants_next_card: &wants_next_card, + }, + ); + let tbl_card = op.locally(Dealer, |_| { + println!("Enter a single card for everyone "); + read_i32() + }); + let table_card = op.broadcast(Dealer, tbl_card); + + struct Outcome<'a, Players: LocationSet> { + hand2: &'a Faceted, Players>, + table_card: i32, + } + impl<'a, Players: LocationSet> FanOutChoreography<()> for Outcome<'a, Players> { + type L = HCons; + type QS = Players; + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located<(), Q> + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + op.locally(Q::new(), |un| { + let mut hand2 = un.unwrap(self.hand2).clone(); + hand2.push(self.table_card); + println!("Final hands: {:?}", hand2); + let sum: i32 = hand2.iter().sum(); + println!("My win result: {}", sum % 21 > 19); + return (); + }) + } + } + + op.fanout( + Players::new(), + Outcome { + hand2: &hand2, + table_card, + }, + ); + } +} + +fn main() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Dealer) + .with(Player1) + .with(Player2) + .build(); + + type Players = LocationSet!(Player1, Player2); + + let transport_dealer = LocalTransport::new(Dealer, transport_channel.clone()); + let transport_player1 = LocalTransport::new(Player1, transport_channel.clone()); + let transport_player2 = LocalTransport::new(Player2, transport_channel.clone()); + + let dealer_projector = Projector::new(Dealer, transport_dealer); + let player1_projector = Projector::new(Player1, transport_player1); + let player2_projector = Projector::new(Player2, transport_player2); + + let mut handles = Vec::new(); + handles.push(std::thread::spawn(move || { + dealer_projector.epp_and_run(Game::new(Players::new())); + })); + handles.push(std::thread::spawn(move || { + player1_projector.epp_and_run(Game::new(Players::new())); + })); + handles.push(std::thread::spawn(move || { + player2_projector.epp_and_run(Game::new(Players::new())); + })); + + for handle in handles { + handle.join().unwrap(); + } +} diff --git a/chorus_lib/examples/enclave-mlv.rs b/chorus_lib/examples/enclave-mlv.rs new file mode 100644 index 0000000..408c337 --- /dev/null +++ b/chorus_lib/examples/enclave-mlv.rs @@ -0,0 +1,143 @@ +use chorus_lib::{ + core::{ + ChoreoOp, Choreography, ChoreographyLocation, Located, LocationSet, MultiplyLocated, + Projector, + }, + transport::local::{LocalTransport, LocalTransportChannelBuilder}, +}; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +type Query = String; + +#[derive(Serialize, Deserialize)] +enum Choice { + Alice, + Bob, +} + +#[derive(ChoreographyLocation)] +struct Alice; + +#[derive(ChoreographyLocation)] +struct Bob; + +#[derive(ChoreographyLocation)] +struct Carol; + +struct MainChoreography; + +impl Choreography for MainChoreography { + type L = LocationSet!(Alice, Bob, Carol); + fn run(self, op: &impl ChoreoOp) { + let choice = op.locally(Alice, |_| { + let mut rng = rand::thread_rng(); + let choice: bool = rng.gen(); + if choice { + Choice::Alice + } else { + Choice::Bob + } + }); + let choice_and_query = op.enclave(ChooseQueryChoreography { + alice_choice: choice, + }); + let query_at_alice = op.locally(Alice, |un| { + let query = un.unwrap(&choice_and_query); + String::from(un.unwrap(&query.1)) + }); + let query_at_carol = op.comm(Alice, Carol, &query_at_alice); + let response_at_carol = op.locally(Carol, |un| { + let query = un.unwrap(&query_at_carol); + println!("Carol received query: {}", query); + let r = format!("Carol's response to {}", query); + return r; + }); + let response = op.broadcast(Carol, response_at_carol); + op.enclave(TerminalChoreography { + choice_and_query, + response, + }); + } +} + +struct ChoiceAndQuery( + MultiplyLocated, + Located, +); + +struct ChooseQueryChoreography { + alice_choice: Located, +} + +impl Choreography for ChooseQueryChoreography { + type L = LocationSet!(Alice, Bob); + fn run(self, op: &impl ChoreoOp) -> ChoiceAndQuery { + let choice = op.broadcast(Alice, self.alice_choice); + let query = match choice { + Choice::Alice => op.locally(Alice, |_| "Alice's query".to_string()), + Choice::Bob => { + let bob_query = op.locally(Bob, |_| "Bob's query".to_string()); + op.comm(Bob, Alice, &bob_query) + } + }; + return ChoiceAndQuery(op.unnaked(choice), query); + } +} + +struct TerminalChoreography { + choice_and_query: MultiplyLocated, + response: String, +} + +impl Choreography for TerminalChoreography { + type L = LocationSet!(Alice, Bob); + fn run(self, op: &impl ChoreoOp) { + let ChoiceAndQuery(choice, _) = op.naked(self.choice_and_query); + match op.naked(choice) { + Choice::Alice => { + op.locally(Alice, |_| { + println!("Alice received response: {}", self.response); + }); + } + Choice::Bob => { + op.locally(Bob, |_| { + println!("Bob received response: {}", self.response); + }); + } + } + } +} + +fn main() { + let mut handles = Vec::new(); + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + { + let transport = LocalTransport::new(Alice, transport_channel.clone()); + handles.push(std::thread::spawn(move || { + let p = Projector::new(Alice, transport); + p.epp_and_run(MainChoreography); + })); + } + { + let transport = LocalTransport::new(Bob, transport_channel.clone()); + handles.push(std::thread::spawn(move || { + let p = Projector::new(Bob, transport); + p.epp_and_run(MainChoreography); + })); + } + { + let transport = LocalTransport::new(Carol, transport_channel.clone()); + handles.push(std::thread::spawn(move || { + let p = Projector::new(Carol, transport); + p.epp_and_run(MainChoreography); + })); + } + for handle in handles { + handle.join().unwrap(); + } +} diff --git a/chorus_lib/examples/fanin.rs b/chorus_lib/examples/fanin.rs new file mode 100644 index 0000000..a4678e0 --- /dev/null +++ b/chorus_lib/examples/fanin.rs @@ -0,0 +1,197 @@ +extern crate chorus_lib; + +use std::marker::PhantomData; +use std::thread; + +use chorus_lib::core::{ + ChoreoOp, Choreography, ChoreographyLocation, FanInChoreography, Located, LocationSet, Member, + MultiplyLocated, Projector, Quire, Subset, +}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +#[derive(ChoreographyLocation, Debug)] +struct Alice; + +#[derive(ChoreographyLocation, Debug)] +struct Bob; + +#[derive(ChoreographyLocation, Debug)] +struct Carol; + +struct FanIn +where + Alice: Member, +{ + phantom: PhantomData<(L, QS, Alice, AliceMemberL)>, +} + +impl + FanIn +where + Alice: Member, +{ + fn new(_: Alice) -> Self + where + Alice: Member, + { + FanIn { + phantom: PhantomData, + } + } +} + +impl + FanInChoreography for FanIn +where + Alice: Member, +{ + type L = L; + type QS = QS; + type RS = LocationSet!(Alice); + + fn run( + &self, + op: &impl ChoreoOp, + ) -> MultiplyLocated + where + Self::QS: Subset, + Self::RS: Subset, + Q: Member, + Q: Member, + { + let msg_at_q = op.locally(Q::new(), |_| { + format!("{} says hi to {}", Q::name(), Alice::name()) + }); + let msg_at_alice = op.comm(Q::new(), Alice::new(), &msg_at_q); + return msg_at_alice; + } +} + +struct MainChoreography; +impl Choreography, Alice>> for MainChoreography { + type L = LocationSet!(Alice, Bob, Carol); + + fn run( + self, + op: &impl ChoreoOp, + ) -> Located, Alice> { + let v = op.fanin(::new(), FanIn::new(Alice)); + op.locally(Alice, |un| { + let m = un.unwrap(&v).get_map(); + println!( + "Alice received: \"{}\" from Bob and \"{}\" from Carol", + m.get(Bob::name()).unwrap_or(&String::from("ERROR")), + m.get(Carol::name()).unwrap_or(&String::from("ERROR")) + ) + }); + return v; + } +} + +fn main() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + let transport_alice = LocalTransport::new(Alice, transport_channel.clone()); + let transport_bob = LocalTransport::new(Bob, transport_channel.clone()); + let transport_carol = LocalTransport::new(Carol, transport_channel.clone()); + + let alice_projector = Projector::new(Alice, transport_alice); + let bob_projector = Projector::new(Bob, transport_bob); + let carol_projector = Projector::new(Carol, transport_carol); + + let mut handles: Vec> = Vec::new(); + handles.push( + thread::Builder::new() + .name("Alice".to_string()) + .spawn(move || { + alice_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Bob".to_string()) + .spawn(move || { + bob_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Carol".to_string()) + .spawn(move || { + carol_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + for handle in handles { + handle.join().unwrap(); + } +} + +#[cfg(test)] +mod tests { + use chorus_lib::core::Runner; + + use super::*; + + #[test] + fn test_projector() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + let transport_alice = LocalTransport::new(Alice, transport_channel.clone()); + let transport_bob = LocalTransport::new(Bob, transport_channel.clone()); + let transport_carol = LocalTransport::new(Carol, transport_channel.clone()); + + let alice_projector = Projector::new(Alice, transport_alice); + let bob_projector = Projector::new(Bob, transport_bob); + let carol_projector = Projector::new(Carol, transport_carol); + + let mut handles: Vec> = Vec::new(); + handles.push( + thread::Builder::new() + .name("Alice".to_string()) + .spawn(move || { + let quire_at_alice = alice_projector.epp_and_run(MainChoreography); + let m = alice_projector.unwrap(quire_at_alice).get_map(); + assert_eq!(m.get(Bob::name()).unwrap(), "Bob says hi to Alice"); + assert_eq!(m.get(Carol::name()).unwrap(), "Carol says hi to Alice"); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Bob".to_string()) + .spawn(move || { + bob_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Carol".to_string()) + .spawn(move || { + carol_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + for handle in handles { + handle.join().unwrap(); + } + } + + #[test] + fn test_runner() { + let runner = Runner::new(); + let quire_at_alice = runner.run(MainChoreography); + let m = runner.unwrap(quire_at_alice).get_map(); + assert_eq!(m.get(Bob::name()).unwrap(), "Bob says hi to Alice"); + assert_eq!(m.get(Carol::name()).unwrap(), "Carol says hi to Alice"); + } +} diff --git a/chorus_lib/examples/fanout.rs b/chorus_lib/examples/fanout.rs new file mode 100644 index 0000000..0e3947e --- /dev/null +++ b/chorus_lib/examples/fanout.rs @@ -0,0 +1,192 @@ +extern crate chorus_lib; + +use std::marker::PhantomData; +use std::thread; + +use chorus_lib::core::{ + ChoreoOp, Choreography, ChoreographyLocation, FanOutChoreography, Located, LocationSet, Member, + Projector, Subset, +}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +#[derive(ChoreographyLocation, Debug)] +struct Alice; + +#[derive(ChoreographyLocation, Debug)] +struct Bob; + +#[derive(ChoreographyLocation, Debug)] +struct Carol; + +struct FanOut +where + Alice: Member, +{ + phantom: PhantomData<(L, QS, Alice, AliceMemberL)>, +} + +impl + FanOut +where + Alice: Member, +{ + fn new(_: Alice) -> Self + where + Alice: Member, + { + FanOut { + phantom: PhantomData, + } + } +} + +impl + FanOutChoreography for FanOut +where + Alice: Member, +{ + type L = L; + type QS = QS; + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let msg_at_alice = op.locally(Alice::new(), |_| { + format!("{} says hi to {}", Alice::name(), Q::name()) + }); + let msg_at_q = op.comm(Alice::new(), Q::new(), &msg_at_alice); + op.locally(Q::new(), |un| { + println!("{} received: \"{}\"", Q::name(), un.unwrap(&msg_at_q)) + }); + msg_at_q + } +} + +struct MainChoreography; +impl Choreography<(Located, Located)> for MainChoreography { + type L = LocationSet!(Alice, Bob, Carol); + fn run(self, op: &impl ChoreoOp) -> (Located, Located) { + let v = op.fanout(::new(), FanOut::new(Alice)); + let value_at_bob = op.locally(Bob, |un| { + let v = un.unwrap(&v); + println!("{}", v); + v.clone() + }); + let value_at_carol = op.locally(Carol, |un| { + let v = un.unwrap(&v); + println!("{}", v); + v.clone() + }); + return (value_at_bob, value_at_carol); + } +} + +fn main() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + let transport_alice = LocalTransport::new(Alice, transport_channel.clone()); + let transport_bob = LocalTransport::new(Bob, transport_channel.clone()); + let transport_carol = LocalTransport::new(Carol, transport_channel.clone()); + + let alice_projector = Projector::new(Alice, transport_alice); + let bob_projector = Projector::new(Bob, transport_bob); + let carol_projector = Projector::new(Carol, transport_carol); + + let mut handles: Vec> = Vec::new(); + handles.push( + thread::Builder::new() + .name("Alice".to_string()) + .spawn(move || { + alice_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Bob".to_string()) + .spawn(move || { + bob_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Carol".to_string()) + .spawn(move || { + carol_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + for handle in handles { + handle.join().unwrap(); + } +} +#[cfg(test)] +mod tests { + use chorus_lib::core::Runner; + + use super::*; + + #[test] + fn test_projector() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + let transport_alice = LocalTransport::new(Alice, transport_channel.clone()); + let transport_bob = LocalTransport::new(Bob, transport_channel.clone()); + let transport_carol = LocalTransport::new(Carol, transport_channel.clone()); + + let alice_projector = Projector::new(Alice, transport_alice); + let bob_projector = Projector::new(Bob, transport_bob); + let carol_projector = Projector::new(Carol, transport_carol); + + let mut handles: Vec> = Vec::new(); + handles.push( + thread::Builder::new() + .name("Alice".to_string()) + .spawn(move || { + alice_projector.epp_and_run(MainChoreography); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Bob".to_string()) + .spawn(move || { + let v = bob_projector.epp_and_run(MainChoreography); + assert_eq!(bob_projector.unwrap(v.0), "Alice says hi to Bob"); + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Carol".to_string()) + .spawn(move || { + let v = carol_projector.epp_and_run(MainChoreography); + assert_eq!(carol_projector.unwrap(v.1), "Alice says hi to Carol"); + }) + .unwrap(), + ); + for handle in handles { + handle.join().unwrap(); + } + } + + #[test] + fn test_runner() { + let runner = Runner::new(); + let (v1, v2) = runner.run(MainChoreography); + assert_eq!(runner.unwrap(v1), "Alice says hi to Bob"); + assert_eq!(runner.unwrap(v2), "Alice says hi to Carol"); + } +} diff --git a/chorus_lib/examples/loc-poly.rs b/chorus_lib/examples/loc-poly.rs index accaadb..b6d3e67 100644 --- a/chorus_lib/examples/loc-poly.rs +++ b/chorus_lib/examples/loc-poly.rs @@ -47,11 +47,13 @@ impl Choreography> for MainChoreography { data: v1, }); let v2 = op.locally(Bob, |un| un.unwrap(&v2) + 10); - return op.enclave(CommAndPrint { - sender: Bob, - receiver: Alice, - data: v2, - }); + return op + .enclave(CommAndPrint { + sender: Bob, + receiver: Alice, + data: v2, + }) + .flatten(); } } diff --git a/chorus_lib/examples/locationset-fold.rs b/chorus_lib/examples/locationset-fold.rs new file mode 100644 index 0000000..b5d63d4 --- /dev/null +++ b/chorus_lib/examples/locationset-fold.rs @@ -0,0 +1,36 @@ +use chorus_lib::core::{ + ChoreographyLocation, LocationSet, LocationSetFoldable, LocationSetFolder, Member, Subset, +}; + +#[derive(ChoreographyLocation)] +struct Alice; +#[derive(ChoreographyLocation)] +struct Bob; +#[derive(ChoreographyLocation)] +struct Carol; + +fn main() { + type L = LocationSet!(Alice, Bob, Carol); + type QS = LocationSet!(Bob, Carol); + struct F; + impl LocationSetFolder for F { + type L = L; + type QS = QS; + fn f( + &self, + acc: String, + _curr: Q, + ) -> String + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let mut x = acc.clone(); + x.push_str(Q::name()); + x + } + } + let x = QS::foldr(F {}, String::new()); + println!("{}", x); +} diff --git a/chorus_lib/examples/multicast.rs b/chorus_lib/examples/multicast.rs new file mode 100644 index 0000000..4c24913 --- /dev/null +++ b/chorus_lib/examples/multicast.rs @@ -0,0 +1,80 @@ +extern crate chorus_lib; + +use std::thread; + +use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, LocationSet, Projector}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +// --- Define two locations (Alice and Bob) --- + +#[derive(ChoreographyLocation)] +struct Alice; + +#[derive(ChoreographyLocation)] +struct Bob; + +#[derive(ChoreographyLocation)] +struct Carol; + +// --- Define a choreography --- +struct MulticastChoreography; + +// Implement the `Choreography` trait for `HelloWorldChoreography` +impl Choreography for MulticastChoreography { + // Define the set of locations involved in the choreography. + // In this case, the set consists of `Alice` and `Bob` and + // the choreography can use theses locations. + type L = LocationSet!(Alice, Bob, Carol); + fn run(self, op: &impl ChoreoOp) { + // Create a located value at Alice + let msg_at_alice = op.locally(Alice, |_| { + println!("Hello from Alice!"); + "Hello from Alice!".to_string() + }); + let msg_at_bob_and_carol = + op.multicast(Alice, ::new(), &msg_at_alice); + op.locally(Bob, |un| { + let msg = un.unwrap(&msg_at_bob_and_carol); + println!("Bob received: {}", msg); + }); + op.locally(Carol, |un| { + let msg = un.unwrap(&msg_at_bob_and_carol); + println!("Carol received: {}", msg); + }); + } +} + +fn main() { + let mut handles: Vec> = Vec::new(); + // Create a transport channel + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + // Run the choreography in two threads + { + let transport = LocalTransport::new(Alice, transport_channel.clone()); + handles.push(thread::spawn(move || { + let p = Projector::new(Alice, transport); + p.epp_and_run(MulticastChoreography); + })); + } + { + let transport = LocalTransport::new(Bob, transport_channel.clone()); + handles.push(thread::spawn(move || { + let p = Projector::new(Bob, transport); + p.epp_and_run(MulticastChoreography); + })); + } + { + let transport = LocalTransport::new(Carol, transport_channel.clone()); + handles.push(thread::spawn(move || { + let p = Projector::new(Carol, transport); + p.epp_and_run(MulticastChoreography); + })); + } + for h in handles { + h.join().unwrap(); + } +} diff --git a/chorus_lib/examples/parallel.rs b/chorus_lib/examples/parallel.rs new file mode 100644 index 0000000..3c21077 --- /dev/null +++ b/chorus_lib/examples/parallel.rs @@ -0,0 +1,69 @@ +extern crate chorus_lib; + +use std::thread; + +use rand::Rng; + +use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, LocationSet, Projector}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +#[derive(ChoreographyLocation, Debug)] +struct Alice; + +#[derive(ChoreographyLocation, Debug)] +struct Bob; + +#[derive(ChoreographyLocation, Debug)] +struct Carol; + +struct ParallelChoreography; +impl Choreography for ParallelChoreography { + type L = LocationSet!(Alice, Bob, Carol); + fn run(self, op: &impl ChoreoOp) { + let faceted = op.parallel(::new(), || { + // return a random number between 1 and 10 + rand::thread_rng().gen_range(1..=10) + }); + op.locally(Alice, |un| { + let x = un.unwrap(&faceted); + println!("Alice picked {}", x); + }); + op.locally(Bob, |un| { + let x = un.unwrap(&faceted); + println!("Bob picked {}", x); + }); + op.locally(Carol, |un| { + let x = un.unwrap(&faceted); + println!("Carol picked {}", x); + }); + } +} + +fn main() { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Alice) + .with(Bob) + .with(Carol) + .build(); + let transport_alice = LocalTransport::new(Alice, transport_channel.clone()); + let transport_bob = LocalTransport::new(Bob, transport_channel.clone()); + let transport_carol = LocalTransport::new(Carol, transport_channel.clone()); + + let alice_projector = Projector::new(Alice, transport_alice); + let bob_projector = Projector::new(Bob, transport_bob); + let carol_projector = Projector::new(Carol, transport_carol); + + let mut handles: Vec> = Vec::new(); + handles.push(thread::spawn(move || { + alice_projector.epp_and_run(ParallelChoreography); + })); + handles.push(thread::spawn(move || { + bob_projector.epp_and_run(ParallelChoreography); + })); + handles.push(thread::spawn(move || { + carol_projector.epp_and_run(ParallelChoreography); + })); + for handle in handles { + handle.join().unwrap(); + } +} diff --git a/chorus_lib/examples/runner.rs b/chorus_lib/examples/runner.rs index 0692d97..b8adb18 100644 --- a/chorus_lib/examples/runner.rs +++ b/chorus_lib/examples/runner.rs @@ -1,6 +1,6 @@ extern crate chorus_lib; use chorus_lib::core::{ - ChoreoOp, Choreography, ChoreographyLocation, Located, LocationSet, Runner, Superposition, + ChoreoOp, Choreography, ChoreographyLocation, Located, LocationSet, Runner, }; #[derive(ChoreographyLocation)] @@ -14,7 +14,6 @@ fn get_random_number() -> u32 { 42 // for presentation purpose } -#[derive(Superposition)] struct BobCarolResult { is_even_at_bob: Located, is_even_at_carol: Located, @@ -53,17 +52,14 @@ impl Choreography for MainChoreography { fn run(self, op: &impl ChoreoOp) { let x_at_alice = op.locally(Alice, |_| get_random_number()); let x_at_bob = op.comm(Alice, Bob, &x_at_alice); - let BobCarolResult { - is_even_at_bob, - is_even_at_carol, - } = op.enclave(BobCarolChoreography { x_at_bob }); + let result = op.enclave(BobCarolChoreography { x_at_bob }); op.locally(Bob, |un| { - let is_even = un.unwrap(&is_even_at_bob); + let is_even = un.unwrap(&un.unwrap(&result).is_even_at_bob); assert!(is_even); println!("Bob: x is even: {}", is_even); }); op.locally(Carol, |un| { - let is_even = un.unwrap(&is_even_at_carol); + let is_even = un.unwrap(&un.unwrap(&result).is_even_at_carol); assert!(is_even); println!("Carol: x is even: {}", is_even); }); diff --git a/chorus_lib/examples/tic-tac-toe.rs b/chorus_lib/examples/tic-tac-toe.rs index 6c441ef..686036a 100644 --- a/chorus_lib/examples/tic-tac-toe.rs +++ b/chorus_lib/examples/tic-tac-toe.rs @@ -110,7 +110,6 @@ struct PlayerX; struct PlayerO; trait Brain { - fn get_player(&self) -> char; fn think(&self, board: &Board) -> Board; } @@ -125,9 +124,6 @@ impl UserBrain { } impl Brain for UserBrain { - fn get_player(&self) -> char { - self.player - } fn think(&self, board: &Board) -> Board { println!("Current board:"); board.draw(); @@ -210,9 +206,6 @@ impl MinimaxBrain { } impl Brain for MinimaxBrain { - fn get_player(&self) -> char { - self.player - } fn think(&self, board: &Board) -> Board { // return the board with the best move board.draw(); diff --git a/chorus_lib/src/core.rs b/chorus_lib/src/core.rs index 5ac5beb..2530665 100644 --- a/chorus_lib/src/core.rs +++ b/chorus_lib/src/core.rs @@ -2,7 +2,7 @@ //! //! This module provides core choreography constructs, such as `Choreography`, `Located`, and `Projector`. -use std::marker::PhantomData; +use std::{collections::HashMap, fmt::Debug, marker::PhantomData}; use serde::de::DeserializeOwned; // re-export so that users can use derive macros without importing serde @@ -20,6 +20,8 @@ pub use serde::{Deserialize, Serialize}; /// struct Alice; /// ``` pub trait ChoreographyLocation: Copy { + /// Constructs a location. + fn new() -> Self; /// Returns the name of the location as a string. fn name() -> &'static str; } @@ -32,94 +34,244 @@ pub trait ChoreographyLocation: Copy { pub trait Portable: Serialize + DeserializeOwned {} impl Portable for T {} -/// Represents a value that might *NOT* be located at a location. Values returned by `enclave` must satisfy this trait. -/// -/// In most cases, you don't need to implement this trait manually. You can derive it using `#[derive(Superposition)]` as long as all the fields consist of located values. -pub trait Superposition { - /// Constructs a struct that is *NOT* located at a location. - fn remote() -> Self; -} - -impl Superposition for () { - fn remote() -> Self { - () - } -} - /// Represents a value located at a location. -/// -/// The struct takes two type parameters: `V` and `L1`. -/// -/// - `V` is an actual type of the value. -/// - `L1` is the location of the value. It must satisfy the `ChoreographicLocation` trait. -#[derive(PartialEq)] -pub struct Located +pub type Located = MultiplyLocated; + +/// Represents a value located at multiple locations. +pub struct MultiplyLocated where - L1: ChoreographyLocation, + L: LocationSet, { - /// `Some` if it is located at the current location and `None` if it is located at another location. value: Option, - /// The struct is parametrized by the location (`L1`). - phantom: PhantomData, + phantom: PhantomData, } -impl Located +impl MultiplyLocated where - L1: ChoreographyLocation, + L: LocationSet, { /// Constructs a struct located at the current location with value - fn local(value: V) -> Self { - Located { + pub fn local(value: V) -> Self { + MultiplyLocated { value: Some(value), phantom: PhantomData, } } + + /// Constructs a struct located at another location + fn remote() -> Self { + MultiplyLocated { + value: None, + phantom: PhantomData, + } + } } -/// If the value implements `Clone`, the located value of the same type also implements `Clone`. -impl Clone for Located +impl MultiplyLocated, LS2> where - L1: ChoreographyLocation, + LS1: LocationSet, + LS2: LocationSet, +{ + /// Flattens a located value located at multiple locations. + pub fn flatten(self) -> MultiplyLocated + where + LS1: Subset, + { + let value = self.value.map(|x| x.value).flatten(); + MultiplyLocated { + value, + phantom: PhantomData, + } + } +} + +impl Clone for MultiplyLocated +where + V: Clone, + L: LocationSet, { fn clone(&self) -> Self { - Located { + MultiplyLocated { value: self.value.clone(), phantom: PhantomData, } } } -impl Superposition for Located +/// Represents a mapping from location names to values +pub struct Quire where - L1: ChoreographyLocation, + L: LocationSet, { - /// Constructs a struct located at another location - fn remote() -> Self { - Located { - value: None, + value: HashMap, + phantom: PhantomData, +} + +impl Debug for Quire +where + L: LocationSet, + V: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_map().entries(self.value.iter()).finish() + } +} + +impl Quire { + /// Constructs a struct located at the current location with value + pub fn new() -> Self { + Quire { + value: HashMap::new(), phantom: PhantomData, } } } +impl Quire +where + L: LocationSet, +{ + /// Add a value located at a location + pub fn add(self, _location: L1, value: V) -> Quire> { + let mut map = self.value; + map.insert(L1::name().to_string(), value); + Quire { + value: map, + phantom: PhantomData, + } + } + /// Turn into a hash map + pub fn into_map(self) -> HashMap { + self.value + } +} + +impl Quire +where + L: LocationSet, + V: Clone, +{ + /// Get a copy as a hash map + pub fn get_map(&self) -> HashMap { + self.value.clone() + } +} + +/// Represents possibly different values located at multiple locations +#[derive(Debug)] +pub struct Faceted +where + L: LocationSet, +{ + value: HashMap, + phantom: PhantomData, +} + +/// Represents a value that can be unwrapped at any location in `L` +pub trait Unwrappable<'a, V> { + /// A location set that the value is located at + type L: LocationSet; + /// Unwraps a located value at a given location + fn unwrap_at(&'a self, location: L1) -> &'a V + where + L1: Member; +} + +impl<'a, V, L: LocationSet> Unwrappable<'a, V> for MultiplyLocated { + type L = L; + + fn unwrap_at(&'a self, _: L1) -> &'a V + where + L1: Member, + { + self.value.as_ref().unwrap() + } +} + +impl<'a, V, L: LocationSet> Unwrappable<'a, V> for Faceted { + type L = L; + + fn unwrap_at(&'a self, _: L1) -> &'a V + where + L1: Member, + { + self.value.get(&L1::name().to_string()).unwrap() + } +} + // --- HList and Helpers --- +/// xx +pub trait LocationSetFolder { + /// x + type L: LocationSet; + /// looping over + type QS: LocationSet; + /// x + fn f(&self, acc: B, curr: Q) -> B + where + Self::QS: Subset, + Q: Member, + Q: Member; +} + /// heterogeneous list #[doc(hidden)] -pub trait LocationSet { +pub trait LocationSet: Sized { + fn new() -> Self; /// returns fn to_string_list() -> Vec<&'static str>; } /// end of HList #[doc(hidden)] +#[derive(Debug)] pub struct HNil; /// An element of HList #[doc(hidden)] +#[derive(Debug)] pub struct HCons(Head, Tail); +/// x +pub trait LocationSetFoldable { + /// x + fn foldr>(f: F, acc: B) -> B; +} + +impl LocationSetFoldable for HNil { + fn foldr>(_f: F, acc: B) -> B { + acc + } +} + +impl< + L: LocationSet, + QS: LocationSet, + Head: ChoreographyLocation, + Tail, + QSSubsetL, + HeadMemberL, + HeadMemberQS, + ITail, + > LocationSetFoldable + for HCons +where + QS: Subset, + Head: Member, + Head: Member, + Tail: LocationSetFoldable, +{ + fn foldr>(f: F, acc: B) -> B { + let x = f.f(acc, Head::new()); + Tail::foldr(f, x) + } +} + impl LocationSet for HNil { + fn new() -> Self { + HNil + } fn to_string_list() -> Vec<&'static str> { Vec::new() } @@ -129,6 +281,9 @@ where Head: ChoreographyLocation, Tail: LocationSet, { + fn new() -> Self { + HCons(Head::new(), Tail::new()) + } fn to_string_list() -> Vec<&'static str> { let mut v = Tail::to_string_list(); v.push(Head::name()); @@ -136,6 +291,13 @@ where } } +#[derive(ChoreographyLocation)] +struct Alice; +#[derive(ChoreographyLocation)] +struct Bob; +#[derive(ChoreographyLocation)] +struct Carol; + // To export `LocationSet` under the `core` module, we define an internal macro and export it. // This is because Rust does not allow us to export a macro from a module without re-exporting it. // `__ChoRus_Internal_LocationSet` is the internal macro and it is configured not to be visible in the documentation. @@ -168,6 +330,8 @@ pub use __ChoRus_Internal_LocationSet as LocationSet; /// Marker #[doc(hidden)] pub struct Here; +#[doc(hidden)] +pub struct Here2; /// Marker #[doc(hidden)] pub struct There(Index); @@ -204,17 +368,16 @@ where /// /// It takes two type parameters `L` and `Index`. `L` is a location set and `Index` is some type that is inferred by the compiler. /// If a location set `M` is a subset of `L`, then there exists a type `Index` such that `M` implements `Subset`. -pub trait Subset {} +pub trait Subset {} -// Base case: HNil is a subset of any set -impl Subset for HNil {} +// Base case: `HNil` is a subset of any collection +impl Subset for HNil {} -// Recursive case -impl Subset> - for HCons +// Recursive case: `Head` is a `Member` and `Tail` is a `Subset` +impl Subset for HCons where - Head: Member, - Tail: Subset, + Head: Member, + Tail: Subset, { } @@ -224,9 +387,13 @@ pub struct Unwrapper { } impl Unwrapper { - /// Takes a reference to the located value at the current location and returns its reference - pub fn unwrap<'a, V>(&self, located: &'a Located) -> &'a V { - located.value.as_ref().unwrap() + /// Unwraps a located value at the current location + pub fn unwrap<'a, V, S: LocationSet, Index, U>(&self, unwrappable: &'a U) -> &'a V + where + U: Unwrappable<'a, V, L = S> + 'a, + L1: Member, + { + unwrappable.unwrap_at::(L1::new()) } } @@ -234,7 +401,7 @@ impl Unwrapper { /// /// The trait provides methods to work with located values. An implementation of the trait is "injected" into /// a choreography at runtime and provides the actual implementation of the operators. -pub trait ChoreoOp { +pub trait ChoreoOp { /// Performs a computation at the specified location. /// /// `locally` performs a computation at a location, which are specified by `location` and `computation`, respectively. @@ -247,47 +414,172 @@ pub trait ChoreoOp { &self, location: L1, computation: impl Fn(Unwrapper) -> V, - ) -> Located + ) -> MultiplyLocated where - L1: Member; + L1: Member; /// Performs a communication between two locations. /// /// `comm` sends `data` from `sender` to `receiver`. The `data` must be a `Located` struct at the `sender` location /// and the value type must implement `Portable`. - fn comm( + fn comm< + L: LocationSet, + Sender: ChoreographyLocation, + Receiver: ChoreographyLocation, + V: Portable, + Index1, + Index2, + Index3, + >( &self, - sender: L1, - receiver: L2, - data: &Located, - ) -> Located + sender: Sender, + receiver: Receiver, + data: &MultiplyLocated, + ) -> MultiplyLocated where - L1: Member, - L2: Member; + L: Subset, + Sender: Member, + Receiver: Member; /// Performs a broadcast from a location to all other locations. /// /// `broadcast` broadcasts `data` from `sender` to all other locations. The `data` must be a `Located` struct at the `sender` location. /// The method returns the non-located value. - fn broadcast( + fn broadcast( &self, - sender: L1, - data: Located, + sender: Sender, + data: MultiplyLocated, ) -> V where - L1: Member; + L: Subset, + Sender: Member; + + /// Performs a multicast from a location to a set of locations. + /// + /// Use `::new()` to create a value of location set. + fn multicast( + &self, + src: Sender, + destination: D, + data: &MultiplyLocated, + ) -> MultiplyLocated + where + Sender: Member, + D: Subset; + + /// Obtains a normal value from a value located at all locations in the census + fn naked(&self, data: MultiplyLocated) -> V + where + ChoreoLS: Subset; + + /// Wraps a value into a located value at the current census + fn unnaked(&self, data: V) -> MultiplyLocated; /// Calls a choreography. fn call>(&self, choreo: C) -> R where - M: LocationSet + Subset; + M: LocationSet + Subset; /// Calls a choreography on a subset of locations. - fn enclave, Index>( + fn enclave, Index>( &self, choreo: C, - ) -> R + ) -> MultiplyLocated where - S: Subset; + S: Subset; + + /// Performs parallel computation. + fn parallel( + &self, + locations: S, + computation: impl Fn() -> V, // TODO: add unwrapper for S + ) -> Faceted + where + S: Subset; + + /// Performs fanout computation. + fn fanout< + // return value type + V, + // locations looping over + QS: LocationSet, + // FanOut Choreography over L iterating over QS returning V + FOC: FanOutChoreography, + // Proof that QS is a subset of L + QSSubsetL, + QSFoldable, + >( + &self, + locations: QS, + c: FOC, + ) -> Faceted + where + QS: Subset, + QS: LocationSetFoldable; + + /// Performs fanin computation. + fn fanin< + // return value type + V, + // locations looping over + QS: LocationSet, + // Recipient locations + RS: LocationSet, + // FanIn Choreography over L iterating over QS returning V + FIC: FanInChoreography, + // Proof that QS is a subset of L + QSSubsetL, + RSSubsetL, + QSFoldable, + >( + &self, + locations: QS, + c: FIC, + ) -> MultiplyLocated, RS> + where + QS: Subset, + RS: Subset, + QS: LocationSetFoldable; +} + +/// Special choreography for fanout +pub trait FanOutChoreography { + /// All locations involved in the choreography + type L: LocationSet; + /// Locations looping over + type QS: LocationSet; + /// The body of the choreography defined in terms of the operators provided by `ChoreoOp` + /// + /// `Q` is the location that the loop variable and is guaranteed to be a member of `L` and `QS`. + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located + where + Self::QS: Subset, + Q: Member, + Q: Member; +} + +/// Special choreography for fanin +pub trait FanInChoreography { + /// All locations involved in the choreography + type L: LocationSet; + /// Locations looping over + type QS: LocationSet; + /// Recipient locations + type RS: LocationSet; + /// The body of the choreography defined in terms of the operators provided by `ChoreoOp` + /// + /// `Q` is the location that the loop variable and is guaranteed to be a member of `L` and `QS`. + fn run( + &self, + op: &impl ChoreoOp, + ) -> MultiplyLocated + where + Self::QS: Subset, + Self::RS: Subset, + Q: Member, + Q: Member; } /// Represents a choreography. @@ -325,26 +617,38 @@ pub trait Transport { } /// Provides a method to perform end-point projection. -pub struct Projector, Index> -where - L1: Member, +pub struct Projector< + // `LS` is a location set supported by the transport + // Projector is capable of projecting any choreographies whose location set is a subset of `LS` + TransportLS: LocationSet, + // `L1` is the projection target + Target: ChoreographyLocation, + // `T` is the transport that supports locations `LS` and for target `L1` + T: Transport, + Index, +> where + Target: Member, { - target: PhantomData, + target: PhantomData, transport: T, - location_set: PhantomData, + location_set: PhantomData, index: PhantomData, } -impl, Index> - Projector +impl< + TransportLS: LocationSet, + Target: ChoreographyLocation, + B: Transport, + Index, + > Projector where - L1: Member, + Target: Member, { /// Constructs a `Projector` struct. /// /// - `target` is the projection target of the choreography. /// - `transport` is an implementation of `Transport`. - pub fn new(target: L1, transport: B) -> Self { + pub fn new(target: Target, transport: B) -> Self { _ = target; Projector { target: PhantomData, @@ -357,166 +661,508 @@ where /// Constructs a `Located` struct located at the projection target using the actual value. /// /// Use this method to run a choreography that takes a located value as an input. - pub fn local(&self, value: V) -> Located { + pub fn local(&self, value: V) -> Located { Located::local(value) } /// Constructs a `Located` struct *NOT* located at the projection target. /// /// Use this method to run a choreography that takes a located value as an input. - pub fn remote(&self, at: L2) -> Located + pub fn remote(&self, _: L) -> Located where - L2: Member<>::Remainder, Index2>, + L: Member<>::Remainder, Index2>, { - _ = at; Located::remote() } + /// Construct a `Faceted` struct owned by the projection target. + /// + /// Use this method to run a choreography that takes a Faceted value as an input. + pub fn local_faceted( + &self, + value: V, + _: Owners, + ) -> Faceted + where + Target: Member, + { + Faceted { + value: HashMap::from([(String::from(Target::name()), value)]), + phantom: PhantomData, + } + } + + /// Construct a `Faceted` struct *NOT* owned by the projection target. + /// + /// Use this method to run a choreography that takes a Faceted value as an input. + pub fn remote_faceted(&self, _: Owners) -> Faceted + where + Owners: Subset<>::Remainder, Index2>, + { + Faceted { + value: HashMap::new(), + phantom: PhantomData, + } + } + /// Unwraps a located value at the projection target. /// /// Use this method to access the located value returned by a choreography. - pub fn unwrap(&self, located: Located) -> V { + pub fn unwrap(&self, located: MultiplyLocated) -> V + where + L: Subset, + Target: Member, + { located.value.unwrap() } /// Performs end-point projection and runs a choreography. - pub fn epp_and_run<'a, V, L: LocationSet, C: Choreography, IndexSet>( + pub fn epp_and_run< + 'a, + V, + // location set of the choreography to EPP + ChoreoLS: LocationSet, + C: Choreography, + IndexSet, + >( &'a self, choreo: C, ) -> V where - L: Subset, + ChoreoLS: Subset, { struct EppOp< 'a, - L: LocationSet, - L1: ChoreographyLocation, - LS: LocationSet, - B: Transport, + ChoreoLS: LocationSet, // L is a location set associated with the choreography + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, > { - target: PhantomData, + target: PhantomData, transport: &'a B, locations: Vec<&'static str>, - marker: PhantomData, - projector_location_set: PhantomData, + marker: PhantomData, + projector_location_set: PhantomData, } - impl<'a, L: LocationSet, T: ChoreographyLocation, LS: LocationSet, B: Transport> - ChoreoOp for EppOp<'a, L, T, LS, B> + impl< + 'a, + ChoreoLS: LocationSet, + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, + > ChoreoOp for EppOp<'a, ChoreoLS, Target, TransportLS, B> { fn locally( &self, _location: L1, computation: impl Fn(Unwrapper) -> V, - ) -> Located { - if L1::name() == T::name() { + ) -> MultiplyLocated { + if L1::name() == Target::name() { let unwrapper = Unwrapper { phantom: PhantomData, }; let value = computation(unwrapper); - Located::local(value) + MultiplyLocated::local(value) } else { - Located::remote() + MultiplyLocated::remote() } } fn comm< - L1: ChoreographyLocation, - L2: ChoreographyLocation, + L: LocationSet, + Sender: ChoreographyLocation, + Receiver: ChoreographyLocation, V: Portable, Index1, Index2, + Index3, >( &self, - _sender: L1, - _receiver: L2, - data: &Located, - ) -> Located { - if L1::name() == T::name() { - self.transport - .send(L1::name(), L2::name(), data.value.as_ref().unwrap()); - Located::remote() - } else if L2::name() == T::name() { - let value = self.transport.receive(L1::name(), L2::name()); - Located::local(value) + _sender: Sender, + _receiver: Receiver, + data: &MultiplyLocated, + ) -> MultiplyLocated { + if Sender::name() == Target::name() && Sender::name() == Receiver::name() { + let s = serde_json::to_string(data.value.as_ref().unwrap()).unwrap(); + return MultiplyLocated::local(serde_json::from_str(s.as_str()).unwrap()); + } + if Sender::name() == Target::name() { + self.transport.send( + Sender::name(), + Receiver::name(), + data.value.as_ref().unwrap(), + ); + MultiplyLocated::remote() + } else if Receiver::name() == Target::name() { + let value = self.transport.receive(Sender::name(), Receiver::name()); + MultiplyLocated::local(value) } else { - Located::remote() + MultiplyLocated::remote() } } - fn broadcast( + fn broadcast< + L: LocationSet, + Sender: ChoreographyLocation, + V: Portable, + Index1, + Index2, + >( &self, - _sender: L1, - data: Located, + _sender: Sender, + data: MultiplyLocated, ) -> V { - if L1::name() == T::name() { + if Sender::name() == Target::name() { for dest in &self.locations { - if T::name() != *dest { - self.transport - .send(&T::name(), &dest, data.value.as_ref().unwrap()); + if Target::name() != *dest { + self.transport.send( + &Target::name(), + &dest, + data.value.as_ref().unwrap(), + ); } } return data.value.unwrap(); } else { - self.transport.receive(L1::name(), &T::name()) + self.transport.receive(Sender::name(), &Target::name()) + } + } + + fn multicast< + Sender: ChoreographyLocation, + V: Portable, + D: LocationSet, + Index1, + Index2, + >( + &self, + _src: Sender, + _destination: D, + data: &MultiplyLocated, + ) -> MultiplyLocated { + if Sender::name() == Target::name() { + for dest in D::to_string_list() { + if Target::name() != dest { + self.transport.send( + &Target::name(), + dest, + data.value.as_ref().unwrap(), + ); + } + } + let s = serde_json::to_string(data.value.as_ref().unwrap()).unwrap(); + return MultiplyLocated::local(serde_json::from_str(s.as_str()).unwrap()); + } else { + let mut is_receiver = false; + for dest in D::to_string_list() { + if Target::name() == dest { + is_receiver = true; + } + } + if is_receiver { + let v = self.transport.receive(Sender::name(), Target::name()); + return MultiplyLocated::local(v); + } else { + return MultiplyLocated::remote(); + } } } + fn naked(&self, data: MultiplyLocated) -> V { + return data.value.unwrap(); + } + + fn unnaked(&self, data: V) -> MultiplyLocated { + return MultiplyLocated::local(data); + } + fn call>(&self, choreo: C) -> R where - M: LocationSet + Subset, + M: LocationSet + Subset, { - let op: EppOp<'a, M, T, LS, B> = EppOp { - target: PhantomData::, + let op: EppOp<'a, M, Target, TransportLS, B> = EppOp { + target: PhantomData::, transport: &self.transport, locations: self.transport.locations(), marker: PhantomData::, - projector_location_set: PhantomData::, + projector_location_set: PhantomData::, }; choreo.run(&op) } - fn enclave, Index>( + fn enclave, Index>( &self, choreo: C, - ) -> R { + ) -> MultiplyLocated { let locs_vec = S::to_string_list(); for location in &locs_vec { - if *location == T::name().to_string() { + if *location == Target::name().to_string() { let op = EppOp { - target: PhantomData::, + target: PhantomData::, transport: self.transport, locations: locs_vec, marker: PhantomData::, - projector_location_set: PhantomData::, + projector_location_set: PhantomData::, }; - return choreo.run(&op); + return MultiplyLocated::local(choreo.run(&op)); + } + } + MultiplyLocated::remote() + } + + fn parallel( + &self, + _locations: S, + computation: impl Fn() -> V, // TODO: add unwrapper for S + ) -> Faceted + where + S: Subset, + { + let mut values = HashMap::new(); + for location in S::to_string_list() { + if location == Target::name() { + let v = computation(); + values.insert(String::from(location), v); + } + } + Faceted { + value: values, + phantom: PhantomData, + } + } + + fn fanout< + // return value type + V, + // locations looping over + QS: LocationSet, + // FanOut Choreography over L iterating over QS returning V + FOC: FanOutChoreography, + // Proof that QS is a subset of L + QSSubsetL, + QSFoldable, + >( + &self, + _locations: QS, + c: FOC, + ) -> Faceted + where + QS: Subset, + QS: LocationSetFoldable, + { + let op: EppOp = EppOp { + target: PhantomData::, + transport: self.transport, + locations: self.transport.locations(), + marker: PhantomData::, + projector_location_set: PhantomData::, + }; + let values = HashMap::new(); + + struct Loop< + 'a, + ChoreoLS: LocationSet, + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, + V, + QSSubsetL, + QS: LocationSet + Subset, + FOC: FanOutChoreography, + > { + phantom: PhantomData<(V, QS, QSSubsetL, FOC)>, + op: EppOp<'a, ChoreoLS, Target, TransportLS, B>, + foc: FOC, + } + + impl< + 'a, + ChoreoLS: LocationSet, + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, + V, + QSSubsetL, + QS: LocationSet + Subset, + FOC: FanOutChoreography, + > LocationSetFolder> + for Loop<'a, ChoreoLS, Target, TransportLS, B, V, QSSubsetL, QS, FOC> + { + type L = ChoreoLS; + type QS = QS; + fn f( + &self, + mut acc: HashMap, + _: Q, + ) -> HashMap + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let v = self.foc.run::(&self.op); + match v.value { + Some(value) => { + acc.insert(String::from(Q::name()), value); + } + None => {} + }; + acc + } + } + let values = QS::foldr( + Loop:: { + phantom: PhantomData, + op, + foc: c, + }, + values, + ); + Faceted { + value: values, + phantom: PhantomData, + } + } + fn fanin< + // return value type + V, + // locations looping over + QS: LocationSet, + // Recipient locations + RS: LocationSet, + // FanIn Choreography over L iterating over QS returning V + FIC: FanInChoreography, + // Proof that QS is a subset of L + QSSubsetL, + RSSubsetL, + QSFoldable, + >( + &self, + _locations: QS, + c: FIC, + ) -> MultiplyLocated, RS> + where + QS: Subset, + RS: Subset, + QS: LocationSetFoldable, + { + let op: EppOp = EppOp { + target: PhantomData::, + transport: self.transport, + locations: self.transport.locations(), + marker: PhantomData::, + projector_location_set: PhantomData::, + }; + + struct Loop< + 'a, + ChoreoLS: LocationSet, + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, + V, + QSSubsetL, + QS: LocationSet + Subset, + RSSubsetL, + RS: LocationSet + Subset, + FIC: FanInChoreography, + > { + phantom: PhantomData<(V, QS, QSSubsetL, RS, RSSubsetL, FIC)>, + op: EppOp<'a, ChoreoLS, Target, TransportLS, B>, + fic: FIC, + } + + impl< + 'a, + ChoreoLS: LocationSet, + Target: ChoreographyLocation, + TransportLS: LocationSet, + B: Transport, + V, + QSSubsetL, + QS: LocationSet + Subset, + RSSubsetL, + RS: LocationSet + Subset, + FIC: FanInChoreography, + > LocationSetFolder> + for Loop< + 'a, + ChoreoLS, + Target, + TransportLS, + B, + V, + QSSubsetL, + QS, + RSSubsetL, + RS, + FIC, + > + { + type L = ChoreoLS; + type QS = QS; + + fn f( + &self, + mut acc: HashMap, + _: Q, + ) -> HashMap + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let v = self + .fic + .run::(&self.op); + // if the target is in RS, `v` has a value (`Some`) + match v.value { + Some(value) => { + acc.insert(String::from(Q::name()), value); + } + None => {} + } + acc } } - R::remote() + + let values = QS::foldr( + Loop:: { + phantom: PhantomData, + op, + fic: c, + }, + HashMap::new(), + ); + + MultiplyLocated::, RS>::local(Quire { + value: values, + phantom: PhantomData, + }) } } - let op: EppOp<'a, L, L1, LS, B> = EppOp { - target: PhantomData::, + let op: EppOp<'a, ChoreoLS, Target, TransportLS, B> = EppOp { + target: PhantomData::, transport: &self.transport, locations: self.transport.locations(), - marker: PhantomData::, - projector_location_set: PhantomData::, + marker: PhantomData::, + projector_location_set: PhantomData::, }; choreo.run(&op) } } /// Provides a method to run a choreography without end-point projection. -pub struct Runner { - marker: PhantomData, +pub struct Runner { + marker: PhantomData, } -impl Runner { +impl Runner { /// Constructs a runner. pub fn new() -> Self { Runner { - marker: PhantomData::, + marker: PhantomData::, } } @@ -527,6 +1173,21 @@ impl Runner { Located::local(value) } + /// Construct a `Faceted` struct from a lookup table. + /// Will almost certainly cause errors if you don't correctly populate the mapping. + /// + /// Use this method to run a choreography that takes a Faceted value as an input. + pub fn unsafe_faceted( + &self, + values: [(String, V); N], + _: Owners, + ) -> Faceted { + Faceted { + value: HashMap::from(values), + phantom: PhantomData, + } + } + /// Unwraps a located value /// /// Runner can unwrap a located value at any location @@ -535,47 +1196,82 @@ impl Runner { } /// Runs a choreography directly - pub fn run<'a, V, C: Choreography>(&'a self, choreo: C) -> V { + pub fn run<'a, V, C: Choreography>(&'a self, choreo: C) -> V { + // Note: Technically, the location set of the choreography can be a subset of `RunnerLS`. + // However, by using the same type, the compiler can infer `RunnerLS` for given choreography. + struct RunOp(PhantomData); impl ChoreoOp for RunOp { fn locally( &self, _location: L1, computation: impl Fn(Unwrapper) -> V, - ) -> Located { + ) -> MultiplyLocated { let unwrapper = Unwrapper { phantom: PhantomData, }; let value = computation(unwrapper); - Located::local(value) + MultiplyLocated::local(value) } fn comm< - L1: ChoreographyLocation, - L2: ChoreographyLocation, + S: LocationSet, + Sender: ChoreographyLocation, + Receiver: ChoreographyLocation, V: Portable, Index1, Index2, + Index3, >( &self, - _sender: L1, - _receiver: L2, - data: &Located, - ) -> Located { + _sender: Sender, + _receiver: Receiver, + data: &MultiplyLocated, + ) -> MultiplyLocated { // clone the value by encoding and decoding it. Requiring `Clone` could improve the performance but is not necessary. // Also, this is closer to what happens to the value with end-point projection. let s = serde_json::to_string(data.value.as_ref().unwrap()).unwrap(); - Located::local(serde_json::from_str(s.as_str()).unwrap()) + MultiplyLocated::local(serde_json::from_str(s.as_str()).unwrap()) } - fn broadcast( + fn broadcast< + S: LocationSet, + Sender: ChoreographyLocation, + V: Portable, + Index1, + Index2, + >( &self, - _sender: L1, - data: Located, + _sender: Sender, + data: MultiplyLocated, ) -> V { data.value.unwrap() } + fn multicast< + Sender: ChoreographyLocation, + V: Portable, + D: LocationSet, + Index1, + Index2, + >( + &self, + _src: Sender, + _destination: D, + data: &MultiplyLocated, + ) -> MultiplyLocated { + let s = serde_json::to_string(data.value.as_ref().unwrap()).unwrap(); + return MultiplyLocated::local(serde_json::from_str(s.as_str()).unwrap()); + } + + fn naked(&self, data: MultiplyLocated) -> V { + return data.value.unwrap(); + } + + fn unnaked(&self, data: V) -> MultiplyLocated { + return MultiplyLocated::local(data); + } + fn call>(&self, choreo: C) -> R where M: LocationSet + Subset, @@ -584,18 +1280,204 @@ impl Runner { choreo.run(&op) } - fn enclave, Index>( + fn enclave, Index>( &self, choreo: C, - ) -> R { + ) -> MultiplyLocated { let op = RunOp::(PhantomData); - choreo.run(&op) + MultiplyLocated::local(choreo.run(&op)) + } + + fn parallel( + &self, + _locations: S, + computation: impl Fn() -> V, // TODO: add unwrapper for S + ) -> Faceted + where + S: Subset, + { + let mut values = HashMap::new(); + for location in S::to_string_list() { + let v = computation(); + values.insert(location.to_string(), v); + } + Faceted { + value: values, + phantom: PhantomData, + } + } + fn fanout< + // return value type + V, + // locations looping over + QS: LocationSet, + // FanOut Choreography over L iterating over QS returning V + FOC: FanOutChoreography, + // Proof that QS is a subset of L + QSSubsetL, + QSFoldable, + >( + &self, + _: QS, + c: FOC, + ) -> Faceted + where + QS: Subset, + QS: LocationSetFoldable, + { + let op = RunOp::(PhantomData); + let values = HashMap::new(); + + struct Loop< + ChoreoLS: LocationSet, + V, + QSSubsetL, + QS: LocationSet + Subset, + FOC: FanOutChoreography, + > { + phantom: PhantomData<(V, QSSubsetL, QS)>, + op: RunOp, + foc: FOC, + } + + impl< + ChoreoLS: LocationSet, + V, + QSSubsetL, + QS: LocationSet + Subset, + FOC: FanOutChoreography, + > LocationSetFolder> + for Loop + { + type L = ChoreoLS; + type QS = QS; + fn f( + &self, + mut acc: HashMap, + _: Q, + ) -> HashMap + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let v = self.foc.run::(&self.op); + match v.value { + Some(value) => { + acc.insert(String::from(Q::name()), value); + } + None => {} + }; + acc + } + } + let values = QS::foldr( + Loop:: { + phantom: PhantomData, + op, + foc: c, + }, + values, + ); + Faceted { + value: values, + phantom: PhantomData, + } + } + + fn fanin< + // return value type + V, + // locations looping over + QS: LocationSet, + // Recipient locations + RS: LocationSet, + // FanIn Choreography over L iterating over QS returning V + FIC: FanInChoreography, + // Proof that QS is a subset of L + QSSubsetL, + RSSubsetL, + QSFoldable, + >( + &self, + _: QS, + c: FIC, + ) -> MultiplyLocated, RS> + where + QS: Subset, + RS: Subset, + QS: LocationSetFoldable, + { + let op: RunOp = RunOp(PhantomData); + struct Loop< + ChoreoLS: LocationSet, + V, + QSSubsetL, + QS: LocationSet + Subset, + RSSubsetL, + RS: LocationSet + Subset, + FIC: FanInChoreography, + > { + phantom: PhantomData<(V, QSSubsetL, QS, RSSubsetL, RS)>, + op: RunOp, + fic: FIC, + } + impl< + ChoreoLS: LocationSet, + V, + QSSubsetL, + QS: LocationSet + Subset, + RSSubsetL, + RS: LocationSet + Subset, + FIC: FanInChoreography, + > LocationSetFolder> + for Loop + { + type L = ChoreoLS; + type QS = QS; + + fn f( + &self, + mut acc: HashMap, + _: Q, + ) -> HashMap + where + Self::QS: Subset, + Q: Member, + Q: Member, + { + let v = self + .fic + .run::(&self.op); + // if the target is in RS, `v` has a value (`Some`) + match v.value { + Some(value) => { + acc.insert(String::from(Q::name()), value); + } + None => {} + } + acc + } + } + let values = QS::foldr( + Loop:: { + phantom: PhantomData, + op, + fic: c, + }, + HashMap::new(), + ); + + MultiplyLocated::, RS>::local(Quire { + value: values, + phantom: PhantomData, + }) } } - let op: RunOp = RunOp(PhantomData); + let op: RunOp = RunOp(PhantomData); choreo.run(&op) } } extern crate chorus_derive; -pub use chorus_derive::{ChoreographyLocation, Superposition}; +pub use chorus_derive::ChoreographyLocation; diff --git a/chorus_lib/tests/booksellers.rs b/chorus_lib/tests/booksellers.rs new file mode 100644 index 0000000..cc30349 --- /dev/null +++ b/chorus_lib/tests/booksellers.rs @@ -0,0 +1,436 @@ +extern crate chorus_lib; + +use std::collections::HashMap; +use std::marker::PhantomData; +use std::sync::Arc; +use std::thread; + +use chorus_lib::{ + core::{ + ChoreoOp, Choreography, ChoreographyLocation, Faceted, FanInChoreography, HCons, Here, + Located, LocationSet, LocationSetFoldable, Member, Projector, Quire, Runner, Subset, There, + }, + transport::local::{LocalTransport, LocalTransportChannelBuilder}, +}; +use chrono::NaiveDate; + +#[derive(ChoreographyLocation)] +struct Buyer1; + +#[derive(ChoreographyLocation)] +struct Buyer2; + +#[derive(ChoreographyLocation)] +struct Seller; + +type Money = i32; +type Title = String; + +type Inventory = HashMap; + +trait Decider { + type Budgets; + fn new(price: Located, Buyer1>, budgets: Self::Budgets) -> Self; +} + +struct Booksellers< + D: Choreography> + Decider, + Budgets, + Buyers, + B1Index, + BSIndex, +> { + inventory: Located, + title: Located, + budgets: Budgets, + _phantoms: PhantomData<(D, Buyers, B1Index, BSIndex)>, +} + +impl< + Buyers: LocationSet, + D: Choreography, L = Buyers> + Decider, + Budgets, + B1Index, + BSIndex, + > Choreography> for Booksellers +where + Buyer1: Member, + Buyer1: Member, There>, + Buyers: Subset, BSIndex>, +{ + type L = HCons; + fn run(self, op: &impl ChoreoOp) -> Option { + let title_at_seller = op.comm(Buyer1, Seller, &self.title); + let price_at_seller = op.locally(Seller, |un| { + let inventory = un.unwrap(&self.inventory); + let title = un.unwrap(&title_at_seller); + inventory.get(title).map(|(price, _)| *price) + }); + let price_at_buyer1 = op.comm(Seller, Buyer1, &price_at_seller); + let decider = D::new(price_at_buyer1, self.budgets); + let decision_at_buyer1 = op.enclave(decider).flatten(); + + struct GetDeliveryDateChoreography { + inventory: Located, + title_at_seller: Located, + decision_at_buyer1: Located, + } + impl Choreography, Buyer1>> for GetDeliveryDateChoreography { + type L = LocationSet!(Buyer1, Seller); + fn run(self, op: &impl ChoreoOp) -> Located, Buyer1> { + let decision = op.broadcast(Buyer1, self.decision_at_buyer1); + if decision { + let delivery_date_at_seller = op.locally(Seller, |un| { + let title = un.unwrap(&self.title_at_seller); + let inventory = un.unwrap(&self.inventory); + inventory + .get(title) + .map(|(_, delivery_date)| *delivery_date) + }); + let delivery_date_at_buyer1 = op.comm(Seller, Buyer1, &delivery_date_at_seller); + return delivery_date_at_buyer1; + } else { + return op.locally(Buyer1, |_| None); + } + } + } + + return op.broadcast( + Buyer1, + op.enclave(GetDeliveryDateChoreography { + inventory: self.inventory.clone(), + title_at_seller: title_at_seller.clone(), + decision_at_buyer1, + }) + .flatten(), + ); + } +} + +struct Unilateral { + price: Located, Buyer1>, + budget: Located, +} +impl Decider for Unilateral { + type Budgets = Located; + fn new(price: Located, Buyer1>, budgets: Located) -> Self { + return Self { + price: price, + budget: budgets, + }; + } +} +impl Choreography> for Unilateral { + type L = LocationSet!(Buyer1); + fn run(self, op: &impl ChoreoOp) -> Located { + op.locally(Buyer1, |un| match un.unwrap(&self.price) { + Some(price) => price <= un.unwrap(&self.budget), + None => false, + }) + } +} + +//////////////////////////////////////////////////////////////////////// +struct Colaborative { + price: Located, Buyer1>, + budgets: Faceted, + _phantoms: PhantomData<(B1Index, BSRefl, BSFld)>, +} +impl Decider + for Colaborative +where + Buyer1: Member, + Buyers: Subset, +{ + type Budgets = Faceted; + fn new(price: Located, Buyer1>, budgets: Faceted) -> Self { + return Self { + price: price, + budgets: budgets, + _phantoms: PhantomData, + }; + } +} +impl Choreography> + for Colaborative +where + Buyer1: Member, + Buyers: Subset, + Buyers: LocationSetFoldable, +{ + type L = Buyers; + fn run(self, op: &impl ChoreoOp) -> Located { + match op.broadcast(Buyer1, self.price) { + Some(price) => { + struct Gather<'a, Buyers: LocationSet, B1Index> { + budgets: &'a Faceted, + _phantoms: PhantomData, + } + impl<'a, Buyers: LocationSet, B1Index> FanInChoreography for Gather<'a, Buyers, B1Index> + where + Buyer1: Member, + { + type L = Buyers; + type QS = Buyers; + type RS = LocationSet!(Buyer1); + fn run( + &self, + op: &impl ChoreoOp, + ) -> Located + where + Self::QS: Subset, + Self::RS: Subset, + Q: Member, + Q: Member, + { + op.comm::<_, Q, Buyer1, Money, (QMemberL, _), QMemberL, B1Index>( + Q::new(), + Buyer1, + &op.locally::(Q::new(), |un| { + *un.unwrap::<_, _, QMemberQS, _>(self.budgets) + }), + ) + } + } + let budgets = op.fanin( + Buyers::new(), + Gather { + budgets: &self.budgets, + _phantoms: PhantomData, + }, + ); + + op.locally(Buyer1, |un| { + let budget = un + .unwrap::, _, Here, _>(&budgets) + .get_map() + .into_values() + .sum(); + return price <= budget; + }) + } + None => op.locally(Buyer1, |_| false), + } + } +} + +fn run_test( + inventory: Inventory, + title: Title, + budget1: Money, + budget2: Option, + answer: Option, +) { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Seller) + .with(Buyer1) + .with(Buyer2) + .build(); + let seller_projector = Arc::new(Projector::new( + Seller, + LocalTransport::new(Seller, transport_channel.clone()), + )); + let buyer1_projector = Arc::new(Projector::new( + Buyer1, + LocalTransport::new(Buyer1, transport_channel.clone()), + )); + let buyer2_projector = Arc::new(Projector::new( + Buyer2, + LocalTransport::new(Buyer2, transport_channel.clone()), + )); + let mut handles = Vec::new(); + + if let Some(budget2) = budget2 { + { + let central_runner = Runner::new(); + let choreo: Booksellers< + Colaborative, + Faceted, + LocationSet!(Buyer1, Buyer2), + _, + _, + > = Booksellers { + inventory: central_runner.local(inventory.clone()), + title: central_runner.local(title.clone()), + budgets: central_runner.unsafe_faceted( + [ + (String::from(Buyer1::name()), budget1), + (String::from(Buyer2::name()), budget2), + ], + ::new(), + ), + _phantoms: PhantomData, + }; + let central_result = central_runner.run(choreo); + assert_eq!(central_result, answer); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Colaborative, + Faceted, + LocationSet!(Buyer1, Buyer2), + _, + _, + > = Booksellers { + inventory: seller_projector.local(inventory.clone()), + title: seller_projector.remote(Buyer1), + budgets: seller_projector.remote_faceted(::new()), + _phantoms: PhantomData, + }; + seller_projector.epp_and_run(choreo) + })); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Colaborative, + Faceted, + LocationSet!(Buyer1, Buyer2), + _, + _, + > = Booksellers { + inventory: buyer1_projector.remote(Seller), + title: buyer1_projector.local(title).clone(), + budgets: buyer1_projector + .local_faceted(budget1, ::new()), + _phantoms: PhantomData, + }; + buyer1_projector.epp_and_run(choreo) + })); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Colaborative, + Faceted, + LocationSet!(Buyer1, Buyer2), + _, + _, + > = Booksellers { + inventory: buyer2_projector.remote(Seller), + title: buyer2_projector.remote(Buyer1), + budgets: buyer2_projector + .local_faceted(budget2, ::new()), + _phantoms: PhantomData, + }; + buyer2_projector.epp_and_run(choreo) + })); + } + } else { + { + let central_runner = Runner::new(); + let choreo: Booksellers< + Unilateral, + Located, + LocationSet!(Buyer1), + _, + _, + > = Booksellers { + inventory: central_runner.local(inventory.clone()), + title: central_runner.local(title.clone()), + budgets: central_runner.local(budget1), + _phantoms: PhantomData, + }; + let central_result = central_runner.run(choreo); + assert_eq!(central_result, answer); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Unilateral, + Located, + LocationSet!(Buyer1), + _, + _, + > = Booksellers { + inventory: seller_projector.local(inventory.clone()), + title: seller_projector.remote(Buyer1), + budgets: seller_projector.remote(Buyer1), + _phantoms: PhantomData, + }; + seller_projector.epp_and_run(choreo) + })); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Unilateral, + Located, + LocationSet!(Buyer1), + _, + _, + > = Booksellers { + inventory: buyer1_projector.remote(Seller), + title: buyer1_projector.local(title).clone(), + budgets: buyer1_projector.local(budget1), + _phantoms: PhantomData, + }; + buyer1_projector.epp_and_run(choreo) + })); + } + { + handles.push(thread::spawn(move || { + let choreo: Booksellers< + Unilateral, + Located, + LocationSet!(Buyer1), + _, + _, + > = Booksellers { + inventory: buyer2_projector.remote(Seller), + title: buyer2_projector.remote(Buyer1), + budgets: buyer2_projector.remote(Buyer1), + _phantoms: PhantomData, + }; + buyer2_projector.epp_and_run(choreo) + })); + } + } + + for h in handles { + assert_eq!(h.join().unwrap(), answer); + } +} + +#[test] +fn main() { + let inventory = { + let mut i = Inventory::new(); + i.insert( + "TAPL".to_string(), + (50, NaiveDate::from_ymd_opt(2023, 8, 3).unwrap()), + ); + i.insert( + "HoTT".to_string(), + (150, NaiveDate::from_ymd_opt(2023, 9, 18).unwrap()), + ); + i + }; + let tapl = "TAPL".to_string(); + let hott = "HoTT".to_string(); + run_test( + inventory.clone(), + tapl.clone(), + 100, + None, + Some(NaiveDate::from_ymd_opt(2023, 8, 3).unwrap()), + ); + run_test(inventory.clone(), hott.clone(), 25, None, None); + run_test( + inventory.clone(), + tapl.clone(), + 30, + Some(30), + Some(NaiveDate::from_ymd_opt(2023, 8, 3).unwrap()), + ); + run_test(inventory.clone(), hott.clone(), 30, Some(30), None); + run_test(inventory.clone(), "nonesuch".to_string(), 25, None, None); + run_test( + inventory.clone(), + "nonesuch".to_string(), + 30, + Some(30), + None, + ); +} diff --git a/chorus_lib/tests/kvs.rs b/chorus_lib/tests/kvs.rs new file mode 100644 index 0000000..959990d --- /dev/null +++ b/chorus_lib/tests/kvs.rs @@ -0,0 +1,234 @@ +extern crate chorus_lib; + +use std::marker::PhantomData; +use std::thread; + +use chorus_lib::core::{ + ChoreoOp, Choreography, ChoreographyLocation, Deserialize, Faceted, FanInChoreography, HCons, + HNil, Located, LocationSet, LocationSetFoldable, Member, MultiplyLocated, Portable, Projector, + Serialize, Subset, +}; +use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder}; + +type Response = i32; +type Key = String; + +#[derive(Serialize, Deserialize)] +enum Request { + Get(Key), + Put(Key, i32), +} + +fn handle_get(key: Key) -> Response { + key.len().try_into().unwrap() +} + +fn handle_put(key: Key, val: i32) -> Response { + (val != handle_get(key)) as Response +} + +#[derive(ChoreographyLocation, Debug)] +struct Client; + +#[derive(ChoreographyLocation, Debug)] +struct Server; + +#[derive(ChoreographyLocation, Debug)] +struct Backup1; + +#[derive(ChoreographyLocation, Debug)] +struct Backup2; + +// This should perhaps be in core? +struct Gather< + 'a, + V, + Senders: LocationSet + Subset, + Recievers: LocationSet + Subset, + Census: LocationSet, + SendersPresent, + RecieversPresent, +> { + values: &'a Faceted, + phantom: PhantomData<(Census, SendersPresent, Recievers, RecieversPresent)>, +} +impl< + 'a, + V: Portable + Copy, + Senders: LocationSet + Subset, + Recievers: LocationSet + Subset, + Census: LocationSet, + SendersPresent, + RecieversPresent, + > FanInChoreography + for Gather<'a, V, Senders, Recievers, Census, SendersPresent, RecieversPresent> +{ + type L = Census; + type QS = Senders; + type RS = Recievers; + fn run< + Sender: ChoreographyLocation, + _SendersPresent, + _RecieversPresent, + SenderPresent, + SenderInSenders, + >( + &self, + op: &impl ChoreoOp, + ) -> MultiplyLocated + where + Self::QS: Subset, + Self::RS: Subset, + Sender: Member, + Sender: Member, + { + let x = op.locally(Sender::new(), |un| *un.unwrap(self.values)); + let x = op.multicast::( + Sender::new(), + ::new(), + &x, + ); + x + } +} + +struct HandleRequest { + request: Located, + _phantoms: PhantomData<(Backups, BackupsPresent, BSpine)>, +} +impl Choreography> + for HandleRequest +where + Backups: Subset, BackupsPresent>, + Backups: LocationSetFoldable, Backups, BSpine>, +{ + type L = HCons; + fn run(self, op: &impl ChoreoOp) -> Located { + match op.broadcast(Server, self.request) { + Request::Put(key, value) => { + let oks = op.parallel(Backups::new(), || handle_put(key.clone(), value)); + let gathered = op.fanin::, _, _, _, _>( + Backups::new(), + Gather { + values: &oks, + phantom: PhantomData, + }, + ); + op.locally(Server, |un| { + let ok = un + .unwrap(&gathered) + .get_map() + .into_values() + .all(|response| response == 0); + if ok { + return handle_put(key.clone(), value); + } else { + return -1; + } + }) + } + Request::Get(key) => op.locally(Server, |_| handle_get(key.clone())), + } + } +} + +struct KVS { + request: Located, + _phantoms: PhantomData<(Backups, BackupsPresent, BackupsAreServers, BSpine)>, +} +impl + Choreography> + for KVS +where + Backups: Subset>, BackupsPresent>, + Backups: Subset, BackupsAreServers>, + Backups: LocationSetFoldable, Backups, BSpine>, +{ + type L = HCons>; + fn run(self, op: &impl ChoreoOp) -> Located { + let request = op.comm(Client, Server, &self.request); + let response = op + .enclave(HandleRequest:: { + request: request, + _phantoms: PhantomData, + }) + .flatten(); + op.comm(Server, Client, &response) + } +} + +fn run_test(request: Request, answer: Response) { + let transport_channel = LocalTransportChannelBuilder::new() + .with(Client) + .with(Server) + .with(Backup1) + .with(Backup2) + .build(); + let transport_client = LocalTransport::new(Client, transport_channel.clone()); + let transport_server = LocalTransport::new(Server, transport_channel.clone()); + let transport_backup1 = LocalTransport::new(Backup1, transport_channel.clone()); + let transport_backup2 = LocalTransport::new(Backup2, transport_channel.clone()); + + let client_projector = Projector::new(Client, transport_client); + let server_projector = Projector::new(Server, transport_server); + let backup1_projector = Projector::new(Backup1, transport_backup1); + let backup2_projector = Projector::new(Backup2, transport_backup2); + + let mut handles: Vec>> = Vec::new(); + handles.push( + thread::Builder::new() + .name("Server".to_string()) + .spawn(move || { + server_projector.epp_and_run(KVS::>, _, _, _> { + request: server_projector.remote(Client), + _phantoms: PhantomData, + }) + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Backup1".to_string()) + .spawn(move || { + backup1_projector.epp_and_run( + KVS::>, _, _, _> { + request: backup1_projector.remote(Client), + _phantoms: PhantomData, + }, + ) + }) + .unwrap(), + ); + handles.push( + thread::Builder::new() + .name("Backup2".to_string()) + .spawn(move || { + backup2_projector.epp_and_run( + KVS::>, _, _, _> { + request: backup2_projector.remote(Client), + _phantoms: PhantomData, + }, + ) + }) + .unwrap(), + ); + let retval = + client_projector.epp_and_run(KVS::>, _, _, _> { + request: client_projector.local(request), + _phantoms: PhantomData, + }); + for handle in handles { + handle.join().unwrap(); + } + assert_eq!(client_projector.unwrap(retval), answer); +} + +#[test] +fn main() { + let two = "xx".to_string(); + let three = "xxx".to_string(); + run_test(Request::Get(two.clone()), 2); + run_test(Request::Get(three.clone()), 3); + run_test(Request::Put(two.clone(), 2), 0); + run_test(Request::Put(three.clone(), 2), -1); +}