Remove wasmer_engine_universal{,_artifact} and merge into wasmer_compiler

This commit is contained in:
Manos Pitsidianakis
2022-06-13 23:59:43 +03:00
parent 9ebe1c2e3a
commit 086205bfcc
74 changed files with 171 additions and 370 deletions

View File

@@ -28,8 +28,16 @@ memmap2 = "0.5"
more-asserts = "0.2"
lazy_static = "1.4"
cfg-if = "1.0"
leb128 = "0.2"
enum-iterator = "0.7.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
wasmer-vm = { path = "../vm", version = "=2.3.0" }
region = { version = "3.0" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winnt", "impl-default"] }
[features]
default = ["std", "enable-serde" ]
@@ -37,6 +45,7 @@ default = ["std", "enable-serde" ]
# `CompilerConfig`, as well as the included wasmparser.
# Disable this feature if you just want a headless engine.
translator = ["wasmparser"]
universal_engine = []
std = ["wasmer-types/std"]
core = ["hashbrown", "wasmer-types/core"]
enable-serde = ["serde", "serde_bytes", "wasmer-types/enable-serde"]

View File

@@ -8,6 +8,9 @@ mod resolver;
mod trap;
mod tunables;
#[cfg(feature = "translator")]
mod universal;
pub use self::artifact::Artifact;
pub use self::error::{InstantiationError, LinkError};
pub use self::export::{Export, ExportFunction, ExportFunctionMetadata};
@@ -15,3 +18,6 @@ pub use self::inner::{Engine, EngineId};
pub use self::resolver::resolve_imports;
pub use self::trap::*;
pub use self::tunables::Tunables;
#[cfg(feature = "translator")]
pub use self::universal::*;

View File

@@ -50,7 +50,7 @@ impl RuntimeError {
///
/// # Example
/// ```
/// let trap = wasmer_engine::RuntimeError::new("unexpected error");
/// let trap = wasmer_compiler::RuntimeError::new("unexpected error");
/// assert_eq!("unexpected error", trap.message());
/// ```
pub fn new<I: Into<String>>(message: I) -> Self {

View File

@@ -0,0 +1,280 @@
//! Define `UniversalArtifact`, based on `UniversalArtifactBuild`
//! to allow compiling and instantiating to be done as separate steps.
use super::engine::{UniversalEngine, UniversalEngineInner};
use crate::engine::universal::link::link_module;
use crate::ArtifactCreate;
use crate::{
register_frame_info, Artifact, FunctionExtent, GlobalFrameInfoRegistration, MetadataHeader,
};
use crate::{CpuFeature, Features, Triple};
#[cfg(feature = "universal_engine")]
use crate::{Engine, ModuleEnvironment, Tunables};
use crate::{SerializableModule, UniversalArtifactBuild};
use enumset::EnumSet;
use std::sync::{Arc, Mutex};
use wasmer_types::entity::{BoxedSlice, PrimaryMap};
use wasmer_types::{
CompileError, DeserializeError, FunctionIndex, LocalFunctionIndex, MemoryIndex, ModuleInfo,
OwnedDataInitializer, SerializeError, SignatureIndex, TableIndex,
};
use wasmer_vm::{
FuncDataRegistry, FunctionBodyPtr, MemoryStyle, TableStyle, VMSharedSignatureIndex,
VMTrampoline,
};
/// A compiled wasm module, ready to be instantiated.
pub struct UniversalArtifact {
artifact: UniversalArtifactBuild,
finished_functions: BoxedSlice<LocalFunctionIndex, FunctionBodyPtr>,
finished_function_call_trampolines: BoxedSlice<SignatureIndex, VMTrampoline>,
finished_dynamic_function_trampolines: BoxedSlice<FunctionIndex, FunctionBodyPtr>,
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
func_data_registry: Arc<FuncDataRegistry>,
frame_info_registration: Mutex<Option<GlobalFrameInfoRegistration>>,
finished_function_lengths: BoxedSlice<LocalFunctionIndex, usize>,
}
impl UniversalArtifact {
/// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated.
#[cfg(feature = "universal_engine")]
pub fn new(
engine: &UniversalEngine,
data: &[u8],
tunables: &dyn Tunables,
) -> Result<Self, CompileError> {
let environ = ModuleEnvironment::new();
let mut inner_engine = engine.inner_mut();
let translation = environ.translate(data).map_err(CompileError::Wasm)?;
let module = translation.module;
let memory_styles: PrimaryMap<MemoryIndex, MemoryStyle> = module
.memories
.values()
.map(|memory_type| tunables.memory_style(memory_type))
.collect();
let table_styles: PrimaryMap<TableIndex, TableStyle> = module
.tables
.values()
.map(|table_type| tunables.table_style(table_type))
.collect();
let artifact = UniversalArtifactBuild::new(
inner_engine.builder_mut(),
data,
engine.target(),
memory_styles,
table_styles,
)?;
Self::from_parts(&mut inner_engine, artifact)
}
/// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated.
#[cfg(not(feature = "universal_engine"))]
pub fn new(_engine: &UniversalEngine, _data: &[u8]) -> Result<Self, CompileError> {
Err(CompileError::Codegen(
"Compilation is not enabled in the engine".to_string(),
))
}
/// Deserialize a UniversalArtifactBuild
///
/// # Safety
/// This function is unsafe because rkyv reads directly without validating
/// the data.
pub unsafe fn deserialize(
engine: &UniversalEngine,
bytes: &[u8],
) -> Result<Self, DeserializeError> {
if !UniversalArtifactBuild::is_deserializable(bytes) {
return Err(DeserializeError::Incompatible(
"The provided bytes are not wasmer-universal".to_string(),
));
}
let bytes = &bytes[UniversalArtifactBuild::MAGIC_HEADER.len()..];
let metadata_len = MetadataHeader::parse(bytes)?;
let metadata_slice: &[u8] = &bytes[MetadataHeader::LEN..][..metadata_len];
let serializable = SerializableModule::deserialize(metadata_slice)?;
let artifact = UniversalArtifactBuild::from_serializable(serializable);
let mut inner_engine = engine.inner_mut();
Self::from_parts(&mut inner_engine, artifact).map_err(DeserializeError::Compiler)
}
/// Construct a `UniversalArtifactBuild` from component parts.
pub fn from_parts(
engine_inner: &mut UniversalEngineInner,
artifact: UniversalArtifactBuild,
) -> Result<Self, CompileError> {
let (
finished_functions,
finished_function_call_trampolines,
finished_dynamic_function_trampolines,
custom_sections,
) = engine_inner.allocate(
artifact.module_ref(),
artifact.get_function_bodies_ref(),
artifact.get_function_call_trampolines_ref(),
artifact.get_dynamic_function_trampolines_ref(),
artifact.get_custom_sections_ref(),
)?;
link_module(
artifact.module_ref(),
&finished_functions,
artifact.get_function_relocations(),
&custom_sections,
artifact.get_custom_section_relocations_ref(),
artifact.get_libcall_trampolines(),
artifact.get_libcall_trampoline_len(),
);
// Compute indices into the shared signature table.
let signatures = {
let signature_registry = engine_inner.signatures();
artifact
.module()
.signatures
.values()
.map(|sig| signature_registry.register(sig))
.collect::<PrimaryMap<_, _>>()
};
let eh_frame = match artifact.get_debug_ref() {
Some(debug) => {
let eh_frame_section_size = artifact.get_custom_sections_ref()[debug.eh_frame]
.bytes
.len();
let eh_frame_section_pointer = custom_sections[debug.eh_frame];
Some(unsafe {
std::slice::from_raw_parts(*eh_frame_section_pointer, eh_frame_section_size)
})
}
None => None,
};
// Make all code compiled thus far executable.
engine_inner.publish_compiled_code();
engine_inner.publish_eh_frame(eh_frame)?;
let finished_function_lengths = finished_functions
.values()
.map(|extent| extent.length)
.collect::<PrimaryMap<LocalFunctionIndex, usize>>()
.into_boxed_slice();
let finished_functions = finished_functions
.values()
.map(|extent| extent.ptr)
.collect::<PrimaryMap<LocalFunctionIndex, FunctionBodyPtr>>()
.into_boxed_slice();
let finished_function_call_trampolines =
finished_function_call_trampolines.into_boxed_slice();
let finished_dynamic_function_trampolines =
finished_dynamic_function_trampolines.into_boxed_slice();
let signatures = signatures.into_boxed_slice();
let func_data_registry = engine_inner.func_data().clone();
Ok(Self {
artifact,
finished_functions,
finished_function_call_trampolines,
finished_dynamic_function_trampolines,
signatures,
frame_info_registration: Mutex::new(None),
finished_function_lengths,
func_data_registry,
})
}
/// Get the default extension when serializing this artifact
pub fn get_default_extension(triple: &Triple) -> &'static str {
UniversalArtifactBuild::get_default_extension(triple)
}
/// Check if the provided bytes look like a serialized `UniversalArtifactBuild`.
pub fn is_deserializable(bytes: &[u8]) -> bool {
UniversalArtifactBuild::is_deserializable(bytes)
}
}
impl ArtifactCreate for UniversalArtifact {
fn module(&self) -> Arc<ModuleInfo> {
self.artifact.module()
}
fn module_ref(&self) -> &ModuleInfo {
self.artifact.module_ref()
}
fn module_mut(&mut self) -> Option<&mut ModuleInfo> {
self.artifact.module_mut()
}
fn features(&self) -> &Features {
self.artifact.features()
}
fn cpu_features(&self) -> EnumSet<CpuFeature> {
self.artifact.cpu_features()
}
fn data_initializers(&self) -> &[OwnedDataInitializer] {
self.artifact.data_initializers()
}
fn memory_styles(&self) -> &PrimaryMap<MemoryIndex, MemoryStyle> {
self.artifact.memory_styles()
}
fn table_styles(&self) -> &PrimaryMap<TableIndex, TableStyle> {
self.artifact.table_styles()
}
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
self.artifact.serialize()
}
}
impl Artifact for UniversalArtifact {
fn register_frame_info(&self) {
let mut info = self.frame_info_registration.lock().unwrap();
if info.is_some() {
return;
}
let finished_function_extents = self
.finished_functions
.values()
.copied()
.zip(self.finished_function_lengths.values().copied())
.map(|(ptr, length)| FunctionExtent { ptr, length })
.collect::<PrimaryMap<LocalFunctionIndex, _>>()
.into_boxed_slice();
let frame_infos = self.artifact.get_frame_info_ref();
*info = register_frame_info(
self.artifact.module(),
&finished_function_extents,
frame_infos.clone(),
);
}
fn finished_functions(&self) -> &BoxedSlice<LocalFunctionIndex, FunctionBodyPtr> {
&self.finished_functions
}
fn finished_function_call_trampolines(&self) -> &BoxedSlice<SignatureIndex, VMTrampoline> {
&self.finished_function_call_trampolines
}
fn finished_dynamic_function_trampolines(&self) -> &BoxedSlice<FunctionIndex, FunctionBodyPtr> {
&self.finished_dynamic_function_trampolines
}
fn signatures(&self) -> &BoxedSlice<SignatureIndex, VMSharedSignatureIndex> {
&self.signatures
}
fn func_data_registry(&self) -> &FuncDataRegistry {
&self.func_data_registry
}
}

View File

@@ -0,0 +1,66 @@
use super::UniversalEngine;
use crate::{CompilerConfig, Features, Target};
/// The Universal builder
pub struct Universal {
#[allow(dead_code)]
compiler_config: Option<Box<dyn CompilerConfig>>,
target: Option<Target>,
features: Option<Features>,
}
impl Universal {
/// Create a new Universal
pub fn new<T>(compiler_config: T) -> Self
where
T: Into<Box<dyn CompilerConfig>>,
{
Self {
compiler_config: Some(compiler_config.into()),
target: None,
features: None,
}
}
/// Create a new headless Universal
pub fn headless() -> Self {
Self {
compiler_config: None,
target: None,
features: None,
}
}
/// Set the target
pub fn target(mut self, target: Target) -> Self {
self.target = Some(target);
self
}
/// Set the features
pub fn features(mut self, features: Features) -> Self {
self.features = Some(features);
self
}
/// Build the `UniversalEngine` for this configuration
#[cfg(feature = "universal_engine")]
pub fn engine(self) -> UniversalEngine {
let target = self.target.unwrap_or_default();
if let Some(compiler_config) = self.compiler_config {
let features = self
.features
.unwrap_or_else(|| compiler_config.default_features_for_target(&target));
let compiler = compiler_config.compiler();
UniversalEngine::new(compiler, target, features)
} else {
UniversalEngine::headless()
}
}
/// Build the `UniversalEngine` for this configuration
#[cfg(not(feature = "universal_engine"))]
pub fn engine(self) -> UniversalEngine {
UniversalEngine::headless()
}
}

View File

@@ -0,0 +1,224 @@
// This file contains code from external sources.
// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
//! Memory management for executable code.
use super::unwind::UnwindRegistry;
use wasmer_types::{CompiledFunctionUnwindInfo, CustomSection, FunctionBody};
use wasmer_vm::{Mmap, VMFunctionBody};
/// The optimal alignment for functions.
///
/// On x86-64, this is 16 since it's what the optimizations assume.
/// When we add support for other architectures, we should also figure out their
/// optimal alignment values.
const ARCH_FUNCTION_ALIGNMENT: usize = 16;
/// The optimal alignment for data.
///
const DATA_SECTION_ALIGNMENT: usize = 64;
/// Memory manager for executable code.
pub struct CodeMemory {
unwind_registry: UnwindRegistry,
mmap: Mmap,
start_of_nonexecutable_pages: usize,
}
impl CodeMemory {
/// Create a new `CodeMemory` instance.
pub fn new() -> Self {
Self {
unwind_registry: UnwindRegistry::new(),
mmap: Mmap::new(),
start_of_nonexecutable_pages: 0,
}
}
/// Mutably get the UnwindRegistry.
pub fn unwind_registry_mut(&mut self) -> &mut UnwindRegistry {
&mut self.unwind_registry
}
/// Allocate a single contiguous block of memory for the functions and custom sections, and copy the data in place.
#[allow(clippy::type_complexity)]
pub fn allocate(
&mut self,
functions: &[&FunctionBody],
executable_sections: &[&CustomSection],
data_sections: &[&CustomSection],
) -> Result<(Vec<&mut [VMFunctionBody]>, Vec<&mut [u8]>, Vec<&mut [u8]>), String> {
let mut function_result = vec![];
let mut data_section_result = vec![];
let mut executable_section_result = vec![];
let page_size = region::page::size();
// 1. Calculate the total size, that is:
// - function body size, including all trampolines
// -- windows unwind info
// -- padding between functions
// - executable section body
// -- padding between executable sections
// - padding until a new page to change page permissions
// - data section body size
// -- padding between data sections
let total_len = round_up(
functions.iter().fold(0, |acc, func| {
round_up(
acc + Self::function_allocation_size(func),
ARCH_FUNCTION_ALIGNMENT,
)
}) + executable_sections.iter().fold(0, |acc, exec| {
round_up(acc + exec.bytes.len(), ARCH_FUNCTION_ALIGNMENT)
}),
page_size,
) + data_sections.iter().fold(0, |acc, data| {
round_up(acc + data.bytes.len(), DATA_SECTION_ALIGNMENT)
});
// 2. Allocate the pages. Mark them all read-write.
self.mmap = Mmap::with_at_least(total_len)?;
// 3. Determine where the pointers to each function, executable section
// or data section are. Copy the functions. Collect the addresses of each and return them.
let mut bytes = 0;
let mut buf = self.mmap.as_mut_slice();
for func in functions {
let len = round_up(
Self::function_allocation_size(func),
ARCH_FUNCTION_ALIGNMENT,
);
let (func_buf, next_buf) = buf.split_at_mut(len);
buf = next_buf;
bytes += len;
let vmfunc = Self::copy_function(&mut self.unwind_registry, func, func_buf);
assert_eq!(vmfunc.as_ptr() as usize % ARCH_FUNCTION_ALIGNMENT, 0);
function_result.push(vmfunc);
}
for section in executable_sections {
let section = &section.bytes;
assert_eq!(buf.as_mut_ptr() as usize % ARCH_FUNCTION_ALIGNMENT, 0);
let len = round_up(section.len(), ARCH_FUNCTION_ALIGNMENT);
let (s, next_buf) = buf.split_at_mut(len);
buf = next_buf;
bytes += len;
s[..section.len()].copy_from_slice(section.as_slice());
executable_section_result.push(s);
}
self.start_of_nonexecutable_pages = bytes;
if !data_sections.is_empty() {
// Data sections have different page permissions from the executable
// code that came before it, so they need to be on different pages.
let padding = round_up(bytes, page_size) - bytes;
buf = buf.split_at_mut(padding).1;
for section in data_sections {
let section = &section.bytes;
assert_eq!(buf.as_mut_ptr() as usize % DATA_SECTION_ALIGNMENT, 0);
let len = round_up(section.len(), DATA_SECTION_ALIGNMENT);
let (s, next_buf) = buf.split_at_mut(len);
buf = next_buf;
s[..section.len()].copy_from_slice(section.as_slice());
data_section_result.push(s);
}
}
Ok((
function_result,
executable_section_result,
data_section_result,
))
}
/// Apply the page permissions.
pub fn publish(&mut self) {
if self.mmap.is_empty() || self.start_of_nonexecutable_pages == 0 {
return;
}
assert!(self.mmap.len() >= self.start_of_nonexecutable_pages);
unsafe {
region::protect(
self.mmap.as_mut_ptr(),
self.start_of_nonexecutable_pages,
region::Protection::READ_EXECUTE,
)
}
.expect("unable to make memory readonly and executable");
}
/// Calculates the allocation size of the given compiled function.
fn function_allocation_size(func: &FunctionBody) -> usize {
match &func.unwind_info {
Some(CompiledFunctionUnwindInfo::WindowsX64(info)) => {
// Windows unwind information is required to be emitted into code memory
// This is because it must be a positive relative offset from the start of the memory
// Account for necessary unwind information alignment padding (32-bit alignment)
((func.body.len() + 3) & !3) + info.len()
}
_ => func.body.len(),
}
}
/// Copies the data of the compiled function to the given buffer.
///
/// This will also add the function to the current function table.
fn copy_function<'a>(
registry: &mut UnwindRegistry,
func: &FunctionBody,
buf: &'a mut [u8],
) -> &'a mut [VMFunctionBody] {
assert_eq!(buf.as_ptr() as usize % ARCH_FUNCTION_ALIGNMENT, 0);
let func_len = func.body.len();
let (body, remainder) = buf.split_at_mut(func_len);
body.copy_from_slice(&func.body);
let vmfunc = Self::view_as_mut_vmfunc_slice(body);
if let Some(CompiledFunctionUnwindInfo::WindowsX64(info)) = &func.unwind_info {
// Windows unwind information is written following the function body
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
let unwind_start = (func_len + 3) & !3;
let unwind_size = info.len();
let padding = unwind_start - func_len;
assert_eq!((func_len + padding) % 4, 0);
let slice = remainder.split_at_mut(padding + unwind_size).0;
slice[padding..].copy_from_slice(info);
}
if let Some(info) = &func.unwind_info {
registry
.register(vmfunc.as_ptr() as usize, 0, func_len as u32, info)
.expect("failed to register unwind information");
}
vmfunc
}
/// Convert mut a slice from u8 to VMFunctionBody.
fn view_as_mut_vmfunc_slice(slice: &mut [u8]) -> &mut [VMFunctionBody] {
let byte_ptr: *mut [u8] = slice;
let body_ptr = byte_ptr as *mut [VMFunctionBody];
unsafe { &mut *body_ptr }
}
}
fn round_up(size: usize, multiple: usize) -> usize {
debug_assert!(multiple.is_power_of_two());
(size + (multiple - 1)) & !(multiple - 1)
}
#[cfg(test)]
mod tests {
use super::CodeMemory;
fn _assert() {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<CodeMemory>();
}
}

View File

@@ -0,0 +1,304 @@
//! Universal compilation.
#[cfg(feature = "universal_engine")]
use crate::Compiler;
use crate::Target;
use crate::UniversalEngineBuilder;
use crate::{Artifact, Engine, EngineId, FunctionExtent, Tunables};
use crate::{CodeMemory, UniversalArtifact};
use std::sync::{Arc, Mutex};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::FunctionBody;
use wasmer_types::{
CompileError, DeserializeError, Features, FunctionIndex, FunctionType, LocalFunctionIndex,
ModuleInfo, SignatureIndex,
};
use wasmer_types::{CustomSection, CustomSectionProtection, SectionIndex};
use wasmer_vm::{
FuncDataRegistry, FunctionBodyPtr, SectionBodyPtr, SignatureRegistry, VMCallerCheckedAnyfunc,
VMFuncRef, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
};
/// A WebAssembly `Universal` Engine.
#[derive(Clone)]
pub struct UniversalEngine {
inner: Arc<Mutex<UniversalEngineInner>>,
/// The target for the compiler
target: Arc<Target>,
engine_id: EngineId,
}
impl UniversalEngine {
/// Create a new `UniversalEngine` with the given config
#[cfg(feature = "universal_engine")]
pub fn new(compiler: Box<dyn Compiler>, target: Target, features: Features) -> Self {
Self {
inner: Arc::new(Mutex::new(UniversalEngineInner {
builder: UniversalEngineBuilder::new(Some(compiler), features),
code_memory: vec![],
signatures: SignatureRegistry::new(),
func_data: Arc::new(FuncDataRegistry::new()),
})),
target: Arc::new(target),
engine_id: EngineId::default(),
}
}
/// Create a headless `UniversalEngine`
///
/// A headless engine is an engine without any compiler attached.
/// This is useful for assuring a minimal runtime for running
/// WebAssembly modules.
///
/// For example, for running in IoT devices where compilers are very
/// expensive, or also to optimize startup speed.
///
/// # Important
///
/// Headless engines can't compile or validate any modules,
/// they just take already processed Modules (via `Module::serialize`).
pub fn headless() -> Self {
Self {
inner: Arc::new(Mutex::new(UniversalEngineInner {
builder: UniversalEngineBuilder::new(None, Features::default()),
code_memory: vec![],
signatures: SignatureRegistry::new(),
func_data: Arc::new(FuncDataRegistry::new()),
})),
target: Arc::new(Target::default()),
engine_id: EngineId::default(),
}
}
pub(crate) fn inner(&self) -> std::sync::MutexGuard<'_, UniversalEngineInner> {
self.inner.lock().unwrap()
}
pub(crate) fn inner_mut(&self) -> std::sync::MutexGuard<'_, UniversalEngineInner> {
self.inner.lock().unwrap()
}
}
impl Engine for UniversalEngine {
/// The target
fn target(&self) -> &Target {
&self.target
}
/// Register a signature
fn register_signature(&self, func_type: &FunctionType) -> VMSharedSignatureIndex {
let compiler = self.inner();
compiler.signatures().register(func_type)
}
fn register_function_metadata(&self, func_data: VMCallerCheckedAnyfunc) -> VMFuncRef {
let compiler = self.inner();
compiler.func_data().register(func_data)
}
/// Lookup a signature
fn lookup_signature(&self, sig: VMSharedSignatureIndex) -> Option<FunctionType> {
let compiler = self.inner();
compiler.signatures().lookup(sig)
}
/// Validates a WebAssembly module
fn validate(&self, binary: &[u8]) -> Result<(), CompileError> {
self.inner().validate(binary)
}
/// Compile a WebAssembly binary
#[cfg(feature = "universal_engine")]
fn compile(
&self,
binary: &[u8],
tunables: &dyn Tunables,
) -> Result<Arc<dyn Artifact>, CompileError> {
Ok(Arc::new(UniversalArtifact::new(self, binary, tunables)?))
}
/// Compile a WebAssembly binary
#[cfg(not(feature = "universal_engine"))]
fn compile(
&self,
_binary: &[u8],
_tunables: &dyn Tunables,
) -> Result<Arc<dyn Artifact>, CompileError> {
Err(CompileError::Codegen(
"The UniversalEngine is operating in headless mode, so it can not compile Modules."
.to_string(),
))
}
/// Deserializes a WebAssembly module
unsafe fn deserialize(&self, bytes: &[u8]) -> Result<Arc<dyn Artifact>, DeserializeError> {
Ok(Arc::new(UniversalArtifact::deserialize(self, bytes)?))
}
fn id(&self) -> &EngineId {
&self.engine_id
}
fn cloned(&self) -> Arc<dyn Engine + Send + Sync> {
Arc::new(self.clone())
}
}
/// The inner contents of `UniversalEngine`
pub struct UniversalEngineInner {
/// The builder (include compiler and cpu features)
builder: UniversalEngineBuilder,
/// The code memory is responsible of publishing the compiled
/// functions to memory.
code_memory: Vec<CodeMemory>,
/// The signature registry is used mainly to operate with trampolines
/// performantly.
signatures: SignatureRegistry,
/// The backing storage of `VMFuncRef`s. This centralized store ensures that 2
/// functions with the same `VMCallerCheckedAnyfunc` will have the same `VMFuncRef`.
/// It also guarantees that the `VMFuncRef`s stay valid until the engine is dropped.
func_data: Arc<FuncDataRegistry>,
}
impl UniversalEngineInner {
/// Gets the compiler associated to this engine.
#[cfg(feature = "universal_engine")]
pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> {
self.builder.compiler()
}
/// Validate the module
pub fn validate(&self, data: &[u8]) -> Result<(), CompileError> {
self.builder.validate(data)
}
/// The Wasm features
pub fn features(&self) -> &Features {
self.builder.features()
}
pub fn builder_mut(&mut self) -> &mut UniversalEngineBuilder {
&mut self.builder
}
/// Allocate compiled functions into memory
#[allow(clippy::type_complexity)]
pub(crate) fn allocate(
&mut self,
_module: &ModuleInfo,
functions: &PrimaryMap<LocalFunctionIndex, FunctionBody>,
function_call_trampolines: &PrimaryMap<SignatureIndex, FunctionBody>,
dynamic_function_trampolines: &PrimaryMap<FunctionIndex, FunctionBody>,
custom_sections: &PrimaryMap<SectionIndex, CustomSection>,
) -> Result<
(
PrimaryMap<LocalFunctionIndex, FunctionExtent>,
PrimaryMap<SignatureIndex, VMTrampoline>,
PrimaryMap<FunctionIndex, FunctionBodyPtr>,
PrimaryMap<SectionIndex, SectionBodyPtr>,
),
CompileError,
> {
let function_bodies = functions
.values()
.chain(function_call_trampolines.values())
.chain(dynamic_function_trampolines.values())
.collect::<Vec<_>>();
let (executable_sections, data_sections): (Vec<_>, _) = custom_sections
.values()
.partition(|section| section.protection == CustomSectionProtection::ReadExecute);
self.code_memory.push(CodeMemory::new());
let (mut allocated_functions, allocated_executable_sections, allocated_data_sections) =
self.code_memory
.last_mut()
.unwrap()
.allocate(
function_bodies.as_slice(),
executable_sections.as_slice(),
data_sections.as_slice(),
)
.map_err(|message| {
CompileError::Resource(format!(
"failed to allocate memory for functions: {}",
message
))
})?;
let allocated_functions_result = allocated_functions
.drain(0..functions.len())
.map(|slice| FunctionExtent {
ptr: FunctionBodyPtr(slice.as_ptr()),
length: slice.len(),
})
.collect::<PrimaryMap<LocalFunctionIndex, _>>();
let mut allocated_function_call_trampolines: PrimaryMap<SignatureIndex, VMTrampoline> =
PrimaryMap::new();
for ptr in allocated_functions
.drain(0..function_call_trampolines.len())
.map(|slice| slice.as_ptr())
{
let trampoline =
unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) };
allocated_function_call_trampolines.push(trampoline);
}
let allocated_dynamic_function_trampolines = allocated_functions
.drain(..)
.map(|slice| FunctionBodyPtr(slice.as_ptr()))
.collect::<PrimaryMap<FunctionIndex, _>>();
let mut exec_iter = allocated_executable_sections.iter();
let mut data_iter = allocated_data_sections.iter();
let allocated_custom_sections = custom_sections
.iter()
.map(|(_, section)| {
SectionBodyPtr(
if section.protection == CustomSectionProtection::ReadExecute {
exec_iter.next()
} else {
data_iter.next()
}
.unwrap()
.as_ptr(),
)
})
.collect::<PrimaryMap<SectionIndex, _>>();
Ok((
allocated_functions_result,
allocated_function_call_trampolines,
allocated_dynamic_function_trampolines,
allocated_custom_sections,
))
}
/// Make memory containing compiled code executable.
pub(crate) fn publish_compiled_code(&mut self) {
self.code_memory.last_mut().unwrap().publish();
}
/// Register DWARF-type exception handling information associated with the code.
pub(crate) fn publish_eh_frame(&mut self, eh_frame: Option<&[u8]>) -> Result<(), CompileError> {
self.code_memory
.last_mut()
.unwrap()
.unwind_registry_mut()
.publish(eh_frame)
.map_err(|e| {
CompileError::Resource(format!("Error while publishing the unwind code: {}", e))
})?;
Ok(())
}
/// Shared signature registry.
pub fn signatures(&self) -> &SignatureRegistry {
&self.signatures
}
/// Shared func metadata registry.
pub(crate) fn func_data(&self) -> &Arc<FuncDataRegistry> {
&self.func_data
}
}

View File

@@ -0,0 +1,116 @@
//! Linking for Universal-compiled code.
use crate::get_libcall_trampoline;
use crate::FunctionExtent;
use std::ptr::{read_unaligned, write_unaligned};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::{LocalFunctionIndex, ModuleInfo};
use wasmer_types::{Relocation, RelocationKind, RelocationTarget, Relocations, SectionIndex};
use wasmer_vm::libcalls::function_pointer;
use wasmer_vm::SectionBodyPtr;
fn apply_relocation(
body: usize,
r: &Relocation,
allocated_functions: &PrimaryMap<LocalFunctionIndex, FunctionExtent>,
allocated_sections: &PrimaryMap<SectionIndex, SectionBodyPtr>,
libcall_trampolines: SectionIndex,
libcall_trampoline_len: usize,
) {
let target_func_address: usize = match r.reloc_target {
RelocationTarget::LocalFunc(index) => *allocated_functions[index].ptr as usize,
RelocationTarget::LibCall(libcall) => {
// Use the direct target of the libcall if the relocation supports
// a full 64-bit address. Otherwise use a trampoline.
if r.kind == RelocationKind::Abs8 || r.kind == RelocationKind::X86PCRel8 {
function_pointer(libcall)
} else {
get_libcall_trampoline(
libcall,
allocated_sections[libcall_trampolines].0 as usize,
libcall_trampoline_len,
)
}
}
RelocationTarget::CustomSection(custom_section) => {
*allocated_sections[custom_section] as usize
}
};
match r.kind {
RelocationKind::Abs8 => unsafe {
let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
write_unaligned(reloc_address as *mut u64, reloc_delta);
},
RelocationKind::X86PCRel4 => unsafe {
let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
write_unaligned(reloc_address as *mut u32, reloc_delta as _);
},
RelocationKind::X86PCRel8 => unsafe {
let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
write_unaligned(reloc_address as *mut u64, reloc_delta);
},
RelocationKind::X86CallPCRel4 => unsafe {
let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
write_unaligned(reloc_address as *mut u32, reloc_delta as _);
},
RelocationKind::Arm64Call => unsafe {
let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
if (reloc_delta as i64).abs() >= 0x1000_0000 {
panic!(
"Relocation to big for {:?} for {:?} with {:x}, current val {:x}",
r.kind,
r.reloc_target,
reloc_delta,
read_unaligned(reloc_address as *mut u32)
)
}
let reloc_delta = (((reloc_delta / 4) as u32) & 0x3ff_ffff)
| (read_unaligned(reloc_address as *mut u32) & 0xfc00_0000);
write_unaligned(reloc_address as *mut u32, reloc_delta);
},
kind => panic!(
"Relocation kind unsupported in the current architecture {}",
kind
),
}
}
/// Links a module, patching the allocated functions with the
/// required relocations and jump tables.
pub fn link_module(
_module: &ModuleInfo,
allocated_functions: &PrimaryMap<LocalFunctionIndex, FunctionExtent>,
function_relocations: Relocations,
allocated_sections: &PrimaryMap<SectionIndex, SectionBodyPtr>,
section_relocations: &PrimaryMap<SectionIndex, Vec<Relocation>>,
libcall_trampolines: SectionIndex,
trampoline_len: usize,
) {
for (i, section_relocs) in section_relocations.iter() {
let body = *allocated_sections[i] as usize;
for r in section_relocs {
apply_relocation(
body,
r,
allocated_functions,
allocated_sections,
libcall_trampolines,
trampoline_len,
);
}
}
for (i, function_relocs) in function_relocations.iter() {
let body = *allocated_functions[i].ptr as usize;
for r in function_relocs {
apply_relocation(
body,
r,
allocated_functions,
allocated_sections,
libcall_trampolines,
trampoline_len,
);
}
}
}

View File

@@ -0,0 +1,18 @@
//! Universal backend for Wasmer compilers.
//!
//! Given a compiler (such as `CraneliftCompiler` or `LLVMCompiler`)
//! it generates the compiled machine code, and publishes it into
//! memory so it can be used externally.
mod artifact;
mod builder;
mod code_memory;
mod engine;
mod link;
mod unwind;
pub use self::artifact::UniversalArtifact;
pub use self::builder::Universal;
pub use self::code_memory::CodeMemory;
pub use self::engine::UniversalEngine;
pub use self::link::link_module;

View File

@@ -0,0 +1,104 @@
use crate::{
CompileModuleInfo, CompiledFunctionFrameInfo, CustomSection, Dwarf, FunctionBody, Relocation,
SectionIndex,
};
use crate::{DeserializeError, SerializeError};
use rkyv::{
archived_value, de::deserializers::SharedDeserializeMap, ser::serializers::AllocSerializer,
ser::Serializer as RkyvSerializer, Archive, Deserialize as RkyvDeserialize,
Serialize as RkyvSerialize,
};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::{FunctionIndex, LocalFunctionIndex, OwnedDataInitializer, SignatureIndex};
/// The compilation related data for a serialized modules
#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
pub struct SerializableCompilation {
pub function_bodies: PrimaryMap<LocalFunctionIndex, FunctionBody>,
pub function_relocations: PrimaryMap<LocalFunctionIndex, Vec<Relocation>>,
pub function_frame_info: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
pub function_call_trampolines: PrimaryMap<SignatureIndex, FunctionBody>,
pub dynamic_function_trampolines: PrimaryMap<FunctionIndex, FunctionBody>,
pub custom_sections: PrimaryMap<SectionIndex, CustomSection>,
pub custom_section_relocations: PrimaryMap<SectionIndex, Vec<Relocation>>,
// The section indices corresponding to the Dwarf debug info
pub debug: Option<Dwarf>,
// Custom section containing libcall trampolines.
pub libcall_trampolines: SectionIndex,
// Length of each libcall trampoline.
pub libcall_trampoline_len: u32,
}
/// Serializable struct that is able to serialize from and to
/// a `UniversalArtifactInfo`.
#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
pub struct SerializableModule {
pub compilation: SerializableCompilation,
pub compile_info: CompileModuleInfo,
pub data_initializers: Box<[OwnedDataInitializer]>,
pub cpu_features: u64,
}
fn to_serialize_error(err: impl std::error::Error) -> SerializeError {
SerializeError::Generic(format!("{}", err))
}
impl SerializableModule {
/// Serialize a Module into bytes
/// The bytes will have the following format:
/// RKYV serialization (any length) + POS (8 bytes)
pub fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
let mut serializer = AllocSerializer::<4096>::default();
let pos = serializer
.serialize_value(self)
.map_err(to_serialize_error)? as u64;
let mut serialized_data = serializer.into_serializer().into_inner();
serialized_data.extend_from_slice(&pos.to_le_bytes());
Ok(serialized_data.to_vec())
}
/// Deserialize a Module from a slice.
/// The slice must have the following format:
/// RKYV serialization (any length) + POS (8 bytes)
///
/// # Safety
///
/// This method is unsafe since it deserializes data directly
/// from memory.
/// Right now we are not doing any extra work for validation, but
/// `rkyv` has an option to do bytecheck on the serialized data before
/// serializing (via `rkyv::check_archived_value`).
pub unsafe fn deserialize(metadata_slice: &[u8]) -> Result<Self, DeserializeError> {
let archived = Self::archive_from_slice(metadata_slice)?;
Self::deserialize_from_archive(archived)
}
/// # Safety
///
/// This method is unsafe.
/// Please check `SerializableModule::deserialize` for more details.
unsafe fn archive_from_slice<'a>(
metadata_slice: &'a [u8],
) -> Result<&'a ArchivedSerializableModule, DeserializeError> {
if metadata_slice.len() < 8 {
return Err(DeserializeError::Incompatible(
"invalid serialized data".into(),
));
}
let mut pos: [u8; 8] = Default::default();
pos.copy_from_slice(&metadata_slice[metadata_slice.len() - 8..metadata_slice.len()]);
let pos: u64 = u64::from_le_bytes(pos);
Ok(archived_value::<SerializableModule>(
&metadata_slice[..metadata_slice.len() - 8],
pos as usize,
))
}
pub fn deserialize_from_archive(
archived: &ArchivedSerializableModule,
) -> Result<Self, DeserializeError> {
let mut deserializer = SharedDeserializeMap::new();
RkyvDeserialize::deserialize(archived, &mut deserializer)
.map_err(|e| DeserializeError::CorruptedBinary(format!("{:?}", e)))
}
}

View File

@@ -0,0 +1,32 @@
//! Module for Dummy unwind registry.
use wasmer_types::CompiledFunctionUnwindInfo;
/// Represents a registry of function unwind information when the host system
/// support any one in specific.
pub struct DummyUnwindRegistry {}
impl DummyUnwindRegistry {
/// Creates a new unwind registry with the given base address.
pub fn new() -> Self {
DummyUnwindRegistry {}
}
/// Registers a function given the start offset, length, and unwind information.
pub fn register(
&mut self,
_base_address: usize,
_func_start: u32,
_func_len: u32,
_info: &CompiledFunctionUnwindInfo,
) -> Result<(), String> {
// Do nothing
Ok(())
}
/// Publishes all registered functions.
pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> {
// Do nothing
Ok(())
}
}

View File

@@ -0,0 +1,13 @@
cfg_if::cfg_if! {
if #[cfg(all(windows, target_arch = "x86_64"))] {
mod windows_x64;
pub use self::windows_x64::*;
} else if #[cfg(unix)] {
mod systemv;
pub use self::systemv::*;
} else {
// Otherwise, we provide a dummy fallback without unwinding
mod dummy;
pub use self::dummy::DummyUnwindRegistry as UnwindRegistry;
}
}

View File

@@ -0,0 +1,121 @@
// This file contains code from external sources.
// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
//! Module for System V ABI unwind registry.
use wasmer_types::CompiledFunctionUnwindInfo;
/// Represents a registry of function unwind information for System V ABI.
pub struct UnwindRegistry {
registrations: Vec<usize>,
published: bool,
}
extern "C" {
// libunwind import
fn __register_frame(fde: *const u8);
fn __deregister_frame(fde: *const u8);
}
impl UnwindRegistry {
/// Creates a new unwind registry with the given base address.
pub fn new() -> Self {
Self {
registrations: Vec::new(),
published: false,
}
}
/// Registers a function given the start offset, length, and unwind information.
pub fn register(
&mut self,
_base_address: usize,
_func_start: u32,
_func_len: u32,
info: &CompiledFunctionUnwindInfo,
) -> Result<(), String> {
match info {
CompiledFunctionUnwindInfo::Dwarf => {}
_ => return Err("unsupported unwind information".to_string()),
};
Ok(())
}
/// Publishes all registered functions.
pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
if let Some(eh_frame) = eh_frame {
unsafe {
self.register_frames(eh_frame);
}
}
self.published = true;
Ok(())
}
#[allow(clippy::cast_ptr_alignment)]
unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
if cfg!(all(target_os = "linux", target_env = "gnu")) {
// Registering an empty `eh_frame` (i.e. which
// contains empty FDEs) cause problems on Linux when
// deregistering it. We must avoid this
// scenario. Usually, this is handled upstream by the
// compilers.
debug_assert_ne!(
eh_frame,
&[0, 0, 0, 0],
"`eh_frame` seems to contain empty FDEs"
);
// On gnu (libgcc), `__register_frame` will walk the FDEs until an entry of length 0
let ptr = eh_frame.as_ptr();
__register_frame(ptr);
self.registrations.push(ptr as usize);
} else {
// For libunwind, `__register_frame` takes a pointer to a single FDE
let start = eh_frame.as_ptr();
let end = start.add(eh_frame.len());
let mut current = start;
// Walk all of the entries in the frame table and register them
while current < end {
let len = std::ptr::read::<u32>(current as *const u32) as usize;
// Skip over the CIE and zero-length FDEs.
// LLVM's libunwind emits a warning on zero-length FDEs.
if current != start && len != 0 {
__register_frame(current);
self.registrations.push(current as usize);
}
// Move to the next table entry (+4 because the length itself is not inclusive)
current = current.add(len + 4);
}
}
}
}
impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
// libgcc stores the frame entries as a linked list in decreasing sort order
// based on the PC value of the registered entry.
//
// As we store the registrations in increasing order, it would be O(N^2) to
// deregister in that order.
//
// To ensure that we just pop off the first element in the list upon every
// deregistration, walk our list of registrations backwards.
for fde in self.registrations.iter().rev() {
__deregister_frame(*fde as *const _);
}
}
}
}
}

View File

@@ -0,0 +1,105 @@
// This file contains code from external sources.
// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
//! Module for Windows x64 ABI unwind registry.
use std::collections::HashMap;
use wasmer_types::CompiledFunctionUnwindInfo;
use winapi::um::winnt;
/// Represents a registry of function unwind information for Windows x64 ABI.
pub struct UnwindRegistry {
// A hashmap mapping the baseaddress with the registered runtime functions
functions: HashMap<usize, Vec<winnt::RUNTIME_FUNCTION>>,
published: bool,
}
impl UnwindRegistry {
/// Creates a new unwind registry with the given base address.
pub fn new() -> Self {
Self {
functions: HashMap::new(),
published: false,
}
}
/// Registers a function given the start offset, length, and unwind information.
pub fn register(
&mut self,
base_address: usize,
func_start: u32,
func_len: u32,
info: &CompiledFunctionUnwindInfo,
) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
match info {
CompiledFunctionUnwindInfo::WindowsX64(_) => {}
_ => return Err("unsupported unwind information".to_string()),
};
let mut entry = winnt::RUNTIME_FUNCTION::default();
entry.BeginAddress = func_start;
entry.EndAddress = func_start + func_len;
// The unwind information should be immediately following the function
// with padding for 4 byte alignment
unsafe {
*entry.u.UnwindInfoAddress_mut() = (entry.EndAddress + 3) & !3;
}
let entries = self
.functions
.entry(base_address)
.or_insert_with(|| Vec::new());
entries.push(entry);
Ok(())
}
/// Publishes all registered functions.
pub fn publish(&mut self, _eh_frame: Option<&[u8]>) -> Result<(), String> {
if self.published {
return Err("unwind registry has already been published".to_string());
}
self.published = true;
if !self.functions.is_empty() {
for (base_address, functions) in self.functions.iter_mut() {
// Windows heap allocations are 32-bit aligned, but assert just in case
assert_eq!(
(functions.as_mut_ptr() as u64) % 4,
0,
"function table allocation was not aligned"
);
unsafe {
if winnt::RtlAddFunctionTable(
functions.as_mut_ptr(),
functions.len() as u32,
*base_address as u64,
) == 0
{
return Err("failed to register function tables".to_string());
}
}
}
}
Ok(())
}
}
impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
for functions in self.functions.values_mut() {
winnt::RtlDeleteFunctionTable(functions.as_mut_ptr());
}
}
}
}
}

View File

@@ -59,6 +59,12 @@ pub use crate::artifact::*;
#[cfg(not(target_arch = "wasm32"))]
pub use crate::engine::*;
#[cfg(feature = "translator")]
mod universal_artifact;
#[cfg(feature = "translator")]
pub use self::universal_artifact::*;
#[cfg(feature = "translator")]
mod compiler;
mod target;

View File

@@ -0,0 +1,235 @@
//! Define `UniversalArtifactBuild` to allow compiling and instantiating to be
//! done as separate steps.
#[cfg(feature = "universal_engine")]
use super::serialize::SerializableCompilation;
use super::serialize::SerializableModule;
#[cfg(feature = "universal_engine")]
use super::trampoline::{libcall_trampoline_len, make_libcall_trampolines};
use crate::MetadataHeader;
use crate::{ArtifactCreate, UniversalEngineBuilder};
use crate::{CpuFeature, Features, Triple};
#[cfg(feature = "universal_engine")]
use crate::{ModuleEnvironment, ModuleMiddlewareChain, Target};
use enumset::EnumSet;
use std::mem;
use std::sync::Arc;
use wasmer_types::entity::PrimaryMap;
#[cfg(feature = "universal_engine")]
use wasmer_types::CompileModuleInfo;
use wasmer_types::SerializeError;
use wasmer_types::{
CompileError, CustomSection, Dwarf, FunctionIndex, LocalFunctionIndex, MemoryIndex,
MemoryStyle, ModuleInfo, OwnedDataInitializer, Relocation, SectionIndex, SignatureIndex,
TableIndex, TableStyle,
};
use wasmer_types::{CompiledFunctionFrameInfo, FunctionBody};
/// A compiled wasm module, ready to be instantiated.
pub struct UniversalArtifactBuild {
serializable: SerializableModule,
}
impl UniversalArtifactBuild {
/// Header signature for wasmu binary
pub const MAGIC_HEADER: &'static [u8; 16] = b"wasmer-universal";
/// Check if the provided bytes look like a serialized `UniversalArtifactBuild`.
pub fn is_deserializable(bytes: &[u8]) -> bool {
bytes.starts_with(Self::MAGIC_HEADER)
}
/// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated.
#[cfg(feature = "universal_engine")]
pub fn new(
inner_engine: &mut UniversalEngineBuilder,
data: &[u8],
target: &Target,
memory_styles: PrimaryMap<MemoryIndex, MemoryStyle>,
table_styles: PrimaryMap<TableIndex, TableStyle>,
) -> Result<Self, CompileError> {
let environ = ModuleEnvironment::new();
let features = inner_engine.features();
let translation = environ.translate(data).map_err(CompileError::Wasm)?;
let compiler = inner_engine.compiler()?;
// We try to apply the middleware first
let mut module = translation.module;
let middlewares = compiler.get_middlewares();
middlewares.apply_on_module_info(&mut module);
let compile_info = CompileModuleInfo {
module: Arc::new(module),
features: features.clone(),
memory_styles,
table_styles,
};
// Compile the Module
let compilation = compiler.compile_module(
target,
&compile_info,
// SAFETY: Calling `unwrap` is correct since
// `environ.translate()` above will write some data into
// `module_translation_state`.
translation.module_translation_state.as_ref().unwrap(),
translation.function_body_inputs,
)?;
let function_call_trampolines = compilation.get_function_call_trampolines();
let dynamic_function_trampolines = compilation.get_dynamic_function_trampolines();
let data_initializers = translation
.data_initializers
.iter()
.map(OwnedDataInitializer::new)
.collect::<Vec<_>>()
.into_boxed_slice();
let frame_infos = compilation.get_frame_info();
// Synthesize a custom section to hold the libcall trampolines.
let mut custom_sections = compilation.get_custom_sections();
let mut custom_section_relocations = compilation.get_custom_section_relocations();
let libcall_trampolines_section = make_libcall_trampolines(target);
custom_section_relocations.push(libcall_trampolines_section.relocations.clone());
let libcall_trampolines = custom_sections.push(libcall_trampolines_section);
let libcall_trampoline_len = libcall_trampoline_len(target) as u32;
let serializable_compilation = SerializableCompilation {
function_bodies: compilation.get_function_bodies(),
function_relocations: compilation.get_relocations(),
function_frame_info: frame_infos,
function_call_trampolines,
dynamic_function_trampolines,
custom_sections,
custom_section_relocations,
debug: compilation.get_debug(),
libcall_trampolines,
libcall_trampoline_len,
};
let serializable = SerializableModule {
compilation: serializable_compilation,
compile_info,
data_initializers,
cpu_features: target.cpu_features().as_u64(),
};
Ok(Self { serializable })
}
/// Compile a data buffer into a `UniversalArtifactBuild`, which may then be instantiated.
#[cfg(not(feature = "universal_engine"))]
pub fn new(_engine: &UniversalEngineBuilder, _data: &[u8]) -> Result<Self, CompileError> {
Err(CompileError::Codegen(
"Compilation is not enabled in the engine".to_string(),
))
}
/// Create a new UniversalArtifactBuild from a SerializableModule
pub fn from_serializable(serializable: SerializableModule) -> Self {
Self { serializable }
}
/// Get the default extension when serializing this artifact
pub fn get_default_extension(_triple: &Triple) -> &'static str {
// `.wasmu` is the default extension for all the triples. It
// stands for “Wasm Universal”.
"wasmu"
}
/// Get Functions Bodies ref
pub fn get_function_bodies_ref(&self) -> &PrimaryMap<LocalFunctionIndex, FunctionBody> {
&self.serializable.compilation.function_bodies
}
/// Get Functions Call Trampolines ref
pub fn get_function_call_trampolines_ref(&self) -> &PrimaryMap<SignatureIndex, FunctionBody> {
&self.serializable.compilation.function_call_trampolines
}
/// Get Dynamic Functions Call Trampolines ref
pub fn get_dynamic_function_trampolines_ref(&self) -> &PrimaryMap<FunctionIndex, FunctionBody> {
&self.serializable.compilation.dynamic_function_trampolines
}
/// Get Custom Sections ref
pub fn get_custom_sections_ref(&self) -> &PrimaryMap<SectionIndex, CustomSection> {
&self.serializable.compilation.custom_sections
}
/// Get Function Relocations
pub fn get_function_relocations(&self) -> PrimaryMap<LocalFunctionIndex, Vec<Relocation>> {
self.serializable.compilation.function_relocations.clone()
}
/// Get Function Relocations ref
pub fn get_custom_section_relocations_ref(&self) -> &PrimaryMap<SectionIndex, Vec<Relocation>> {
&self.serializable.compilation.custom_section_relocations
}
/// Get LibCall Trampoline Section Index
pub fn get_libcall_trampolines(&self) -> SectionIndex {
self.serializable.compilation.libcall_trampolines
}
/// Get LibCall Trampoline Length
pub fn get_libcall_trampoline_len(&self) -> usize {
self.serializable.compilation.libcall_trampoline_len as usize
}
/// Get Debug optional Dwarf ref
pub fn get_debug_ref(&self) -> &Option<Dwarf> {
&self.serializable.compilation.debug
}
/// Get Function Relocations ref
pub fn get_frame_info_ref(&self) -> &PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo> {
&self.serializable.compilation.function_frame_info
}
}
impl ArtifactCreate for UniversalArtifactBuild {
fn module(&self) -> Arc<ModuleInfo> {
self.serializable.compile_info.module.clone()
}
fn module_ref(&self) -> &ModuleInfo {
&self.serializable.compile_info.module
}
fn module_mut(&mut self) -> Option<&mut ModuleInfo> {
Arc::get_mut(&mut self.serializable.compile_info.module)
}
fn features(&self) -> &Features {
&self.serializable.compile_info.features
}
fn cpu_features(&self) -> EnumSet<CpuFeature> {
EnumSet::from_u64(self.serializable.cpu_features)
}
fn data_initializers(&self) -> &[OwnedDataInitializer] {
&*self.serializable.data_initializers
}
fn memory_styles(&self) -> &PrimaryMap<MemoryIndex, MemoryStyle> {
&self.serializable.compile_info.memory_styles
}
fn table_styles(&self) -> &PrimaryMap<TableIndex, TableStyle> {
&self.serializable.compile_info.table_styles
}
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
let serialized_data = self.serializable.serialize()?;
assert!(mem::align_of::<SerializableModule>() <= MetadataHeader::ALIGN);
let mut metadata_binary = vec![];
metadata_binary.extend(Self::MAGIC_HEADER);
metadata_binary.extend(MetadataHeader::new(serialized_data.len()).into_bytes());
metadata_binary.extend(serialized_data);
Ok(metadata_binary)
}
}

View File

@@ -0,0 +1,39 @@
//! Universal compilation.
use crate::Compiler;
use wasmer_types::{CompileError, Features};
/// The Builder contents of `UniversalEngine`
pub struct UniversalEngineBuilder {
/// The compiler
compiler: Option<Box<dyn Compiler>>,
/// The features to compile the Wasm module with
features: Features,
}
impl UniversalEngineBuilder {
/// Create a new builder with pre-made components
pub fn new(compiler: Option<Box<dyn Compiler>>, features: Features) -> Self {
Self { compiler, features }
}
/// Gets the compiler associated to this engine.
pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> {
if self.compiler.is_none() {
return Err(CompileError::Codegen(
"The UniversalEngine is not compiled in.".to_string(),
));
}
Ok(&**self.compiler.as_ref().unwrap())
}
/// Validate the module
pub fn validate(&self, data: &[u8]) -> Result<(), CompileError> {
self.compiler()?.validate_module(self.features(), data)
}
/// The Wasm features
pub fn features(&self) -> &Features {
&self.features
}
}

View File

@@ -0,0 +1,11 @@
//! Generic Artifact abstraction for Wasmer Engines.
mod artifact;
mod engine;
mod serialize;
mod trampoline;
pub use self::artifact::UniversalArtifactBuild;
pub use self::engine::UniversalEngineBuilder;
pub use self::serialize::SerializableModule;
pub use self::trampoline::*;

View File

@@ -0,0 +1,107 @@
use rkyv::{
archived_value, de::deserializers::SharedDeserializeMap, ser::serializers::AllocSerializer,
ser::Serializer as RkyvSerializer, Archive, Deserialize as RkyvDeserialize,
Serialize as RkyvSerialize,
};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::{CompileModuleInfo, CompiledFunctionFrameInfo, Dwarf, FunctionBody};
use wasmer_types::{CustomSection, Relocation, SectionIndex};
use wasmer_types::{DeserializeError, SerializeError};
use wasmer_types::{FunctionIndex, LocalFunctionIndex, OwnedDataInitializer, SignatureIndex};
/// The compilation related data for a serialized modules
#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
pub struct SerializableCompilation {
pub function_bodies: PrimaryMap<LocalFunctionIndex, FunctionBody>,
pub function_relocations: PrimaryMap<LocalFunctionIndex, Vec<Relocation>>,
pub function_frame_info: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
pub function_call_trampolines: PrimaryMap<SignatureIndex, FunctionBody>,
pub dynamic_function_trampolines: PrimaryMap<FunctionIndex, FunctionBody>,
pub custom_sections: PrimaryMap<SectionIndex, CustomSection>,
pub custom_section_relocations: PrimaryMap<SectionIndex, Vec<Relocation>>,
// The section indices corresponding to the Dwarf debug info
pub debug: Option<Dwarf>,
// Custom section containing libcall trampolines.
pub libcall_trampolines: SectionIndex,
// Length of each libcall trampoline.
pub libcall_trampoline_len: u32,
}
/// Serializable struct that is able to serialize from and to
/// a `UniversalArtifactInfo`.
#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
pub struct SerializableModule {
/// The main serializable compilation object
pub compilation: SerializableCompilation,
/// Compilation informations
pub compile_info: CompileModuleInfo,
/// Datas initializers
pub data_initializers: Box<[OwnedDataInitializer]>,
/// CPU Feature flags for this compilation
pub cpu_features: u64,
}
fn to_serialize_error(err: impl std::error::Error) -> SerializeError {
SerializeError::Generic(format!("{}", err))
}
impl SerializableModule {
/// Serialize a Module into bytes
/// The bytes will have the following format:
/// RKYV serialization (any length) + POS (8 bytes)
pub fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
let mut serializer = AllocSerializer::<4096>::default();
let pos = serializer
.serialize_value(self)
.map_err(to_serialize_error)? as u64;
let mut serialized_data = serializer.into_serializer().into_inner();
serialized_data.extend_from_slice(&pos.to_le_bytes());
Ok(serialized_data.to_vec())
}
/// Deserialize a Module from a slice.
/// The slice must have the following format:
/// RKYV serialization (any length) + POS (8 bytes)
///
/// # Safety
///
/// This method is unsafe since it deserializes data directly
/// from memory.
/// Right now we are not doing any extra work for validation, but
/// `rkyv` has an option to do bytecheck on the serialized data before
/// serializing (via `rkyv::check_archived_value`).
pub unsafe fn deserialize(metadata_slice: &[u8]) -> Result<Self, DeserializeError> {
let archived = Self::archive_from_slice(metadata_slice)?;
Self::deserialize_from_archive(archived)
}
/// # Safety
///
/// This method is unsafe.
/// Please check `SerializableModule::deserialize` for more details.
unsafe fn archive_from_slice(
metadata_slice: &[u8],
) -> Result<&ArchivedSerializableModule, DeserializeError> {
if metadata_slice.len() < 8 {
return Err(DeserializeError::Incompatible(
"invalid serialized data".into(),
));
}
let mut pos: [u8; 8] = Default::default();
pos.copy_from_slice(&metadata_slice[metadata_slice.len() - 8..metadata_slice.len()]);
let pos: u64 = u64::from_le_bytes(pos);
Ok(archived_value::<Self>(
&metadata_slice[..metadata_slice.len() - 8],
pos as usize,
))
}
/// Deserialize a compilation module from an archive
pub fn deserialize_from_archive(
archived: &ArchivedSerializableModule,
) -> Result<Self, DeserializeError> {
let mut deserializer = SharedDeserializeMap::new();
RkyvDeserialize::deserialize(archived, &mut deserializer)
.map_err(|e| DeserializeError::CorruptedBinary(format!("{:?}", e)))
}
}

View File

@@ -0,0 +1,88 @@
//! Trampolines for libcalls.
//!
//! This is needed because the target of libcall relocations are not reachable
//! through normal branch instructions.
use crate::{Architecture, Target};
use enum_iterator::IntoEnumIterator;
use wasmer_types::{
CustomSection, CustomSectionProtection, LibCall, Relocation, RelocationKind, RelocationTarget,
SectionBody,
};
// SystemV says that both x16 and x17 are available as intra-procedural scratch
// registers but Apple's ABI restricts us to use x17.
// LDR x17, [PC, #8] 51 00 00 58
// BR x17 20 02 1f d6
// JMPADDR 00 00 00 00 00 00 00 00
const AARCH64_TRAMPOLINE: [u8; 16] = [
0x51, 0x00, 0x00, 0x58, 0x20, 0x02, 0x1f, 0xd6, 0, 0, 0, 0, 0, 0, 0, 0,
];
// 2 padding bytes are used to preserve alignment.
// JMP [RIP + 2] FF 25 02 00 00 00 [00 00]
// 64-bit ADDR 00 00 00 00 00 00 00 00
const X86_64_TRAMPOLINE: [u8; 16] = [
0xff, 0x25, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
fn make_trampoline(
target: &Target,
libcall: LibCall,
code: &mut Vec<u8>,
relocations: &mut Vec<Relocation>,
) {
match target.triple().architecture {
Architecture::Aarch64(_) => {
code.extend(&AARCH64_TRAMPOLINE);
relocations.push(Relocation {
kind: RelocationKind::Abs8,
reloc_target: RelocationTarget::LibCall(libcall),
offset: code.len() as u32 - 8,
addend: 0,
});
}
Architecture::X86_64 => {
code.extend(&X86_64_TRAMPOLINE);
relocations.push(Relocation {
kind: RelocationKind::Abs8,
reloc_target: RelocationTarget::LibCall(libcall),
offset: code.len() as u32 - 8,
addend: 0,
});
}
arch => panic!("Unsupported architecture: {}", arch),
};
}
/// Returns the length of a libcall trampoline.
pub fn libcall_trampoline_len(target: &Target) -> usize {
match target.triple().architecture {
Architecture::Aarch64(_) => AARCH64_TRAMPOLINE.len(),
Architecture::X86_64 => X86_64_TRAMPOLINE.len(),
arch => panic!("Unsupported architecture: {}", arch),
}
}
/// Creates a custom section containing the libcall trampolines.
pub fn make_libcall_trampolines(target: &Target) -> CustomSection {
let mut code = vec![];
let mut relocations = vec![];
for libcall in LibCall::into_enum_iter() {
make_trampoline(target, libcall, &mut code, &mut relocations);
}
CustomSection {
protection: CustomSectionProtection::ReadExecute,
bytes: SectionBody::new_with_vec(code),
relocations,
}
}
/// Returns the address of a trampoline in the libcall trampolines section.
pub fn get_libcall_trampoline(
libcall: LibCall,
libcall_trampolines: usize,
libcall_trampoline_len: usize,
) -> usize {
libcall_trampolines + libcall as usize * libcall_trampoline_len
}