forked from linera-io/linera-protocol
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Witty traits for slices (linera-io#3123)
## Motivation <!-- Briefly describe the goal(s) of this PR. --> The WIT specification supports a `list` type, which Witty mapped only to `Vec` types. However, it's also possible to use arbitrary slices with that type. Heap slices (`Box<[T]>`, `Rc<[T]>`, `Arc<[T]>`) can be used to load and store from host to guest, while more generic slices (`&[T]`) can only be stored on the guest. ## Proposal <!-- Summarize the proposed changes and how they address the goal(s) stated above. --> Implement `WitType`, `WitLoad` and `WitStore` for all slice types. More specifically, implement `WitType` and `WitStore` for generic slices (`&[T]`) and `WitLoad` for boxed slices (`Box<[T]>`). Then delegate implementations of all other types to them (including `Vec`, in order to avoid having duplicate code that's easy to get wrong). ## Test Plan <!-- Explain how you made sure that the changes are correct and that they perform as intended. Please describe testing protocols (CI, manual tests, benchmarks, etc) in a way that others can reproduce the results. --> Unit tests were added to cover the new types. ## Release Plan <!-- If this PR targets the `main` branch, **keep the applicable lines** to indicate if you recommend the changes to be picked in release branches, SDKs, and hotfixes. This generally concerns only bug fixes. Note that altering the public protocol (e.g. transaction format, WASM syscalls) or storage formats requires a new deployment. --> - Nothing to do / These changes follow the usual release cycle. ## Links <!-- Optional section for related PRs, related issues, and other references. If needed, please create issues to track future improvements and link them here. --> - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
- Loading branch information
Showing
9 changed files
with
860 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,6 +96,7 @@ mod phantom_data; | |
mod primitives; | ||
mod rc; | ||
mod result; | ||
mod slices; | ||
mod string; | ||
mod sync; | ||
mod time; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
linera-witty/src/type_traits/implementations/std/slices.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
// Copyright (c) Zefchain Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! Implementations of the custom traits for slice types. | ||
use std::{borrow::Cow, ops::Deref, rc::Rc, sync::Arc}; | ||
|
||
use frunk::{hlist, hlist_pat, HList}; | ||
|
||
use crate::{ | ||
GuestPointer, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory, | ||
WitLoad, WitStore, WitType, | ||
}; | ||
|
||
/// A macro to implement [`WitType`], [`WitLoad`] and [`WitStore`] for a slice wrapper type. | ||
/// | ||
/// This assumes that: | ||
/// - The type is `$wrapper<[T]>` | ||
/// - The type implements `From<Box<[T]>>` | ||
/// - The type implements `Deref<Target = [T]>` | ||
macro_rules! impl_wit_traits_for_slice_wrapper { | ||
($wrapper:ident) => { | ||
impl_wit_type_as_slice!($wrapper); | ||
impl_wit_store_as_slice!($wrapper); | ||
impl_wit_load_as_boxed_slice!($wrapper); | ||
}; | ||
} | ||
|
||
/// A macro to implement [`WitType`] for a slice wrapper type. | ||
/// | ||
/// This assumes that: | ||
/// - The type is `$wrapper<[T]>` | ||
macro_rules! impl_wit_type_as_slice { | ||
($wrapper:ident) => { | ||
impl<T> WitType for $wrapper<[T]> | ||
where | ||
T: WitType, | ||
{ | ||
const SIZE: u32 = <[T] as WitType>::SIZE; | ||
|
||
type Layout = <[T] as WitType>::Layout; | ||
type Dependencies = <[T] as WitType>::Dependencies; | ||
|
||
fn wit_type_name() -> Cow<'static, str> { | ||
<[T] as WitType>::wit_type_name() | ||
} | ||
|
||
fn wit_type_declaration() -> Cow<'static, str> { | ||
<[T] as WitType>::wit_type_declaration() | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/// A macro to implement [`WitStore`] for a slice wrapper type. | ||
/// | ||
/// This assumes that: | ||
/// - The type is `$wrapper<[T]>` | ||
/// - The type implements `Deref<Target = [T]>` | ||
macro_rules! impl_wit_store_as_slice { | ||
($wrapper:ident) => { | ||
impl<T> WitStore for $wrapper<[T]> | ||
where | ||
T: WitStore, | ||
{ | ||
fn store<Instance>( | ||
&self, | ||
memory: &mut Memory<'_, Instance>, | ||
location: GuestPointer, | ||
) -> Result<(), RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
self.deref().store(memory, location) | ||
} | ||
|
||
fn lower<Instance>( | ||
&self, | ||
memory: &mut Memory<'_, Instance>, | ||
) -> Result<Self::Layout, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
self.deref().lower(memory) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/// A macro to implement [`WitLoad`] for a slice wrapper type. | ||
/// | ||
/// This assumes that: | ||
/// - The type is `$wrapper<[T]>` | ||
/// - The type implements `From<Box<[T]>>` | ||
macro_rules! impl_wit_load_as_boxed_slice { | ||
($wrapper:ident) => { | ||
impl<T> WitLoad for $wrapper<[T]> | ||
where | ||
T: WitLoad, | ||
{ | ||
fn load<Instance>( | ||
memory: &Memory<'_, Instance>, | ||
location: GuestPointer, | ||
) -> Result<Self, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
<Box<[T]> as WitLoad>::load(memory, location).map($wrapper::from) | ||
} | ||
|
||
fn lift_from<Instance>( | ||
flat_layout: <Self::Layout as Layout>::Flat, | ||
memory: &Memory<'_, Instance>, | ||
) -> Result<Self, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
<Box<[T]> as WitLoad>::lift_from(flat_layout, memory).map($wrapper::from) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
impl<T> WitType for [T] | ||
where | ||
T: WitType, | ||
{ | ||
const SIZE: u32 = 8; | ||
|
||
type Layout = HList![i32, i32]; | ||
type Dependencies = HList![T]; | ||
|
||
fn wit_type_name() -> Cow<'static, str> { | ||
format!("list<{}>", T::wit_type_name()).into() | ||
} | ||
|
||
fn wit_type_declaration() -> Cow<'static, str> { | ||
// The native `list` type doesn't need to be declared | ||
"".into() | ||
} | ||
} | ||
|
||
impl<T> WitStore for [T] | ||
where | ||
T: WitStore, | ||
{ | ||
fn store<Instance>( | ||
&self, | ||
memory: &mut Memory<'_, Instance>, | ||
location: GuestPointer, | ||
) -> Result<(), RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
// There's no need to account for padding between the elements, because the element's size | ||
// is always aligned: | ||
// https://github.com/WebAssembly/component-model/blob/cbdd15d9033446558571824af52a78022aaa3f58/design/mvp/CanonicalABI.md#element-size | ||
let length = u32::try_from(self.len())?; | ||
let size = length * T::SIZE; | ||
|
||
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?; | ||
|
||
destination.store(memory, location)?; | ||
length.store(memory, location.after::<GuestPointer>())?; | ||
|
||
self.iter() | ||
.zip(0..) | ||
.try_for_each(|(element, index)| element.store(memory, destination.index::<T>(index))) | ||
} | ||
|
||
fn lower<Instance>( | ||
&self, | ||
memory: &mut Memory<'_, Instance>, | ||
) -> Result<Self::Layout, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
// There's no need to account for padding between the elements, because the element's size | ||
// is always aligned: | ||
// https://github.com/WebAssembly/component-model/blob/cbdd15d9033446558571824af52a78022aaa3f58/design/mvp/CanonicalABI.md#element-size | ||
let length = u32::try_from(self.len())?; | ||
let size = length * T::SIZE; | ||
|
||
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?; | ||
|
||
self.iter().zip(0..).try_for_each(|(element, index)| { | ||
element.store(memory, destination.index::<T>(index)) | ||
})?; | ||
|
||
Ok(destination.lower(memory)? + hlist![length as i32]) | ||
} | ||
} | ||
|
||
impl_wit_type_as_slice!(Box); | ||
impl_wit_store_as_slice!(Box); | ||
|
||
impl<T> WitLoad for Box<[T]> | ||
where | ||
T: WitLoad, | ||
{ | ||
fn load<Instance>( | ||
memory: &Memory<'_, Instance>, | ||
location: GuestPointer, | ||
) -> Result<Self, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
let address = GuestPointer::load(memory, location)?; | ||
let length = u32::load(memory, location.after::<GuestPointer>())?; | ||
|
||
(0..length) | ||
.map(|index| T::load(memory, address.index::<T>(index))) | ||
.collect() | ||
} | ||
|
||
fn lift_from<Instance>( | ||
hlist_pat![address, length]: <Self::Layout as Layout>::Flat, | ||
memory: &Memory<'_, Instance>, | ||
) -> Result<Self, RuntimeError> | ||
where | ||
Instance: InstanceWithMemory, | ||
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>, | ||
{ | ||
let address = GuestPointer(address.try_into()?); | ||
let length = length as u32; | ||
|
||
(0..length) | ||
.map(|index| T::load(memory, address.index::<T>(index))) | ||
.collect() | ||
} | ||
} | ||
|
||
impl_wit_traits_for_slice_wrapper!(Rc); | ||
impl_wit_traits_for_slice_wrapper!(Arc); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.