From fe1643733bae18f114017aaf007b2561081e7a03 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Nov 2018 16:50:05 -0800 Subject: [PATCH] Remove use of offset_of! from wasmtime-environ. wasmtime-environ is meant to support cross compilation, so it shouldn't have dependencies on target layout of structs. This moves the layout back into wasmtime-execute, and adds a system of asserts for checking that wasmtime-environ's offsets stay in sync. --- lib/environ/Cargo.toml | 2 +- lib/environ/src/environ.rs | 41 +++----- lib/environ/src/lib.rs | 4 +- lib/environ/src/vmcontext.rs | 33 ------- lib/environ/src/vmoffsets.rs | 145 ++++++++++++++++++++++++++++ lib/execute/Cargo.toml | 4 + lib/execute/src/execute.rs | 2 + lib/execute/src/lib.rs | 4 + lib/execute/src/memory.rs | 4 +- lib/execute/src/vmcontext.rs | 182 +++++++++++++++++++++++++++++++++++ 10 files changed, 355 insertions(+), 66 deletions(-) delete mode 100644 lib/environ/src/vmcontext.rs create mode 100644 lib/environ/src/vmoffsets.rs create mode 100644 lib/execute/src/vmcontext.rs diff --git a/lib/environ/Cargo.toml b/lib/environ/Cargo.toml index 33acdbf194..0ab1a41fb8 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } -memoffset = "0.2.1" +cast = { version = "0.2.2", default-features = false } [features] default = ["std"] diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 6cd3c151fe..9b8071bb62 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -15,17 +15,15 @@ use module::{ DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements, }; use std::clone::Clone; -use std::mem; use std::string::String; use std::vec::Vec; use tunables::Tunables; -use vmcontext; +use vmoffsets::VMOffsets; use WASM_PAGE_SIZE; /// Compute a `ir::ExternalName` for a given wasm function index. pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { - debug_assert!(FuncIndex::new(func_index.index() as u32 as usize) == func_index); - ir::ExternalName::user(0, func_index.index() as u32) + ir::ExternalName::user(0, func_index.as_u32()) } /// Object containing the standalone environment information. To be passed after creation as @@ -104,6 +102,9 @@ pub struct FuncEnvironment<'module_environment> { /// The external function declaration for implementing wasm's `grow_memory`. grow_memory_extfunc: Option, + + /// Offsets to struct fields accessed by JIT code. + offsets: VMOffsets, } impl<'module_environment> FuncEnvironment<'module_environment> { @@ -120,6 +121,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { globals_base: None, current_memory_extfunc: None, grow_memory_extfunc: None, + offsets: VMOffsets::new(isa.frontend_config().pointer_bytes()), } } @@ -149,10 +151,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data, 'module> { - fn get_func_name(&self, func_index: FuncIndex) -> ir::ExternalName { - get_func_name(func_index) - } - fn target_config(&self) -> isa::TargetFrontendConfig { self.isa.frontend_config() } @@ -302,19 +300,16 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let globals_base = self.globals_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, globals) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_globals())), global_type: self.pointer_type(), readonly: true, }); self.globals_base = Some(new_base); new_base }); - // For now, give each global gets a pointer-sized region of - // storage, regardless of its type. - let offset = index.index() * mem::size_of::<*mut u8>(); let gv = func.create_global_value(ir::GlobalValueData::IAddImm { base: globals_base, - offset: Imm64::new(offset as i64), + offset: Imm64::new(i64::from(self.offsets.index_vmglobal(index.as_u32()))), global_type: self.pointer_type(), }); GlobalVariable::Memory { @@ -328,16 +323,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let memories_base = self.memories_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, memories) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_memories())), global_type: self.pointer_type(), readonly: true, }); self.memories_base = Some(new_base); new_base }); - let offset = index.index() * mem::size_of::(); - let offset32 = offset as i32; - debug_assert_eq!(offset32 as usize, offset); // If we have a declared maximum, we can make this a "static" heap, which is // allocated up front and never moved. let (offset_guard_size, heap_style, readonly_base) = match self.module.memory_plans[index] { @@ -349,7 +341,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let heap_bound = func.create_global_value(ir::GlobalValueData::Load { base: memories_base, offset: Offset32::new( - offset32 + offset_of!(vmcontext::VMMemory, current_length) as i32, + self.offsets.index_vmmemory_current_length(index.as_u32()), ), global_type: I32, readonly: false, @@ -377,7 +369,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let heap_base = func.create_global_value(ir::GlobalValueData::Load { base: memories_base, - offset: Offset32::new(offset32 + offset_of!(vmcontext::VMMemory, base) as i32), + offset: Offset32::new(self.offsets.index_vmmemory_base(index.as_u32())), global_type: self.pointer_type(), readonly: readonly_base, }); @@ -395,27 +387,22 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let tables_base = self.tables_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, tables) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_tables())), global_type: self.pointer_type(), readonly: true, }); self.tables_base = Some(new_base); new_base }); - let offset = index.index() * mem::size_of::(); - let offset32 = offset as i32; - debug_assert_eq!(offset32 as usize, offset); let base_gv = func.create_global_value(ir::GlobalValueData::Load { base: tables_base, - offset: Offset32::new(offset32 + offset_of!(vmcontext::VMTable, base) as i32), + offset: Offset32::new(self.offsets.index_vmtable_base(index.as_u32())), global_type: self.pointer_type(), readonly: false, }); let bound_gv = func.create_global_value(ir::GlobalValueData::Load { base: tables_base, - offset: Offset32::new( - offset32 + offset_of!(vmcontext::VMTable, current_num_elements) as i32, - ), + offset: Offset32::new(self.offsets.index_vmtable_current_elements(index.as_u32())), global_type: I32, readonly: false, }); diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 6e550c3c8f..7bd0a8cd3b 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -33,8 +33,6 @@ extern crate cranelift_codegen; extern crate cranelift_entity; extern crate cranelift_wasm; -#[macro_use] -extern crate memoffset; #[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; @@ -43,7 +41,7 @@ mod compilation; mod environ; mod module; mod tunables; -mod vmcontext; +mod vmoffsets; pub use compilation::{ compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations, diff --git a/lib/environ/src/vmcontext.rs b/lib/environ/src/vmcontext.rs deleted file mode 100644 index 098b22c454..0000000000 --- a/lib/environ/src/vmcontext.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// The main fields a JIT needs to access to utilize a WebAssembly linear, -/// memory, namely the start address and the size in bytes. -#[repr(C, packed)] -pub struct VMMemory { - pub base: *mut u8, - pub current_length: usize, -} - -/// The main fields a JIT needs to access to utilize a WebAssembly table, -/// namely the start address and the number of elements. -#[repr(C, packed)] -pub struct VMTable { - pub base: *mut u8, - pub current_num_elements: usize, -} - -/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift. -/// This has pointers to the globals, memories, tables, and other runtime -/// state associated with the current instance. -#[repr(C, packed)] -pub struct VMContext { - /// A pointer to an array of globals. - pub globals: *mut u8, - /// A pointer to an array of `VMMemory` instances, indexed by - /// WebAssembly memory index. - pub memories: *mut VMMemory, - /// A pointer to an array of `VMTable` instances, indexed by - /// WebAssembly table index. - pub tables: *mut VMTable, - /// A pointer to extra runtime state that isn't directly accessed - /// from JIT code. - pub instance: *mut u8, -} diff --git a/lib/environ/src/vmoffsets.rs b/lib/environ/src/vmoffsets.rs new file mode 100644 index 0000000000..7dfa251251 --- /dev/null +++ b/lib/environ/src/vmoffsets.rs @@ -0,0 +1,145 @@ +/// This class computes offsets to fields within `VMContext` and other +/// related structs that JIT code accesses directly. +pub struct VMOffsets { + pointer_size: u8, +} + +impl VMOffsets { + /// Return a new `VMOffsets` instance, for a given pointer size. + pub fn new(pointer_size: u8) -> Self { + Self { pointer_size } + } +} + +/// Offsets for `wasmtime_execute::VMMemory`. +impl VMOffsets { + /// The offset of the `base` field. + pub fn vmmemory_base(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `current_length` field. + pub fn vmmemory_current_length(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of `VMMemory`. + pub fn size_of_vmmemory(&self) -> u8 { + 2 * self.pointer_size + } +} + +/// Offsets for `wasmtime_execute::VMGlobal`. +impl VMOffsets { + /// Return the size of `VMGlobal`. + pub fn size_of_vmglobal(&self) -> u8 { + 8 + } +} + +/// Offsets for `wasmtime_execute::VMTable`. +impl VMOffsets { + /// The offset of the `base` field. + pub fn vmtable_base(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `current_elements` field. + pub fn vmtable_current_elements(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of `VMTable`. + pub fn size_of_vmtable(&self) -> u8 { + 2 * self.pointer_size + } +} + +/// Offsets for `wasmtime_execute::VMContext`. +impl VMOffsets { + /// The offset of the `memories` field. + pub fn vmctx_memories(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `globals` field. + pub fn vmctx_globals(&self) -> u8 { + 1 * self.pointer_size + } + + /// The offset of the `tables` field. + pub fn vmctx_tables(&self) -> u8 { + 2 * self.pointer_size + } + + /// The offset of the `instance` field. + #[allow(dead_code)] + pub fn vmctx_instance(&self) -> u8 { + 3 * self.pointer_size + } + + /// Return the size of `VMContext`. + #[allow(dead_code)] + pub fn size_of_vmctx(&self) -> u8 { + 4 * self.pointer_size + } + + /// Return the offset from the `memories` pointer to `VMMemory` index `index`. + pub fn index_vmmemory(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmmemory())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `globals` pointer to `VMGlobal` index `index`. + pub fn index_vmglobal(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmglobal())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `tables` pointer to `VMTable` index `index`. + pub fn index_vmtable(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmtable())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `memories` pointer to the `base` field in + /// `VMMemory` index `index`. + pub fn index_vmmemory_base(&self, index: u32) -> i32 { + self.index_vmmemory(index) + .checked_add(i32::from(self.vmmemory_base())) + .unwrap() + } + + /// Return the offset from the `memories` pointer to the `current_length` field in + /// `VMMemory` index `index`. + pub fn index_vmmemory_current_length(&self, index: u32) -> i32 { + self.index_vmmemory(index) + .checked_add(i32::from(self.vmmemory_current_length())) + .unwrap() + } + + /// Return the offset from the `tables` pointer to the `base` field in + /// `VMTable` index `index`. + pub fn index_vmtable_base(&self, index: u32) -> i32 { + self.index_vmtable(index) + .checked_add(i32::from(self.vmtable_base())) + .unwrap() + } + + /// Return the offset from the `tables` pointer to the `current_elements` field in + /// `VMTable` index `index`. + pub fn index_vmtable_current_elements(&self, index: u32) -> i32 { + self.index_vmtable(index) + .checked_add(i32::from(self.vmtable_current_elements())) + .unwrap() + } +} diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index aad42d2590..25e3d687c8 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -19,12 +19,16 @@ region = "1.0.0" lazy_static = "1.2.0" libc = { version = "0.2.44", default-features = false } errno = "0.2.4" +cast = { version = "0.2.2", default-features = false } [build-dependencies] cmake = "0.1.35" bindgen = "0.44.0" regex = "1.0.6" +[dev-dependencies] +memoffset = "0.2.1" + [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std"] diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 11cc2284fd..91f5ab28f5 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -68,6 +68,7 @@ fn relocate( let body = &mut compilation.functions[i]; match r.reloc { + #[cfg(target_pointer_width = "64")] Reloc::Abs8 => unsafe { let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; @@ -76,6 +77,7 @@ fn relocate( .unwrap(); write_unaligned(reloc_address as *mut u64, reloc_abs); }, + #[cfg(target_pointer_width = "32")] Reloc::X86PCRel4 => unsafe { let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 0abbc0c061..55b690c50d 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -40,6 +40,10 @@ extern crate alloc; #[macro_use] extern crate lazy_static; extern crate libc; +#[cfg(test)] +#[macro_use] +extern crate memoffset; +extern crate cast; mod code; mod execute; diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 51758183da..4ee93b609e 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,5 +1,6 @@ //! Memory management for linear memory. +use cast; use mmap::Mmap; use region; use std::fmt; @@ -69,8 +70,7 @@ impl LinearMemory { pub fn current_size(&self) -> u32 { assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; - assert_eq!(num_pages as u32 as usize, num_pages); - num_pages as u32 + cast::u32(num_pages).unwrap() } /// Grow memory by the specified amount of pages. diff --git a/lib/execute/src/vmcontext.rs b/lib/execute/src/vmcontext.rs new file mode 100644 index 0000000000..c50a5cec26 --- /dev/null +++ b/lib/execute/src/vmcontext.rs @@ -0,0 +1,182 @@ +//! This file declares `VMContext` and several related structs which contain +//! fields that JIT code accesses directly. + +use std::ptr::{size_of, align_of}; + +/// The main fields a JIT needs to access to utilize a WebAssembly linear, +/// memory, namely the start address and the size in bytes. +#[repr(C, packed)] +pub struct VMMemory { + pub base: *mut u8, + pub current_length: usize, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmmemory_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmmemory()); + assert_eq!(offset_of!(VMMemory, base), offsets.vmmemory_base()); + assert_eq!(offset_of!(VMMemory, current_length), offsets.vmmemory_current_length()); + } +} + +impl VMMemory { + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.base, self.current_length) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.base, self.current_length) } + } + + pub fn as_ptr(&self) -> *const u8 { + self.base + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.base + } + + pub fn len(&self) -> usize { + self.current_length + } +} + +#[repr(C, packed, align(8))] +pub struct VMGlobal { + pub storage: [u8; 8], + // If more elements are added here, remember to add offset_of tests below! +} + +/// The storage for a WebAssembly global. +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmglobal_alignment() { + assert!(align_of() <= align_of()); + assert!(align_of() >= align_of()); + assert!(align_of() >= align_of()); + assert!(align_of() >= align_of()); + } + + #[test] + fn check_vmglobal_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmglobal()); + } +} + +/// The main fields a JIT needs to access to utilize a WebAssembly table, +/// namely the start address and the number of elements. +#[repr(C, packed)] +pub struct VMTableStorage { + pub base: *mut u8, + pub current_elements: usize, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmtable_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmtable()); + assert_eq!(offset_of!(VMTableStorage, base), offsets.vmtable_base()); + assert_eq!(offset_of!(VMTableStorage, current_elements), offsets.vmtable_current_elements()); + } +} + +impl VMTableStorage { + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.base, self.current_length) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.base, self.current_length) } + } + + pub fn as_ptr(&self) -> *const u8 { + self.base + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.base + } + + pub fn len(&self) -> usize { + self.current_length + } +} + +/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift. +/// This has pointers to the globals, memories, tables, and other runtime +/// state associated with the current instance. +#[repr(C, packed)] +pub struct VMContext { + /// A pointer to an array of `VMMemory` instances, indexed by + /// WebAssembly memory index. + pub memories: *mut VMMemory, + /// A pointer to an array of globals. + pub globals: *mut u8, + /// A pointer to an array of `VMTableStorage` instances, indexed by + /// WebAssembly table index. + pub tables: *mut VMTableStorage, + /// A pointer to extra runtime state that isn't directly accessed + /// from JIT code. + pub instance: *mut u8, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmctx_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmctx()); + assert_eq!(offset_of!(VMContext, globals), offsets.vmctx_globals()); + assert_eq!(offset_of!(VMContext, memories), offsets.vmctx_memories()); + assert_eq!(offset_of!(VMContext, tables), offsets.vmctx_tables()); + assert_eq!(offset_of!(VMContext, instance), offsets.vmctx_instance()); + } +} + +impl VMContext { + unsafe pub fn global_storage(&mut self, index: usize) -> *mut u8 { + globals.add(index * global_size) + } + + unsafe pub fn global_i32(&mut self, index: usize) -> &mut i32 { + self.global_storage(index) as &mut i32 + } + + unsafe pub fn global_i64(&mut self, index: usize) -> &mut i64 { + self.global_storage(index) as &mut i64 + } + + unsafe pub fn global_f32(&mut self, index: usize) -> &mut f32 { + self.global_storage(index) as &mut f32 + } + + unsafe pub fn global_f64(&mut self, index: usize) -> &mut f64 { + self.global_storage(index) as &mut f64 + } + + unsafe pub fn memory(&mut self, index: usize) -> &mut VMMemory { + memories.add(index) as &mut VMMemory + } + + unsafe pub fn table(&mut self, index: usize) -> &mut VMTableStorage { + tables.add(index) as &mut VMTableStorage + } +}