Skip to content

Commit fbd08e9

Browse files
authored
Implement Witty traits for slices (#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)
1 parent be9d398 commit fbd08e9

File tree

9 files changed

+860
-93
lines changed

9 files changed

+860
-93
lines changed

linera-witty/src/type_traits/implementations/std/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ mod phantom_data;
9696
mod primitives;
9797
mod rc;
9898
mod result;
99+
mod slices;
99100
mod string;
100101
mod sync;
101102
mod time;

linera-witty/src/type_traits/implementations/std/primitives.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,25 @@ impl WitStore for bool {
8080

8181
impl<'t, T> WitType for &'t T
8282
where
83-
T: WitType,
83+
T: WitType + ?Sized,
8484
{
8585
const SIZE: u32 = T::SIZE;
8686

8787
type Layout = T::Layout;
88-
type Dependencies = HList![];
88+
type Dependencies = T::Dependencies;
8989

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

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

9999
impl<'t, T> WitStore for &'t T
100100
where
101-
T: WitStore,
101+
T: WitStore + ?Sized,
102102
{
103103
fn store<Instance>(
104104
&self,
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Copyright (c) Zefchain Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Implementations of the custom traits for slice types.
5+
6+
use std::{borrow::Cow, ops::Deref, rc::Rc, sync::Arc};
7+
8+
use frunk::{hlist, hlist_pat, HList};
9+
10+
use crate::{
11+
GuestPointer, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory,
12+
WitLoad, WitStore, WitType,
13+
};
14+
15+
/// A macro to implement [`WitType`], [`WitLoad`] and [`WitStore`] for a slice wrapper type.
16+
///
17+
/// This assumes that:
18+
/// - The type is `$wrapper<[T]>`
19+
/// - The type implements `From<Box<[T]>>`
20+
/// - The type implements `Deref<Target = [T]>`
21+
macro_rules! impl_wit_traits_for_slice_wrapper {
22+
($wrapper:ident) => {
23+
impl_wit_type_as_slice!($wrapper);
24+
impl_wit_store_as_slice!($wrapper);
25+
impl_wit_load_as_boxed_slice!($wrapper);
26+
};
27+
}
28+
29+
/// A macro to implement [`WitType`] for a slice wrapper type.
30+
///
31+
/// This assumes that:
32+
/// - The type is `$wrapper<[T]>`
33+
macro_rules! impl_wit_type_as_slice {
34+
($wrapper:ident) => {
35+
impl<T> WitType for $wrapper<[T]>
36+
where
37+
T: WitType,
38+
{
39+
const SIZE: u32 = <[T] as WitType>::SIZE;
40+
41+
type Layout = <[T] as WitType>::Layout;
42+
type Dependencies = <[T] as WitType>::Dependencies;
43+
44+
fn wit_type_name() -> Cow<'static, str> {
45+
<[T] as WitType>::wit_type_name()
46+
}
47+
48+
fn wit_type_declaration() -> Cow<'static, str> {
49+
<[T] as WitType>::wit_type_declaration()
50+
}
51+
}
52+
};
53+
}
54+
55+
/// A macro to implement [`WitStore`] for a slice wrapper type.
56+
///
57+
/// This assumes that:
58+
/// - The type is `$wrapper<[T]>`
59+
/// - The type implements `Deref<Target = [T]>`
60+
macro_rules! impl_wit_store_as_slice {
61+
($wrapper:ident) => {
62+
impl<T> WitStore for $wrapper<[T]>
63+
where
64+
T: WitStore,
65+
{
66+
fn store<Instance>(
67+
&self,
68+
memory: &mut Memory<'_, Instance>,
69+
location: GuestPointer,
70+
) -> Result<(), RuntimeError>
71+
where
72+
Instance: InstanceWithMemory,
73+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
74+
{
75+
self.deref().store(memory, location)
76+
}
77+
78+
fn lower<Instance>(
79+
&self,
80+
memory: &mut Memory<'_, Instance>,
81+
) -> Result<Self::Layout, RuntimeError>
82+
where
83+
Instance: InstanceWithMemory,
84+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
85+
{
86+
self.deref().lower(memory)
87+
}
88+
}
89+
};
90+
}
91+
92+
/// A macro to implement [`WitLoad`] for a slice wrapper type.
93+
///
94+
/// This assumes that:
95+
/// - The type is `$wrapper<[T]>`
96+
/// - The type implements `From<Box<[T]>>`
97+
macro_rules! impl_wit_load_as_boxed_slice {
98+
($wrapper:ident) => {
99+
impl<T> WitLoad for $wrapper<[T]>
100+
where
101+
T: WitLoad,
102+
{
103+
fn load<Instance>(
104+
memory: &Memory<'_, Instance>,
105+
location: GuestPointer,
106+
) -> Result<Self, RuntimeError>
107+
where
108+
Instance: InstanceWithMemory,
109+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
110+
{
111+
<Box<[T]> as WitLoad>::load(memory, location).map($wrapper::from)
112+
}
113+
114+
fn lift_from<Instance>(
115+
flat_layout: <Self::Layout as Layout>::Flat,
116+
memory: &Memory<'_, Instance>,
117+
) -> Result<Self, RuntimeError>
118+
where
119+
Instance: InstanceWithMemory,
120+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
121+
{
122+
<Box<[T]> as WitLoad>::lift_from(flat_layout, memory).map($wrapper::from)
123+
}
124+
}
125+
};
126+
}
127+
128+
impl<T> WitType for [T]
129+
where
130+
T: WitType,
131+
{
132+
const SIZE: u32 = 8;
133+
134+
type Layout = HList![i32, i32];
135+
type Dependencies = HList![T];
136+
137+
fn wit_type_name() -> Cow<'static, str> {
138+
format!("list<{}>", T::wit_type_name()).into()
139+
}
140+
141+
fn wit_type_declaration() -> Cow<'static, str> {
142+
// The native `list` type doesn't need to be declared
143+
"".into()
144+
}
145+
}
146+
147+
impl<T> WitStore for [T]
148+
where
149+
T: WitStore,
150+
{
151+
fn store<Instance>(
152+
&self,
153+
memory: &mut Memory<'_, Instance>,
154+
location: GuestPointer,
155+
) -> Result<(), RuntimeError>
156+
where
157+
Instance: InstanceWithMemory,
158+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
159+
{
160+
// There's no need to account for padding between the elements, because the element's size
161+
// is always aligned:
162+
// https://github.com/WebAssembly/component-model/blob/cbdd15d9033446558571824af52a78022aaa3f58/design/mvp/CanonicalABI.md#element-size
163+
let length = u32::try_from(self.len())?;
164+
let size = length * T::SIZE;
165+
166+
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?;
167+
168+
destination.store(memory, location)?;
169+
length.store(memory, location.after::<GuestPointer>())?;
170+
171+
self.iter()
172+
.zip(0..)
173+
.try_for_each(|(element, index)| element.store(memory, destination.index::<T>(index)))
174+
}
175+
176+
fn lower<Instance>(
177+
&self,
178+
memory: &mut Memory<'_, Instance>,
179+
) -> Result<Self::Layout, RuntimeError>
180+
where
181+
Instance: InstanceWithMemory,
182+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
183+
{
184+
// There's no need to account for padding between the elements, because the element's size
185+
// is always aligned:
186+
// https://github.com/WebAssembly/component-model/blob/cbdd15d9033446558571824af52a78022aaa3f58/design/mvp/CanonicalABI.md#element-size
187+
let length = u32::try_from(self.len())?;
188+
let size = length * T::SIZE;
189+
190+
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?;
191+
192+
self.iter().zip(0..).try_for_each(|(element, index)| {
193+
element.store(memory, destination.index::<T>(index))
194+
})?;
195+
196+
Ok(destination.lower(memory)? + hlist![length as i32])
197+
}
198+
}
199+
200+
impl_wit_type_as_slice!(Box);
201+
impl_wit_store_as_slice!(Box);
202+
203+
impl<T> WitLoad for Box<[T]>
204+
where
205+
T: WitLoad,
206+
{
207+
fn load<Instance>(
208+
memory: &Memory<'_, Instance>,
209+
location: GuestPointer,
210+
) -> Result<Self, RuntimeError>
211+
where
212+
Instance: InstanceWithMemory,
213+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
214+
{
215+
let address = GuestPointer::load(memory, location)?;
216+
let length = u32::load(memory, location.after::<GuestPointer>())?;
217+
218+
(0..length)
219+
.map(|index| T::load(memory, address.index::<T>(index)))
220+
.collect()
221+
}
222+
223+
fn lift_from<Instance>(
224+
hlist_pat![address, length]: <Self::Layout as Layout>::Flat,
225+
memory: &Memory<'_, Instance>,
226+
) -> Result<Self, RuntimeError>
227+
where
228+
Instance: InstanceWithMemory,
229+
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
230+
{
231+
let address = GuestPointer(address.try_into()?);
232+
let length = length as u32;
233+
234+
(0..length)
235+
.map(|index| T::load(memory, address.index::<T>(index)))
236+
.collect()
237+
}
238+
}
239+
240+
impl_wit_traits_for_slice_wrapper!(Rc);
241+
impl_wit_traits_for_slice_wrapper!(Arc);

linera-witty/src/type_traits/implementations/std/vec.rs

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
//! Implementations of the custom traits for the [`Vec`] type.
55
6-
use std::borrow::Cow;
7-
8-
use frunk::{hlist, hlist_pat, HList};
6+
use std::{borrow::Cow, ops::Deref};
97

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

21-
type Layout = HList![i32, i32];
22-
type Dependencies = HList![T];
19+
type Layout = <[T] as WitType>::Layout;
20+
type Dependencies = <[T] as WitType>::Dependencies;
2321

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

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

@@ -43,28 +40,18 @@ where
4340
Instance: InstanceWithMemory,
4441
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
4542
{
46-
let address = GuestPointer::load(memory, location)?;
47-
let length = u32::load(memory, location.after::<GuestPointer>())?;
48-
49-
(0..length)
50-
.map(|index| T::load(memory, address.index::<T>(index)))
51-
.collect()
43+
Box::load(memory, location).map(Vec::from)
5244
}
5345

5446
fn lift_from<Instance>(
55-
hlist_pat![address, length]: <Self::Layout as Layout>::Flat,
47+
flat_layout: <Self::Layout as Layout>::Flat,
5648
memory: &Memory<'_, Instance>,
5749
) -> Result<Self, RuntimeError>
5850
where
5951
Instance: InstanceWithMemory,
6052
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
6153
{
62-
let address = GuestPointer(address.try_into()?);
63-
let length = length as u32;
64-
65-
(0..length)
66-
.map(|index| T::load(memory, address.index::<T>(index)))
67-
.collect()
54+
Box::lift_from(flat_layout, memory).map(Vec::from)
6855
}
6956
}
7057

@@ -81,17 +68,7 @@ where
8168
Instance: InstanceWithMemory,
8269
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
8370
{
84-
let length = u32::try_from(self.len())?;
85-
let size = length * T::SIZE;
86-
87-
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?;
88-
89-
destination.store(memory, location)?;
90-
length.store(memory, location.after::<GuestPointer>())?;
91-
92-
self.iter()
93-
.zip(0..)
94-
.try_for_each(|(element, index)| element.store(memory, destination.index::<T>(index)))
71+
self.deref().store(memory, location)
9572
}
9673

9774
fn lower<Instance>(
@@ -102,15 +79,6 @@ where
10279
Instance: InstanceWithMemory,
10380
<Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
10481
{
105-
let length = u32::try_from(self.len())?;
106-
let size = length * T::SIZE;
107-
108-
let destination = memory.allocate(size, <T::Layout as Layout>::ALIGNMENT)?;
109-
110-
self.iter().zip(0..).try_for_each(|(element, index)| {
111-
element.store(memory, destination.index::<T>(index))
112-
})?;
113-
114-
Ok(destination.lower(memory)? + hlist![length as i32])
82+
self.deref().lower(memory)
11583
}
11684
}

0 commit comments

Comments
 (0)