Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
13113d8
Add shared memories
abrown May 20, 2022
8e88076
review: remove thread feature check
abrown May 26, 2022
6281c86
review: swap wasmtime-types dependency for existing wasmtime-environ use
abrown May 26, 2022
9a6d871
review: remove unused VMMemoryUnion
abrown May 26, 2022
234ea40
review: reword cross-engine error message
abrown May 26, 2022
681a627
review: improve tests
abrown May 26, 2022
41a5016
review: refactor to separate prevent Memory <-> SharedMemory conversion
abrown May 31, 2022
4cc2ddf
review: into_shared_memory -> as_shared_memory
abrown May 31, 2022
9cc4db0
review: remove commented out code
abrown May 31, 2022
7a723e1
review: limit shared min/max to 32 bits
abrown May 31, 2022
04aaf4f
review: skip imported memories
abrown May 31, 2022
26c22f5
review: imported memories are not owned
abrown May 31, 2022
188cb65
review: remove TODO
abrown May 31, 2022
06dbb4d
review: document unsafe send + sync
abrown May 31, 2022
2f9d139
review: add limiter assertion
abrown May 31, 2022
e236a24
review: remove TODO
abrown May 31, 2022
0b1f51b
review: improve tests
abrown May 31, 2022
90d452e
review: fix doc test
abrown May 31, 2022
7402dc1
fix: fixes based on discussion with Alex
abrown Jun 1, 2022
f79b322
review: add `Extern::SharedMemory`
abrown Jun 1, 2022
25f4a4e
review: remove TODO
abrown Jun 1, 2022
09f8d3d
review: atomically load from VMMemoryDescription in JIT-generated code
abrown Jun 1, 2022
6669977
review: add test probing the last available memory slot across threads
abrown Jun 2, 2022
bee0ece
fix: move assertion to new location due to rebase
abrown Jun 2, 2022
12420e4
fix: doc link
abrown Jun 2, 2022
1be4ad0
fix: add TODOs to c-api
abrown Jun 2, 2022
e44a58a
fix: broken doc link
abrown Jun 2, 2022
14ce663
fix: modify pooling allocator messages in tests
abrown Jun 2, 2022
f087502
review: make owned_memory_index panic instead of returning an option
abrown Jun 6, 2022
b8ef602
review: clarify calculation of num_owned_memories
abrown Jun 6, 2022
e37f881
review: move 'use' to top of file
abrown Jun 6, 2022
87da77a
review: change '*const [u8]' to '*mut [u8]'
abrown Jun 6, 2022
17f8961
review: remove TODO
abrown Jun 6, 2022
8d981bf
review: avoid hard-coding memory index
abrown Jun 6, 2022
b154a16
review: remove 'preallocation' parameter from 'Memory::_new'
abrown Jun 7, 2022
e90c3ea
fix: component model memory length
abrown Jun 7, 2022
bd6e799
review: check that shared memory plans are static
abrown Jun 7, 2022
2bdaaed
review: ignore growth limits for shared memory
abrown Jun 7, 2022
19f3081
review: improve atomic store comment
abrown Jun 7, 2022
0a13a7d
review: add FIXME for memory growth failure
abrown Jun 7, 2022
75ddcd1
review: add comment about absence of bounds-checked 'memory.size'
abrown Jun 7, 2022
a96fccd
review: make 'current_length()' doc comment more precise
abrown Jun 7, 2022
480a1e1
review: more comments related to memory.size non-determinism
abrown Jun 7, 2022
be221c7
review: make 'vmmemory' unreachable for shared memory
abrown Jun 7, 2022
6868519
review: move code around
abrown Jun 7, 2022
e8101a0
review: thread plan through to 'wrap()'
abrown Jun 7, 2022
9e71b72
review: disallow shared memory allocation with the pooling allocator
abrown Jun 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/c-api/src/extern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub extern "C" fn wasm_extern_kind(e: &wasm_extern_t) -> wasm_externkind_t {
Extern::Global(_) => crate::WASM_EXTERN_GLOBAL,
Extern::Table(_) => crate::WASM_EXTERN_TABLE,
Extern::Memory(_) => crate::WASM_EXTERN_MEMORY,
Extern::SharedMemory(_) => todo!(),
}
}

Expand Down Expand Up @@ -119,6 +120,7 @@ impl From<Extern> for wasmtime_extern_t {
kind: WASMTIME_EXTERN_MEMORY,
of: wasmtime_extern_union { memory },
},
Extern::SharedMemory(_memory) => todo!(),
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions crates/cranelift/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use cranelift_codegen::{MachSrcLoc, MachStackMap};
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::FunctionBuilder;
use cranelift_wasm::{
DefinedFuncIndex, DefinedMemoryIndex, FuncIndex, FuncTranslator, MemoryIndex, SignatureIndex,
DefinedFuncIndex, FuncIndex, FuncTranslator, MemoryIndex, OwnedMemoryIndex, SignatureIndex,
WasmFuncType,
};
use object::write::{Object, StandardSegment, SymbolId};
Expand Down Expand Up @@ -711,8 +711,18 @@ impl Compiler {
let memory_offset = if ofs.num_imported_memories > 0 {
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
} else if ofs.num_defined_memories > 0 {
// The addition of shared memory makes the following assumption,
// "owned memory index = 0", possibly false. If the first memory
// is a shared memory, the base pointer will not be stored in
// the `owned_memories` array. The following code should
// eventually be fixed to not only handle shared memories but
// also multiple memories.
assert_eq!(
ofs.num_defined_memories, ofs.num_owned_memories,
"the memory base pointer may be incorrect due to sharing memory"
);
ModuleMemoryOffset::Defined(
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)),
ofs.vmctx_vmmemory_definition_base(OwnedMemoryIndex::new(0)),
)
} else {
ModuleMemoryOffset::None
Expand Down
100 changes: 78 additions & 22 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1368,18 +1368,37 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m

fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult<ir::Heap> {
let pointer_type = self.pointer_type();

let is_shared = self.module.memory_plans[index].memory.shared;
let (ptr, base_offset, current_length_offset) = {
let vmctx = self.vmctx(func);
if let Some(def_index) = self.module.defined_memory_index(index) {
let base_offset =
i32::try_from(self.offsets.vmctx_vmmemory_definition_base(def_index)).unwrap();
let current_length_offset = i32::try_from(
self.offsets
.vmctx_vmmemory_definition_current_length(def_index),
)
.unwrap();
(vmctx, base_offset, current_length_offset)
if is_shared {
// As with imported memory, the `VMMemoryDefinition` for a
// shared memory is stored elsewhere. We store a `*mut
// VMMemoryDefinition` to it and dereference that when
// atomically growing it.
let from_offset = self.offsets.vmctx_vmmemory_pointer(def_index);
let memory = func.create_global_value(ir::GlobalValueData::Load {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got me thinking "does this need to be an atomic load?" and I think the answer is no because shared memories never have their base pointer change (so it's write-once only during initialization). That being said though one thing this did remind me of is that the current_length should never be used here. I think it's only used for dynamic memories, so could this have an assert below that the dynamic memory branch is never taken if the memory is shared?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand what you mean by "dynamic memory branch" here... there is an if is_shared ... above?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the sense of "make sure we don't feed current_length to the heap-access machinery in Cranelift"-safety, we could maybe make current_length_offset an Option here, and return None for the shared case. Then below wherever we use it, unwrap it with "expected current-length field (always present on non-shared memories)" or something of the sort as an expect error?

base: vmctx,
offset: Offset32::new(i32::try_from(from_offset).unwrap()),
global_type: pointer_type,
readonly: true,
});
let base_offset = i32::from(self.offsets.vmmemory_definition_base());
let current_length_offset =
i32::from(self.offsets.vmmemory_definition_current_length());
(memory, base_offset, current_length_offset)
} else {
let owned_index = self.module.owned_memory_index(def_index);
let owned_base_offset =
self.offsets.vmctx_vmmemory_definition_base(owned_index);
let owned_length_offset = self
.offsets
.vmctx_vmmemory_definition_current_length(owned_index);
let current_base_offset = i32::try_from(owned_base_offset).unwrap();
let current_length_offset = i32::try_from(owned_length_offset).unwrap();
(vmctx, current_base_offset, current_length_offset)
}
} else {
let from_offset = self.offsets.vmctx_vmmemory_import_from(index);
let memory = func.create_global_value(ir::GlobalValueData::Load {
Expand Down Expand Up @@ -1693,28 +1712,65 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
) -> WasmResult<ir::Value> {
let pointer_type = self.pointer_type();
let vmctx = self.vmctx(&mut pos.func);
let is_shared = self.module.memory_plans[index].memory.shared;
let base = pos.ins().global_value(pointer_type, vmctx);
let current_length_in_bytes = match self.module.defined_memory_index(index) {
Some(def_index) => {
let offset = i32::try_from(
self.offsets
.vmctx_vmmemory_definition_current_length(def_index),
)
.unwrap();
pos.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset)
if is_shared {
let offset =
i32::try_from(self.offsets.vmctx_vmmemory_pointer(def_index)).unwrap();
let vmmemory_ptr =
pos.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset);
let vmmemory_definition_offset =
i64::from(self.offsets.vmmemory_definition_current_length());
let vmmemory_definition_ptr =
pos.ins().iadd_imm(vmmemory_ptr, vmmemory_definition_offset);
// This atomic access of the
// `VMMemoryDefinition::current_length` is direct; no bounds
// check is needed. This is possible because shared memory
// has a static size (the maximum is always known). Shared
// memory is thus built with a static memory plan and no
// bounds-checked version of this is implemented.
pos.ins().atomic_load(
Comment thread
abrown marked this conversation as resolved.
pointer_type,
ir::MemFlags::trusted(),
vmmemory_definition_ptr,
)
} else {
let owned_index = self.module.owned_memory_index(def_index);
let offset = i32::try_from(
self.offsets
.vmctx_vmmemory_definition_current_length(owned_index),
)
.unwrap();
pos.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset)
}
}
None => {
let offset = i32::try_from(self.offsets.vmctx_vmmemory_import_from(index)).unwrap();
let vmmemory_ptr =
pos.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset);
pos.ins().load(
pointer_type,
ir::MemFlags::trusted(),
vmmemory_ptr,
i32::from(self.offsets.vmmemory_definition_current_length()),
)
if is_shared {
let vmmemory_definition_offset =
i64::from(self.offsets.vmmemory_definition_current_length());
let vmmemory_definition_ptr =
pos.ins().iadd_imm(vmmemory_ptr, vmmemory_definition_offset);
pos.ins().atomic_load(
pointer_type,
ir::MemFlags::trusted(),
vmmemory_definition_ptr,
)
} else {
pos.ins().load(
pointer_type,
ir::MemFlags::trusted(),
vmmemory_ptr,
i32::from(self.offsets.vmmemory_definition_current_length()),
)
}
}
};
let current_length_in_pages = pos
Expand Down
28 changes: 25 additions & 3 deletions crates/environ/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ use std::mem;
use std::ops::Range;
use wasmtime_types::*;

/// Implemenation styles for WebAssembly linear memory.
/// Implementation styles for WebAssembly linear memory.
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub enum MemoryStyle {
/// The actual memory can be resized and moved.
Dynamic {
/// Extra space to reserve when a memory must be moved due to growth.
reserve: u64,
},
/// Addresss space is allocated up front.
/// Address space is allocated up front.
Static {
/// The number of mapped and unmapped pages.
bound: u64,
Expand Down Expand Up @@ -160,7 +160,7 @@ pub enum MemoryInitialization {
/// which might reside in a compiled module on disk, available immediately
/// in a linear memory's address space.
///
/// To facilitate the latter fo these techniques the `try_static_init`
/// To facilitate the latter of these techniques the `try_static_init`
/// function below, which creates this variant, takes a host page size
/// argument which can page-align everything to make mmap-ing possible.
Static {
Expand Down Expand Up @@ -919,6 +919,28 @@ impl Module {
}
}

/// Convert a `DefinedMemoryIndex` into an `OwnedMemoryIndex`. Returns None
/// if the index is an imported memory.
#[inline]
pub fn owned_memory_index(&self, memory: DefinedMemoryIndex) -> OwnedMemoryIndex {
assert!(
memory.index() < self.memory_plans.len(),
"non-shared memory must have an owned index"
);

// Once we know that the memory index is not greater than the number of
// plans, we can iterate through the plans up to the memory index and
// count how many are not shared (i.e., owned).
let owned_memory_index = self
.memory_plans
.iter()
.skip(self.num_imported_memories)
.take(memory.index())
.filter(|(_, mp)| !mp.memory.shared)
.count();
Comment thread
abrown marked this conversation as resolved.
OwnedMemoryIndex::new(owned_memory_index)
}

/// Test whether the given memory index is for an imported memory.
#[inline]
pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {
Expand Down
6 changes: 0 additions & 6 deletions crates/environ/src/module_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,6 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
EntityType::Function(sig_index)
}
TypeRef::Memory(ty) => {
if ty.shared {
return Err(WasmError::Unsupported("shared memories".to_owned()));
}
self.result.module.num_imported_memories += 1;
EntityType::Memory(ty.into())
}
Expand Down Expand Up @@ -296,9 +293,6 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {

for entry in memories {
let memory = entry?;
if memory.shared {
return Err(WasmError::Unsupported("shared memories".to_owned()));
}
let plan = MemoryPlan::for_memory(memory.into(), &self.tunables);
self.result.module.memory_plans.push(plan);
}
Expand Down
Loading