Skip to content

Commit

Permalink
Implement Witty traits for slices (linera-io#3123)
Browse files Browse the repository at this point in the history
## 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
jvff authored Jan 14, 2025
1 parent be9d398 commit fbd08e9
Show file tree
Hide file tree
Showing 9 changed files with 860 additions and 93 deletions.
1 change: 1 addition & 0 deletions linera-witty/src/type_traits/implementations/std/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ mod phantom_data;
mod primitives;
mod rc;
mod result;
mod slices;
mod string;
mod sync;
mod time;
Expand Down
10 changes: 5 additions & 5 deletions linera-witty/src/type_traits/implementations/std/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,25 @@ impl WitStore for bool {

impl<'t, T> WitType for &'t T
where
T: WitType,
T: WitType + ?Sized,
{
const SIZE: u32 = T::SIZE;

type Layout = T::Layout;
type Dependencies = HList![];
type Dependencies = T::Dependencies;

fn wit_type_name() -> Cow<'static, str> {
panic!("Borrowed values can't be used in WIT files generated with Witty");
T::wit_type_name()
}

fn wit_type_declaration() -> Cow<'static, str> {
panic!("Borrowed values can't be used in WIT files generated with Witty");
T::wit_type_declaration()
}
}

impl<'t, T> WitStore for &'t T
where
T: WitStore,
T: WitStore + ?Sized,
{
fn store<Instance>(
&self,
Expand Down
241 changes: 241 additions & 0 deletions linera-witty/src/type_traits/implementations/std/slices.rs
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);
54 changes: 11 additions & 43 deletions linera-witty/src/type_traits/implementations/std/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

//! Implementations of the custom traits for the [`Vec`] type.
use std::borrow::Cow;

use frunk::{hlist, hlist_pat, HList};
use std::{borrow::Cow, ops::Deref};

use crate::{
GuestPointer, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory,
Expand All @@ -16,18 +14,17 @@ impl<T> WitType for Vec<T>
where
T: WitType,
{
const SIZE: u32 = 8;
const SIZE: u32 = <[T] as WitType>::SIZE;

type Layout = HList![i32, i32];
type Dependencies = HList![T];
type Layout = <[T] as WitType>::Layout;
type Dependencies = <[T] as WitType>::Dependencies;

fn wit_type_name() -> Cow<'static, str> {
format!("list<{}>", T::wit_type_name()).into()
<[T] as WitType>::wit_type_name()
}

fn wit_type_declaration() -> Cow<'static, str> {
// The native `list` type doesn't need to be declared
"".into()
<[T] as WitType>::wit_type_declaration()
}
}

Expand All @@ -43,28 +40,18 @@ 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()
Box::load(memory, location).map(Vec::from)
}

fn lift_from<Instance>(
hlist_pat![address, length]: <Self::Layout as Layout>::Flat,
flat_layout: <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()
Box::lift_from(flat_layout, memory).map(Vec::from)
}
}

Expand All @@ -81,17 +68,7 @@ where
Instance: InstanceWithMemory,
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
{
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)))
self.deref().store(memory, location)
}

fn lower<Instance>(
Expand All @@ -102,15 +79,6 @@ where
Instance: InstanceWithMemory,
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
{
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])
self.deref().lower(memory)
}
}
Loading

0 comments on commit fbd08e9

Please sign in to comment.