mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 22:28:21 +00:00
Remove wasmer_engine_universal{,_artifact} and merge into wasmer_compiler
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
280
lib/compiler/src/engine/universal/artifact.rs
Normal file
280
lib/compiler/src/engine/universal/artifact.rs
Normal 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
|
||||
}
|
||||
}
|
||||
66
lib/compiler/src/engine/universal/builder.rs
Normal file
66
lib/compiler/src/engine/universal/builder.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
224
lib/compiler/src/engine/universal/code_memory.rs
Normal file
224
lib/compiler/src/engine/universal/code_memory.rs
Normal 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 = §ion.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 = §ion.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>();
|
||||
}
|
||||
}
|
||||
304
lib/compiler/src/engine/universal/engine.rs
Normal file
304
lib/compiler/src/engine/universal/engine.rs
Normal 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
|
||||
}
|
||||
}
|
||||
116
lib/compiler/src/engine/universal/link.rs
Normal file
116
lib/compiler/src/engine/universal/link.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
lib/compiler/src/engine/universal/mod.rs
Normal file
18
lib/compiler/src/engine/universal/mod.rs
Normal 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;
|
||||
104
lib/compiler/src/engine/universal/serialize.rs
Normal file
104
lib/compiler/src/engine/universal/serialize.rs
Normal 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)))
|
||||
}
|
||||
}
|
||||
32
lib/compiler/src/engine/universal/unwind/dummy.rs
Normal file
32
lib/compiler/src/engine/universal/unwind/dummy.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
13
lib/compiler/src/engine/universal/unwind/mod.rs
Normal file
13
lib/compiler/src/engine/universal/unwind/mod.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
121
lib/compiler/src/engine/universal/unwind/systemv.rs
Normal file
121
lib/compiler/src/engine/universal/unwind/systemv.rs
Normal 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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
lib/compiler/src/engine/universal/unwind/windows_x64.rs
Normal file
105
lib/compiler/src/engine/universal/unwind/windows_x64.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
235
lib/compiler/src/universal_artifact/artifact.rs
Normal file
235
lib/compiler/src/universal_artifact/artifact.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
39
lib/compiler/src/universal_artifact/engine.rs
Normal file
39
lib/compiler/src/universal_artifact/engine.rs
Normal 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
|
||||
}
|
||||
}
|
||||
11
lib/compiler/src/universal_artifact/mod.rs
Normal file
11
lib/compiler/src/universal_artifact/mod.rs
Normal 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::*;
|
||||
107
lib/compiler/src/universal_artifact/serialize.rs
Normal file
107
lib/compiler/src/universal_artifact/serialize.rs
Normal 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)))
|
||||
}
|
||||
}
|
||||
88
lib/compiler/src/universal_artifact/trampoline.rs
Normal file
88
lib/compiler/src/universal_artifact/trampoline.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user