Move wasm data sections out of wasmtime_environ::Module (#3231)
* Reduce indentation in `to_paged` Use a few early-returns from `match` to avoid lots of extra indentation. * Move wasm data sections out of `wasmtime_environ::Module` This is the first step down the road of #3230. The long-term goal is that `Module` is always `bincode`-decoded, but wasm data segments are a possibly very-large portion of this residing in modules which we don't want to shove through bincode. This refactors the internals of wasmtime to be ok with this data living separately from the `Module` itself, providing access at necessary locations. Wasm data segments are now extracted from a wasm module and concatenated directly. Data sections then describe ranges within this concatenated list of data, and passive data works the same way. This implementation does not lend itself to eventually optimizing the case where passive data is dropped and no longer needed. That's left for a future PR.
This commit is contained in:
@@ -16,9 +16,9 @@ use memoffset::offset_of;
|
||||
use more_asserts::assert_lt;
|
||||
use std::alloc::Layout;
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Range;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr, slice};
|
||||
@@ -147,6 +147,12 @@ pub(crate) struct Instance {
|
||||
/// If the index is present in the set, the segment has been dropped.
|
||||
dropped_data: EntitySet<DataIndex>,
|
||||
|
||||
/// A slice pointing to all data that is referenced by this instance. This
|
||||
/// data is managed externally so this is effectively an unsafe reference,
|
||||
/// and this does not live for the `'static` lifetime so the API boundaries
|
||||
/// here are careful to never hand out static references.
|
||||
wasm_data: &'static [u8],
|
||||
|
||||
/// Hosts can store arbitrary per-instance information here.
|
||||
///
|
||||
/// Most of the time from Wasmtime this is `Box::new(())`, a noop
|
||||
@@ -509,22 +515,6 @@ impl Instance {
|
||||
self.vmctx_plus_offset(self.offsets.vmctx_anyfuncs_begin())
|
||||
}
|
||||
|
||||
fn find_passive_segment<'a, I, D, T>(
|
||||
index: I,
|
||||
index_map: &BTreeMap<I, usize>,
|
||||
data: &'a Vec<D>,
|
||||
dropped: &EntitySet<I>,
|
||||
) -> &'a [T]
|
||||
where
|
||||
D: AsRef<[T]>,
|
||||
I: EntityRef + Ord,
|
||||
{
|
||||
match index_map.get(&index) {
|
||||
Some(index) if !dropped.contains(I::new(*index)) => data[*index].as_ref(),
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// The `table.init` operation: initializes a portion of a table with a
|
||||
/// passive element.
|
||||
///
|
||||
@@ -544,12 +534,13 @@ impl Instance {
|
||||
// inform `rustc` that the lifetime of the elements here are
|
||||
// disconnected from the lifetime of `self`.
|
||||
let module = self.module.clone();
|
||||
let elements = Self::find_passive_segment(
|
||||
elem_index,
|
||||
&module.passive_elements_map,
|
||||
&module.passive_elements,
|
||||
&self.dropped_elements,
|
||||
);
|
||||
|
||||
let elements = match module.passive_elements_map.get(&elem_index) {
|
||||
Some(index) if !self.dropped_elements.contains(elem_index) => {
|
||||
module.passive_elements[*index].as_ref()
|
||||
}
|
||||
_ => &[],
|
||||
};
|
||||
self.table_init_segment(table_index, elements, dst, src, len)
|
||||
}
|
||||
|
||||
@@ -601,9 +592,7 @@ impl Instance {
|
||||
pub(crate) fn elem_drop(&mut self, elem_index: ElemIndex) {
|
||||
// https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop
|
||||
|
||||
if let Some(index) = self.module.passive_elements_map.get(&elem_index) {
|
||||
self.dropped_elements.insert(ElemIndex::new(*index));
|
||||
}
|
||||
self.dropped_elements.insert(elem_index);
|
||||
|
||||
// Note that we don't check that we actually removed a segment because
|
||||
// dropping a non-passive segment is a no-op (not a trap).
|
||||
@@ -700,23 +689,21 @@ impl Instance {
|
||||
src: u32,
|
||||
len: u32,
|
||||
) -> Result<(), Trap> {
|
||||
// TODO: this `clone()` shouldn't be necessary but is used for now to
|
||||
// inform `rustc` that the lifetime of the elements here are
|
||||
// disconnected from the lifetime of `self`.
|
||||
let module = self.module.clone();
|
||||
let data = Self::find_passive_segment(
|
||||
data_index,
|
||||
&module.passive_data_map,
|
||||
&module.passive_data,
|
||||
&self.dropped_data,
|
||||
);
|
||||
self.memory_init_segment(memory_index, &data, dst, src, len)
|
||||
let range = match self.module.passive_data_map.get(&data_index).cloned() {
|
||||
Some(range) if !self.dropped_data.contains(data_index) => range,
|
||||
_ => 0..0,
|
||||
};
|
||||
self.memory_init_segment(memory_index, range, dst, src, len)
|
||||
}
|
||||
|
||||
pub(crate) fn wasm_data(&self, range: Range<u32>) -> &[u8] {
|
||||
&self.wasm_data[range.start as usize..range.end as usize]
|
||||
}
|
||||
|
||||
pub(crate) fn memory_init_segment(
|
||||
&mut self,
|
||||
memory_index: MemoryIndex,
|
||||
data: &[u8],
|
||||
range: Range<u32>,
|
||||
dst: u64,
|
||||
src: u32,
|
||||
len: u32,
|
||||
@@ -724,6 +711,7 @@ impl Instance {
|
||||
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init
|
||||
|
||||
let memory = self.get_memory(memory_index);
|
||||
let data = self.wasm_data(range);
|
||||
let dst = self.validate_inbounds(memory.current_length, dst, len.into())?;
|
||||
let src = self.validate_inbounds(data.len(), src.into(), len.into())?;
|
||||
let len = len as usize;
|
||||
@@ -741,9 +729,7 @@ impl Instance {
|
||||
|
||||
/// Drop the given data segment, truncating its length to zero.
|
||||
pub(crate) fn data_drop(&mut self, data_index: DataIndex) {
|
||||
if let Some(index) = self.module.passive_data_map.get(&data_index) {
|
||||
self.dropped_data.insert(DataIndex::new(*index));
|
||||
}
|
||||
self.dropped_data.insert(data_index);
|
||||
|
||||
// Note that we don't check that we actually removed a segment because
|
||||
// dropping a non-passive segment is a no-op (not a trap).
|
||||
|
||||
@@ -61,6 +61,16 @@ pub struct InstanceAllocationRequest<'a> {
|
||||
/// We use a number of `PhantomPinned` declarations to indicate this to the
|
||||
/// compiler. More info on this in `wasmtime/src/store.rs`
|
||||
pub store: Option<*mut dyn Store>,
|
||||
|
||||
/// A list of all wasm data that can be referenced by the module that
|
||||
/// will be allocated. The `Module` given here has active/passive data
|
||||
/// segments that are specified as relative indices into this list of bytes.
|
||||
///
|
||||
/// Note that this is an unsafe pointer. The pointer is expected to live for
|
||||
/// the entire duration of the instance at this time. It's the
|
||||
/// responsibility of the callee when allocating to ensure that this data
|
||||
/// outlives the instance.
|
||||
pub wasm_data: *const [u8],
|
||||
}
|
||||
|
||||
/// An link error while instantiating a module.
|
||||
@@ -337,10 +347,10 @@ fn initialize_memories(
|
||||
instance
|
||||
.memory_init_segment(
|
||||
init.memory_index,
|
||||
&init.data,
|
||||
init.data.clone(),
|
||||
get_memory_init_start(init, instance)?,
|
||||
0,
|
||||
u32::try_from(init.data.len()).unwrap(),
|
||||
init.data.end - init.data.start,
|
||||
)
|
||||
.map_err(InstantiationError::Trap)?;
|
||||
}
|
||||
@@ -391,13 +401,11 @@ fn initialize_instance(
|
||||
let slice =
|
||||
unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
|
||||
|
||||
for (page_index, page) in pages.iter().enumerate() {
|
||||
if let Some(data) = page {
|
||||
debug_assert_eq!(data.len(), WASM_PAGE_SIZE as usize);
|
||||
let start = page_index * WASM_PAGE_SIZE as usize;
|
||||
let end = start + WASM_PAGE_SIZE as usize;
|
||||
slice[start..end].copy_from_slice(data);
|
||||
}
|
||||
for (page_index, page) in pages {
|
||||
debug_assert_eq!(page.end - page.start, WASM_PAGE_SIZE);
|
||||
let start = (*page_index * u64::from(WASM_PAGE_SIZE)) as usize;
|
||||
let end = start + WASM_PAGE_SIZE as usize;
|
||||
slice[start..end].copy_from_slice(instance.wasm_data(page.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,8 +660,9 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
|
||||
memories,
|
||||
tables,
|
||||
dropped_elements: EntitySet::with_capacity(req.module.passive_elements.len()),
|
||||
dropped_data: EntitySet::with_capacity(req.module.passive_data.len()),
|
||||
dropped_data: EntitySet::with_capacity(req.module.passive_data_map.len()),
|
||||
host_state,
|
||||
wasm_data: &*req.wasm_data,
|
||||
vmctx: VMContext {
|
||||
_marker: marker::PhantomPinned,
|
||||
},
|
||||
|
||||
@@ -364,6 +364,7 @@ impl InstancePool {
|
||||
dropped_elements: EntitySet::new(),
|
||||
dropped_data: EntitySet::new(),
|
||||
host_state: Box::new(()),
|
||||
wasm_data: &[],
|
||||
vmctx: VMContext {
|
||||
_marker: marker::PhantomPinned,
|
||||
},
|
||||
@@ -382,6 +383,7 @@ impl InstancePool {
|
||||
instance.module = req.module.clone();
|
||||
instance.offsets = VMOffsets::new(HostPtr, instance.module.as_ref());
|
||||
instance.host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
||||
instance.wasm_data = &*req.wasm_data;
|
||||
|
||||
let mut limiter = req.store.and_then(|s| (*s).limiter());
|
||||
Self::set_instance_memories(
|
||||
@@ -492,6 +494,7 @@ impl InstancePool {
|
||||
// fresh allocation later on.
|
||||
instance.module = self.empty_module.clone();
|
||||
instance.offsets = VMOffsets::new(HostPtr, &self.empty_module);
|
||||
instance.wasm_data = &[];
|
||||
|
||||
self.free_list.lock().unwrap().push(index);
|
||||
}
|
||||
@@ -527,7 +530,6 @@ impl InstancePool {
|
||||
}
|
||||
|
||||
debug_assert!(instance.dropped_data.is_empty());
|
||||
instance.dropped_data.resize(module.passive_data.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1410,6 +1412,7 @@ mod test {
|
||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||
host_state: Box::new(()),
|
||||
store: None,
|
||||
wasm_data: &[],
|
||||
},
|
||||
)
|
||||
.expect("allocation should succeed"),
|
||||
@@ -1432,6 +1435,7 @@ mod test {
|
||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||
host_state: Box::new(()),
|
||||
store: None,
|
||||
wasm_data: &[],
|
||||
},
|
||||
) {
|
||||
Err(InstantiationError::Limit(3)) => {}
|
||||
|
||||
@@ -269,7 +269,9 @@ unsafe fn initialize_wasm_page(
|
||||
if let MemoryInitialization::Paged { map, .. } = &instance.module.memory_initialization {
|
||||
let pages = &map[memory_index];
|
||||
|
||||
if let Some(Some(data)) = pages.get(page_index) {
|
||||
let pos = pages.binary_search_by_key(&(page_index as u64), |k| k.0);
|
||||
if let Ok(i) = pos {
|
||||
let data = instance.wasm_data(pages[i].1.clone());
|
||||
debug_assert_eq!(data.len(), WASM_PAGE_SIZE);
|
||||
|
||||
log::trace!(
|
||||
@@ -530,6 +532,7 @@ mod test {
|
||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||
host_state: Box::new(()),
|
||||
store: None,
|
||||
wasm_data: &[],
|
||||
},
|
||||
)
|
||||
.expect("instance should allocate"),
|
||||
|
||||
Reference in New Issue
Block a user