Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasmtime: Implement the custom-page-sizes proposal #8763

Merged
merged 19 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions cranelift/codegen/src/isa/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,23 @@ impl TargetIsa for AArch64Backend {
inst::Inst::function_alignment()
}

fn page_size_align_log2(&self) -> u8 {
use target_lexicon::*;
match self.triple().operating_system {
OperatingSystem::MacOSX { .. }
| OperatingSystem::Darwin
| OperatingSystem::Ios
| OperatingSystem::Tvos => {
debug_assert_eq!(1 << 14, 0x4000);
14
}
_ => {
debug_assert_eq!(1 << 16, 0x10000);
16
}
}
}

#[cfg(feature = "disas")]
fn to_capstone(&self) -> Result<capstone::Capstone, capstone::Error> {
use capstone::prelude::*;
Expand Down
13 changes: 13 additions & 0 deletions cranelift/codegen/src/isa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ pub struct TargetFrontendConfig {

/// The pointer width of the target.
pub pointer_width: PointerWidth,

/// The log2 of the target's page size and alignment.
///
/// Note that this may be an upper-bound that is larger than necessary for
/// some platforms since it may depend on runtime configuration.
pub page_size_align_log2: u8,
}

impl TargetFrontendConfig {
Expand Down Expand Up @@ -333,6 +339,12 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
/// alignment, for performance, required by this ISA.
fn function_alignment(&self) -> FunctionAlignment;

/// The log2 of the target's page size and alignment.
///
/// Note that this may be an upper-bound that is larger than necessary for
/// some platforms since it may depend on runtime configuration.
fn page_size_align_log2(&self) -> u8;

/// Create a polymorphic TargetIsa from this specific implementation.
fn wrapped(self) -> OwnedTargetIsa
where
Expand Down Expand Up @@ -433,6 +445,7 @@ impl<'a> dyn TargetIsa + 'a {
TargetFrontendConfig {
default_call_conv: self.default_call_conv(),
pointer_width: self.pointer_width(),
page_size_align_log2: self.page_size_align_log2(),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions cranelift/codegen/src/isa/riscv64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ impl TargetIsa for Riscv64Backend {
inst::Inst::function_alignment()
}

fn page_size_align_log2(&self) -> u8 {
debug_assert_eq!(1 << 12, 0x1000);
12
}

#[cfg(feature = "disas")]
fn to_capstone(&self) -> Result<capstone::Capstone, capstone::Error> {
use capstone::prelude::*;
Expand Down
5 changes: 5 additions & 0 deletions cranelift/codegen/src/isa/s390x/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ impl TargetIsa for S390xBackend {
inst::Inst::function_alignment()
}

fn page_size_align_log2(&self) -> u8 {
debug_assert_eq!(1 << 12, 0x1000);
12
}

#[cfg(feature = "disas")]
fn to_capstone(&self) -> Result<capstone::Capstone, capstone::Error> {
use capstone::prelude::*;
Expand Down
5 changes: 5 additions & 0 deletions cranelift/codegen/src/isa/x64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ impl TargetIsa for X64Backend {
Inst::function_alignment()
}

fn page_size_align_log2(&self) -> u8 {
debug_assert_eq!(1 << 12, 0x1000);
12
}

#[cfg(feature = "disas")]
fn to_capstone(&self) -> Result<capstone::Capstone, capstone::Error> {
use capstone::prelude::*;
Expand Down
1 change: 1 addition & 0 deletions cranelift/frontend/src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ mod tests {
TargetFrontendConfig {
default_call_conv: CallConv::SystemV,
pointer_width: PointerWidth::U64,
page_size_align_log2: 12,
}
}

Expand Down
22 changes: 20 additions & 2 deletions cranelift/wasm/src/code_translator/bounds_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ where
let spectre_mitigations_enabled = env.heap_access_spectre_mitigation();
let pcc = env.proof_carrying_code();

let host_page_size_log2 = env.target_config().page_size_align_log2;
let can_use_virtual_memory = heap.page_size_log2 >= host_page_size_log2;

let make_compare = |builder: &mut FunctionBuilder,
compare_kind: IntCC,
lhs: ir::Value,
Expand Down Expand Up @@ -188,7 +191,9 @@ where
// offset immediates -- which is a common code pattern when accessing
// multiple fields in the same struct that is in linear memory --
// will all emit the same `index > bound` check, which we can GVN.
HeapStyle::Dynamic { bound_gv } if offset_and_size <= heap.offset_guard_size => {
HeapStyle::Dynamic { bound_gv }
if can_use_virtual_memory && offset_and_size <= heap.offset_guard_size =>
{
let bound = get_dynamic_heap_bound(builder, env, heap);
let oob = make_compare(
builder,
Expand Down Expand Up @@ -313,6 +318,10 @@ where
// bound`, since we will end up being out-of-bounds regardless of the
// given `index`.
HeapStyle::Static { bound } if offset_and_size > bound.into() => {
assert!(
can_use_virtual_memory,
"static memories require the ability to use virtual memory"
);
env.before_unconditionally_trapping_memory_access(builder)?;
builder.ins().trap(ir::TrapCode::HeapOutOfBounds);
Unreachable
Expand Down Expand Up @@ -357,10 +366,15 @@ where
// within the guard page region, neither of which require emitting an
// explicit bounds check.
HeapStyle::Static { bound }
if heap.index_type == ir::types::I32
if can_use_virtual_memory
&& heap.index_type == ir::types::I32
&& u64::from(u32::MAX)
<= u64::from(bound) + u64::from(heap.offset_guard_size) - offset_and_size =>
{
assert!(
can_use_virtual_memory,
"static memories require the ability to use virtual memory"
);
Reachable(compute_addr(
&mut builder.cursor(),
heap,
Expand All @@ -386,6 +400,10 @@ where
// precise, not rely on the virtual memory subsystem at all, and not
// factor in the guard pages here.
HeapStyle::Static { bound } => {
assert!(
can_use_virtual_memory,
"static memories require the ability to use virtual memory"
);
// NB: this subtraction cannot wrap because we didn't hit the first
// special case.
let adjusted_bound = u64::from(bound) - offset_and_size;
Expand Down
3 changes: 3 additions & 0 deletions cranelift/wasm/src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ pub struct HeapData {

/// The memory type for the pointed-to memory, if using proof-carrying code.
pub memory_type: Option<MemoryType>,

/// The log2 of this memory's page size.
pub page_size_log2: u8,
}

/// Style of heap including style-specific information.
Expand Down
20 changes: 5 additions & 15 deletions cranelift/wasm/src/sections_translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use crate::environ::ModuleEnvironment;
use crate::wasm_unsupported;
use crate::{
DataIndex, ElemIndex, FuncIndex, GlobalIndex, Memory, MemoryIndex, TableIndex, Tag, TagIndex,
DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Tag, TagIndex,
TypeIndex, WasmError, WasmResult,
};
use cranelift_entity::packed_option::ReservedValue;
Expand All @@ -20,20 +20,11 @@ use std::vec::Vec;
use wasmparser::{
Data, DataKind, DataSectionReader, Element, ElementItems, ElementKind, ElementSectionReader,
Export, ExportSectionReader, ExternalKind, FunctionSectionReader, GlobalSectionReader,
ImportSectionReader, MemorySectionReader, MemoryType, Operator, TableSectionReader,
TagSectionReader, TagType, TypeRef, TypeSectionReader,
ImportSectionReader, MemorySectionReader, Operator, TableSectionReader, TagSectionReader,
TagType, TypeRef, TypeSectionReader,
};
use wasmtime_types::ConstExpr;

fn memory(ty: MemoryType) -> Memory {
Memory {
minimum: ty.initial,
maximum: ty.maximum,
shared: ty.shared,
memory64: ty.memory64,
}
}

fn tag(e: TagType) -> Tag {
match e.kind {
wasmparser::TagKind::Exception => Tag {
Expand Down Expand Up @@ -75,7 +66,7 @@ pub fn parse_import_section<'data>(
)?;
}
TypeRef::Memory(ty) => {
environ.declare_memory_import(memory(ty), import.module, import.name)?;
environ.declare_memory_import(ty.into(), import.module, import.name)?;
}
TypeRef::Tag(e) => {
environ.declare_tag_import(tag(e), import.module, import.name)?;
Expand Down Expand Up @@ -139,8 +130,7 @@ pub fn parse_memory_section(
environ.reserve_memories(memories.count())?;

for entry in memories {
let memory = memory(entry?);
environ.declare_memory(memory)?;
environ.declare_memory(entry?.into())?;
}

Ok(())
Expand Down
71 changes: 47 additions & 24 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::mem;
use wasmparser::Operator;
use wasmtime_environ::{
BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypesBuilder,
PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE,
PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets,
};
use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};

Expand Down Expand Up @@ -680,7 +680,13 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
}
}

fn cast_pointer_to_memory_index(
/// Convert the target pointer-sized integer `val` that is holding a memory
/// length (or the `-1` `memory.grow`-failed sentinel) into the memory's
/// index type.
///
/// This might involve extending or truncating it depending on the memory's
/// index type and the target's pointer type.
fn convert_memory_length_to_index_type(
&self,
mut pos: FuncCursor<'_>,
val: ir::Value,
Expand All @@ -698,18 +704,32 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
} else if pointer_type.bits() > desired_type.bits() {
pos.ins().ireduce(desired_type, val)
} else {
// Note that we `sextend` instead of the probably expected
// `uextend`. This function is only used within the contexts of
// `memory.size` and `memory.grow` where we're working with units of
// pages instead of actual bytes, so we know that the upper bit is
// always cleared for "valid values". The one case we care about
// sextend would be when the return value of `memory.grow` is `-1`,
// in which case we want to copy the sign bit.
//
// This should only come up on 32-bit hosts running wasm64 modules,
// which at some point also makes you question various assumptions
// made along the way...
pos.ins().sextend(desired_type, val)
// We have a 64-bit memory on a 32-bit host -- this combo doesn't
// really make a whole lot of sense to do from a user perspective
// but that is neither here nor there. We want to logically do an
// unsigned extend *except* when we are given the `-1` sentinel,
// which we must preserve as `-1` in the wider type.
match self.module.memory_plans[index].memory.page_size_log2 {
16 => {
// In the case that we have default page sizes, we can
// always sign extend, since valid memory lengths (in pages)
// never have their sign bit set, and so if the sign bit is
// set then this must be the `-1` sentinel, which we want to
// preserve through the extension.
pos.ins().sextend(desired_type, val)
}
0 => {
// For single-byte pages, we have to explicitly check for
// `-1` and choose whether to do an unsigned extension or
// return a larger `-1` because there are valid memory
// lengths (in pages) that have the sign bit set.
let extended = pos.ins().uextend(desired_type, val);
let neg_one = pos.ins().iconst(desired_type, -1);
let is_failure = pos.ins().icmp_imm(IntCC::Equal, val, -1);
pos.ins().select(is_failure, neg_one, extended)
}
_ => unreachable!("only page sizes 2**0 and 2**16 are currently valid"),
}
}
}

Expand Down Expand Up @@ -2001,21 +2021,21 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m

let min_size = self.module.memory_plans[index]
.memory
.minimum
.checked_mul(u64::from(WASM_PAGE_SIZE))
.unwrap_or_else(|| {
.minimum_byte_size()
.unwrap_or_else(|_| {
// The only valid Wasm memory size that won't fit in a 64-bit
// integer is the maximum memory64 size (2^64) which is one
// larger than `u64::MAX` (2^64 - 1). In this case, just say the
// minimum heap size is `u64::MAX`.
debug_assert_eq!(self.module.memory_plans[index].memory.minimum, 1 << 48);
debug_assert_eq!(self.module.memory_plans[index].memory.page_size(), 1 << 16);
u64::MAX
});

let max_size = self.module.memory_plans[index]
.memory
.maximum
.and_then(|max| max.checked_mul(u64::from(WASM_PAGE_SIZE)));
.maximum_byte_size()
.ok();

let (ptr, base_offset, current_length_offset, ptr_memtype) = {
let vmctx = self.vmctx(func);
Expand Down Expand Up @@ -2069,6 +2089,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
}
};

let page_size_log2 = self.module.memory_plans[index].memory.page_size_log2;

// 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, base_fact, memory_type) =
Expand Down Expand Up @@ -2233,6 +2255,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
style: heap_style,
index_type: self.memory_index_type(index),
memory_type,
page_size_log2,
}))
}

Expand Down Expand Up @@ -2397,7 +2420,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let val = self.cast_memory_index_to_i64(&mut pos, val, index);
let call_inst = pos.ins().call(memory_grow, &[vmctx, val, memory_index]);
let result = *pos.func.dfg.inst_results(call_inst).first().unwrap();
Ok(self.cast_pointer_to_memory_index(pos, result, index))
Ok(self.convert_memory_length_to_index_type(pos, result, index))
}

fn translate_memory_size(
Expand Down Expand Up @@ -2469,11 +2492,11 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
}
}
};
let current_length_in_pages = pos
.ins()
.udiv_imm(current_length_in_bytes, i64::from(WASM_PAGE_SIZE));

Ok(self.cast_pointer_to_memory_index(pos, current_length_in_pages, index))
let page_size_log2 = i64::from(self.module.memory_plans[index].memory.page_size_log2);
let current_length_in_pages = pos.ins().ushr_imm(current_length_in_bytes, page_size_log2);

Ok(self.convert_memory_length_to_index_type(pos, current_length_in_pages, index))
}

fn translate_memory_copy(
Expand Down
Loading
Loading