wasmtime/runtime/vm/gc/enabled/data.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
//! Unchecked methods for working with the data inside GC objects.
use crate::V128;
use core::mem;
/// A plain-old-data type that can be stored in a `ValType` or a `StorageType`.
pub trait PodValType<const SIZE: usize>: Copy {
/// Read an instance of `Self` from the given native-endian bytes.
fn read_le(le_bytes: &[u8; SIZE]) -> Self;
/// Write `self` into the given memory location, as native-endian bytes.
fn write_le(&self, into: &mut [u8; SIZE]);
}
macro_rules! impl_pod_val_type {
( $( $t:ty , )* ) => {
$(
impl PodValType<{mem::size_of::<$t>()}> for $t {
fn read_le(le_bytes: &[u8; mem::size_of::<$t>()]) -> Self {
<$t>::from_le_bytes(*le_bytes)
}
fn write_le(&self, into: &mut [u8; mem::size_of::<$t>()]) {
*into = self.to_le_bytes();
}
}
)*
};
}
impl_pod_val_type! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
}
impl PodValType<{ mem::size_of::<V128>() }> for V128 {
fn read_le(le_bytes: &[u8; mem::size_of::<V128>()]) -> Self {
u128::from_le_bytes(*le_bytes).into()
}
fn write_le(&self, into: &mut [u8; mem::size_of::<V128>()]) {
*into = self.as_u128().to_le_bytes();
}
}
/// The backing storage for a GC-managed object.
///
/// Methods on this type do not, generally, check against things like type
/// mismatches or that the given offset to read from even falls on a field
/// boundary. Omitting these checks is memory safe, due to our untrusted,
/// indexed GC heaps. Providing incorrect offsets will result in general
/// incorrectness, such as wrong answers or even panics, however.
///
/// Finally, these methods *will* panic on out-of-bounds accesses, either out of
/// the GC heap's bounds or out of this object's bounds. The former is necessary
/// for preserving the memory safety of indexed GC heaps in the face of (for
/// example) collector bugs, but the latter is just a defensive technique to
/// catch bugs early and prevent action at a distance as much as possible.
pub struct VMGcObjectDataMut<'a> {
data: &'a mut [u8],
}
macro_rules! impl_pod_methods {
( $( $t:ty, $read:ident, $write:ident; )* ) => {
$(
/// Read a `
#[doc = stringify!($t)]
/// ` field this object.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn $read(&self, offset: u32) -> $t {
self.read_pod::<{ mem::size_of::<$t>() }, $t>(offset)
}
/// Write a `
#[doc = stringify!($t)]
/// ` into this object.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn $write(&mut self, offset: u32, val: $t) {
self.write_pod::<{ mem::size_of::<$t>() }, $t>(offset, val);
}
)*
};
}
impl<'a> VMGcObjectDataMut<'a> {
/// Construct a `VMStructDataMut` from the given slice of bytes.
#[inline]
pub fn new(data: &'a mut [u8]) -> Self {
Self { data }
}
/// Read a POD field out of this object.
///
/// Panics on out-of-bounds accesses.
///
/// Don't generally use this method, use `read_u8`, `read_i64`,
/// etc... instead.
#[inline]
fn read_pod<const N: usize, T>(&self, offset: u32) -> T
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let bytes = self.data.get(offset..end).expect("out of bounds field");
T::read_le(bytes.try_into().unwrap())
}
/// Read a POD field out of this object.
///
/// Panics on out-of-bounds accesses.
///
/// Don't generally use this method, use `write_u8`, `write_i64`,
/// etc... instead.
#[inline]
fn write_pod<const N: usize, T>(&mut self, offset: u32, val: T)
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let into = match self.data.get_mut(offset..end) {
Some(into) => into,
None => panic!(
"out of bounds field! field range = {offset:#x}..{end:#x}; object len = {:#x}",
self.data.len(),
),
};
val.write_le(into.try_into().unwrap());
}
/// Get a slice of this object's data.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn slice(&self, offset: u32, len: u32) -> &[u8] {
let start = usize::try_from(offset).unwrap();
let len = usize::try_from(len).unwrap();
let end = start.checked_add(len).unwrap();
self.data.get(start..end).expect("out of bounds slice")
}
/// Get a mutable slice of this object's data.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn slice_mut(&mut self, offset: u32, len: u32) -> &mut [u8] {
let start = usize::try_from(offset).unwrap();
let len = usize::try_from(len).unwrap();
let end = start.checked_add(len).unwrap();
self.data.get_mut(start..end).expect("out of bounds slice")
}
/// Copy the given slice into this object's data at the given offset.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn copy_from_slice(&mut self, offset: u32, src: &[u8]) {
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(src.len()).unwrap();
let into = self.data.get_mut(offset..end).expect("out of bounds copy");
into.copy_from_slice(src);
}
impl_pod_methods! {
u8, read_u8, write_u8;
u16, read_u16, write_u16;
u32, read_u32, write_u32;
u64, read_u64, write_u64;
i8, read_i8, write_i8;
i16, read_i16, write_i16;
i32, read_i32, write_i32;
i64, read_i64, write_i64;
V128, read_v128, write_v128;
}
}