linera_witty/runtime/
memory.rs1use std::borrow::Cow;
7
8use frunk::{hlist, hlist_pat, HList};
9
10use super::{
11    traits::{CabiFreeAlias, CabiReallocAlias},
12    InstanceWithFunction, Runtime, RuntimeError,
13};
14use crate::{Layout, WitType};
15
16#[cfg(test)]
17#[path = "unit_tests/memory.rs"]
18mod tests;
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub struct GuestPointer(pub(crate) u32);
23
24impl GuestPointer {
25    pub const fn aligned_at(&self, alignment: u32) -> Self {
28        let padding = (-(self.0 as i32) & (alignment as i32 - 1)) as u32;
32
33        GuestPointer(self.0 + padding)
34    }
35
36    pub const fn after<T: WitType>(&self) -> Self {
38        GuestPointer(self.0 + T::SIZE)
39    }
40
41    pub const fn after_padding_for<T: WitType>(&self) -> Self {
44        self.aligned_at(<T::Layout as Layout>::ALIGNMENT)
45    }
46
47    pub const fn index<T: WitType>(&self, index: u32) -> Self {
49        let element_size = GuestPointer(T::SIZE).after_padding_for::<T>();
50
51        GuestPointer(self.0 + index * element_size.0)
52    }
53}
54
55pub trait RuntimeMemory<Instance> {
57    fn read<'instance>(
59        &self,
60        instance: &'instance Instance,
61        location: GuestPointer,
62        length: u32,
63    ) -> Result<Cow<'instance, [u8]>, RuntimeError>;
64
65    fn write(
67        &mut self,
68        instance: &mut Instance,
69        location: GuestPointer,
70        bytes: &[u8],
71    ) -> Result<(), RuntimeError>;
72}
73
74#[expect(clippy::type_complexity)]
76pub struct Memory<'runtime, Instance>
77where
78    Instance: CabiReallocAlias + CabiFreeAlias,
79{
80    instance: &'runtime mut Instance,
81    memory: <Instance::Runtime as Runtime>::Memory,
82    cabi_realloc: Option<
83        <Instance as InstanceWithFunction<HList![i32, i32, i32, i32], HList![i32]>>::Function,
84    >,
85    cabi_free: Option<<Instance as InstanceWithFunction<HList![i32], HList![]>>::Function>,
86}
87
88impl<'runtime, Instance> Memory<'runtime, Instance>
89where
90    Instance: CabiReallocAlias + CabiFreeAlias,
91{
92    pub(super) fn new(
94        instance: &'runtime mut Instance,
95        memory: <Instance::Runtime as Runtime>::Memory,
96    ) -> Self {
97        Memory {
98            instance,
99            memory,
100            cabi_realloc: None,
101            cabi_free: None,
102        }
103    }
104}
105
106impl<Instance> Memory<'_, Instance>
107where
108    Instance: CabiReallocAlias + CabiFreeAlias,
109    <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
110{
111    pub fn read(&self, location: GuestPointer, length: u32) -> Result<Cow<'_, [u8]>, RuntimeError> {
115        self.memory.read(&*self.instance, location, length)
116    }
117
118    pub fn write(&mut self, location: GuestPointer, bytes: &[u8]) -> Result<(), RuntimeError> {
120        self.memory.write(&mut *self.instance, location, bytes)
121    }
122
123    pub fn allocate(&mut self, size: u32, alignment: u32) -> Result<GuestPointer, RuntimeError> {
129        if self.cabi_realloc.is_none() {
130            self.cabi_realloc = Some(<Instance as InstanceWithFunction<
131                HList![i32, i32, i32, i32],
132                HList![i32],
133            >>::load_function(self.instance, "cabi_realloc")?);
134        }
135
136        let size = i32::try_from(size).map_err(|_| RuntimeError::AllocationTooLarge)?;
137        let alignment = i32::try_from(alignment).map_err(|_| RuntimeError::InvalidAlignment)?;
138
139        let cabi_realloc = self
140            .cabi_realloc
141            .as_ref()
142            .expect("`cabi_realloc` function was not loaded before it was called");
143
144        let hlist_pat![allocation_address] = self
145            .instance
146            .call(cabi_realloc, hlist![0, 0, alignment, size])?;
147
148        Ok(GuestPointer(
149            allocation_address
150                .try_into()
151                .map_err(|_| RuntimeError::AllocationFailed)?,
152        ))
153    }
154
155    pub fn deallocate(&mut self, allocation: GuestPointer) -> Result<(), RuntimeError> {
157        if self.cabi_free.is_none() {
158            self.cabi_free = Some(
159                <Instance as InstanceWithFunction<HList![i32], HList![]>>::load_function(
160                    self.instance,
161                    "cabi_free",
162                )?,
163            );
164        }
165
166        let address = allocation
167            .0
168            .try_into()
169            .map_err(|_| RuntimeError::DeallocateInvalidAddress)?;
170
171        let cabi_free = self
172            .cabi_free
173            .as_ref()
174            .expect("`cabi_free` function was not loaded before it was called");
175
176        self.instance.call(cabi_free, hlist![address])?;
177
178        Ok(())
179    }
180}