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.
This commit is contained in:
Dan Gohman
2018-11-30 16:50:05 -08:00
parent 099f85f821
commit fe1643733b
10 changed files with 355 additions and 66 deletions

View File

@@ -13,7 +13,7 @@ readme = "README.md"
cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } 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-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" }
cranelift-wasm = { 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] [features]
default = ["std"] default = ["std"]

View File

@@ -15,17 +15,15 @@ use module::{
DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements, DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements,
}; };
use std::clone::Clone; use std::clone::Clone;
use std::mem;
use std::string::String; use std::string::String;
use std::vec::Vec; use std::vec::Vec;
use tunables::Tunables; use tunables::Tunables;
use vmcontext; use vmoffsets::VMOffsets;
use WASM_PAGE_SIZE; use WASM_PAGE_SIZE;
/// Compute a `ir::ExternalName` for a given wasm function index. /// Compute a `ir::ExternalName` for a given wasm function index.
pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { 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.as_u32())
ir::ExternalName::user(0, func_index.index() as u32)
} }
/// Object containing the standalone environment information. To be passed after creation as /// 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`. /// The external function declaration for implementing wasm's `grow_memory`.
grow_memory_extfunc: Option<FuncRef>, grow_memory_extfunc: Option<FuncRef>,
/// Offsets to struct fields accessed by JIT code.
offsets: VMOffsets,
} }
impl<'module_environment> FuncEnvironment<'module_environment> { impl<'module_environment> FuncEnvironment<'module_environment> {
@@ -120,6 +121,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
globals_base: None, globals_base: None,
current_memory_extfunc: None, current_memory_extfunc: None,
grow_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> impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
for ModuleEnvironment<'data, 'module> 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 { fn target_config(&self) -> isa::TargetFrontendConfig {
self.isa.frontend_config() 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 globals_base = self.globals_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load { let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx, 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(), global_type: self.pointer_type(),
readonly: true, readonly: true,
}); });
self.globals_base = Some(new_base); self.globals_base = Some(new_base);
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 { let gv = func.create_global_value(ir::GlobalValueData::IAddImm {
base: globals_base, 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(), global_type: self.pointer_type(),
}); });
GlobalVariable::Memory { 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 memories_base = self.memories_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load { let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx, 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(), global_type: self.pointer_type(),
readonly: true, readonly: true,
}); });
self.memories_base = Some(new_base); self.memories_base = Some(new_base);
new_base new_base
}); });
let offset = index.index() * mem::size_of::<vmcontext::VMMemory>();
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 // If we have a declared maximum, we can make this a "static" heap, which is
// allocated up front and never moved. // allocated up front and never moved.
let (offset_guard_size, heap_style, readonly_base) = match self.module.memory_plans[index] { 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 { let heap_bound = func.create_global_value(ir::GlobalValueData::Load {
base: memories_base, base: memories_base,
offset: Offset32::new( offset: Offset32::new(
offset32 + offset_of!(vmcontext::VMMemory, current_length) as i32, self.offsets.index_vmmemory_current_length(index.as_u32()),
), ),
global_type: I32, global_type: I32,
readonly: false, 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 { let heap_base = func.create_global_value(ir::GlobalValueData::Load {
base: memories_base, 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(), global_type: self.pointer_type(),
readonly: readonly_base, 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 tables_base = self.tables_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load { let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx, 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(), global_type: self.pointer_type(),
readonly: true, readonly: true,
}); });
self.tables_base = Some(new_base); self.tables_base = Some(new_base);
new_base new_base
}); });
let offset = index.index() * mem::size_of::<vmcontext::VMTable>();
let offset32 = offset as i32;
debug_assert_eq!(offset32 as usize, offset);
let base_gv = func.create_global_value(ir::GlobalValueData::Load { let base_gv = func.create_global_value(ir::GlobalValueData::Load {
base: tables_base, 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(), global_type: self.pointer_type(),
readonly: false, readonly: false,
}); });
let bound_gv = func.create_global_value(ir::GlobalValueData::Load { let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
base: tables_base, base: tables_base,
offset: Offset32::new( offset: Offset32::new(self.offsets.index_vmtable_current_elements(index.as_u32())),
offset32 + offset_of!(vmcontext::VMTable, current_num_elements) as i32,
),
global_type: I32, global_type: I32,
readonly: false, readonly: false,
}); });

View File

@@ -33,8 +33,6 @@
extern crate cranelift_codegen; extern crate cranelift_codegen;
extern crate cranelift_entity; extern crate cranelift_entity;
extern crate cranelift_wasm; extern crate cranelift_wasm;
#[macro_use]
extern crate memoffset;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;
@@ -43,7 +41,7 @@ mod compilation;
mod environ; mod environ;
mod module; mod module;
mod tunables; mod tunables;
mod vmcontext; mod vmoffsets;
pub use compilation::{ pub use compilation::{
compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations, compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations,

View File

@@ -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,
}

View File

@@ -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()
}
}

View File

@@ -19,12 +19,16 @@ region = "1.0.0"
lazy_static = "1.2.0" lazy_static = "1.2.0"
libc = { version = "0.2.44", default-features = false } libc = { version = "0.2.44", default-features = false }
errno = "0.2.4" errno = "0.2.4"
cast = { version = "0.2.2", default-features = false }
[build-dependencies] [build-dependencies]
cmake = "0.1.35" cmake = "0.1.35"
bindgen = "0.44.0" bindgen = "0.44.0"
regex = "1.0.6" regex = "1.0.6"
[dev-dependencies]
memoffset = "0.2.1"
[features] [features]
default = ["std"] default = ["std"]
std = ["cranelift-codegen/std", "cranelift-wasm/std"] std = ["cranelift-codegen/std", "cranelift-wasm/std"]

View File

@@ -68,6 +68,7 @@ fn relocate<F>(
let body = &mut compilation.functions[i]; let body = &mut compilation.functions[i];
match r.reloc { match r.reloc {
#[cfg(target_pointer_width = "64")]
Reloc::Abs8 => unsafe { Reloc::Abs8 => unsafe {
let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize;
let reloc_addend = r.addend as isize; let reloc_addend = r.addend as isize;
@@ -76,6 +77,7 @@ fn relocate<F>(
.unwrap(); .unwrap();
write_unaligned(reloc_address as *mut u64, reloc_abs); write_unaligned(reloc_address as *mut u64, reloc_abs);
}, },
#[cfg(target_pointer_width = "32")]
Reloc::X86PCRel4 => unsafe { Reloc::X86PCRel4 => unsafe {
let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize;
let reloc_addend = r.addend as isize; let reloc_addend = r.addend as isize;

View File

@@ -40,6 +40,10 @@ extern crate alloc;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate libc; extern crate libc;
#[cfg(test)]
#[macro_use]
extern crate memoffset;
extern crate cast;
mod code; mod code;
mod execute; mod execute;

View File

@@ -1,5 +1,6 @@
//! Memory management for linear memory. //! Memory management for linear memory.
use cast;
use mmap::Mmap; use mmap::Mmap;
use region; use region;
use std::fmt; use std::fmt;
@@ -69,8 +70,7 @@ impl LinearMemory {
pub fn current_size(&self) -> u32 { pub fn current_size(&self) -> u32 {
assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0);
let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize;
assert_eq!(num_pages as u32 as usize, num_pages); cast::u32(num_pages).unwrap()
num_pages as u32
} }
/// Grow memory by the specified amount of pages. /// Grow memory by the specified amount of pages.

View File

@@ -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<VMMemory>(), 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<VMGlobal>() <= align_of<i32>());
assert!(align_of<VMGlobal>() >= align_of<i64>());
assert!(align_of<VMGlobal>() >= align_of<f32>());
assert!(align_of<VMGlobal>() >= align_of<f64>());
}
#[test]
fn check_vmglobal_offsets() {
let offsets = VMOffsets::new(size_of<*mut u8>());
assert_eq!(size_of<VMGlobal>(), 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<VMTableStorage>(), 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<VMContext>(), 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
}
}