* feat: implement memory.atomic.notify,wait32,wait64 Added the parking_spot crate, which provides the needed registry for the operations. Signed-off-by: Harald Hoyer <harald@profian.com> * fix: change trap message for HeapMisaligned The threads spec test wants "unaligned atomic" instead of "misaligned memory access". Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add test for atomic wait on non-shared memory Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add tests/spec_testsuite/proposals/threads without pooling and reference types. Also "shared_memory" is added to the "spectest" interface. Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add atomics_notify.wast checking that notify with 0 waiters returns 0 on shared and non-shared memory. Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add tests for atomic wait on shared memory - return 2 - timeout for 0 - return 2 - timeout for 1000ns - return 1 - invalid value Signed-off-by: Harald Hoyer <harald@profian.com> * fixup! feat: implement memory.atomic.notify,wait32,wait64 Signed-off-by: Harald Hoyer <harald@profian.com> * fixup! feat: implement memory.atomic.notify,wait32,wait64 Signed-off-by: Harald Hoyer <harald@profian.com> Signed-off-by: Harald Hoyer <harald@profian.com>
234 lines
8.5 KiB
Rust
234 lines
8.5 KiB
Rust
use crate::obj::ELF_WASMTIME_TRAPS;
|
|
use object::write::{Object, StandardSegment};
|
|
use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
|
|
use std::convert::TryFrom;
|
|
use std::fmt;
|
|
use std::ops::Range;
|
|
|
|
/// A helper structure to build the custom-encoded section of a wasmtime
|
|
/// compilation image which encodes trap information.
|
|
///
|
|
/// This structure is incrementally fed the results of compiling individual
|
|
/// functions and handles all the encoding internally, allowing usage of
|
|
/// `lookup_trap_code` below with the resulting section.
|
|
#[derive(Default)]
|
|
pub struct TrapEncodingBuilder {
|
|
offsets: Vec<U32Bytes<LittleEndian>>,
|
|
traps: Vec<u8>,
|
|
last_offset: u32,
|
|
}
|
|
|
|
/// Information about trap.
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub struct TrapInformation {
|
|
/// The offset of the trapping instruction in native code.
|
|
///
|
|
/// This is relative to the beginning of the function.
|
|
pub code_offset: u32,
|
|
|
|
/// Code of the trap.
|
|
pub trap_code: Trap,
|
|
}
|
|
|
|
// The code can be accessed from the c-api, where the possible values are
|
|
// translated into enum values defined there:
|
|
//
|
|
// * `wasm_trap_code` in c-api/src/trap.rs, and
|
|
// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
|
|
//
|
|
// These need to be kept in sync.
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
#[allow(missing_docs)]
|
|
pub enum Trap {
|
|
/// The current stack space was exhausted.
|
|
StackOverflow,
|
|
|
|
/// An out-of-bounds memory access.
|
|
MemoryOutOfBounds,
|
|
|
|
/// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
|
|
HeapMisaligned,
|
|
|
|
/// An out-of-bounds access to a table.
|
|
TableOutOfBounds,
|
|
|
|
/// Indirect call to a null table entry.
|
|
IndirectCallToNull,
|
|
|
|
/// Signature mismatch on indirect call.
|
|
BadSignature,
|
|
|
|
/// An integer arithmetic operation caused an overflow.
|
|
IntegerOverflow,
|
|
|
|
/// An integer division by zero.
|
|
IntegerDivisionByZero,
|
|
|
|
/// Failed float-to-int conversion.
|
|
BadConversionToInteger,
|
|
|
|
/// Code that was supposed to have been unreachable was reached.
|
|
UnreachableCodeReached,
|
|
|
|
/// Execution has potentially run too long and may be interrupted.
|
|
Interrupt,
|
|
|
|
/// When the `component-model` feature is enabled this trap represents a
|
|
/// function that was `canon lift`'d, then `canon lower`'d, then called.
|
|
/// This combination of creation of a function in the component model
|
|
/// generates a function that always traps and, when called, produces this
|
|
/// flavor of trap.
|
|
AlwaysTrapAdapter,
|
|
|
|
/// When wasm code is configured to consume fuel and it runs out of fuel
|
|
/// then this trap will be raised.
|
|
OutOfFuel,
|
|
|
|
/// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
|
|
AtomicWaitNonSharedMemory,
|
|
// if adding a variant here be sure to update the `check!` macro below
|
|
}
|
|
|
|
impl fmt::Display for Trap {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
use Trap::*;
|
|
|
|
let desc = match self {
|
|
StackOverflow => "call stack exhausted",
|
|
MemoryOutOfBounds => "out of bounds memory access",
|
|
HeapMisaligned => "unaligned atomic",
|
|
TableOutOfBounds => "undefined element: out of bounds table access",
|
|
IndirectCallToNull => "uninitialized element",
|
|
BadSignature => "indirect call type mismatch",
|
|
IntegerOverflow => "integer overflow",
|
|
IntegerDivisionByZero => "integer divide by zero",
|
|
BadConversionToInteger => "invalid conversion to integer",
|
|
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
|
Interrupt => "interrupt",
|
|
AlwaysTrapAdapter => "degenerate component adapter called",
|
|
OutOfFuel => "all fuel consumed by WebAssembly",
|
|
AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
|
|
};
|
|
write!(f, "wasm trap: {desc}")
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for Trap {}
|
|
|
|
impl TrapEncodingBuilder {
|
|
/// Appends trap information about a function into this section.
|
|
///
|
|
/// This function is called to describe traps for the `func` range
|
|
/// specified. The `func` offsets are specified relative to the text section
|
|
/// itself, and the `traps` offsets are specified relative to the start of
|
|
/// `func`.
|
|
///
|
|
/// This is required to be called in-order for increasing ranges of `func`
|
|
/// to ensure the final array is properly sorted. Additionally `traps` must
|
|
/// be sorted.
|
|
pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
|
|
// NB: for now this only supports <=4GB text sections in object files.
|
|
// Alternative schemes will need to be created for >32-bit offsets to
|
|
// avoid making this section overly large.
|
|
let func_start = u32::try_from(func.start).unwrap();
|
|
let func_end = u32::try_from(func.end).unwrap();
|
|
|
|
// Sanity-check to ensure that functions are pushed in-order, otherwise
|
|
// the `offsets` array won't be sorted which is our goal.
|
|
assert!(func_start >= self.last_offset);
|
|
|
|
self.offsets.reserve(traps.len());
|
|
self.traps.reserve(traps.len());
|
|
for info in traps {
|
|
let pos = func_start + info.code_offset;
|
|
assert!(pos >= self.last_offset);
|
|
self.offsets.push(U32Bytes::new(LittleEndian, pos));
|
|
self.traps.push(info.trap_code as u8);
|
|
self.last_offset = pos;
|
|
}
|
|
|
|
self.last_offset = func_end;
|
|
}
|
|
|
|
/// Encodes this section into the object provided.
|
|
pub fn append_to(self, obj: &mut Object) {
|
|
let section = obj.add_section(
|
|
obj.segment_name(StandardSegment::Data).to_vec(),
|
|
ELF_WASMTIME_TRAPS.as_bytes().to_vec(),
|
|
SectionKind::ReadOnlyData,
|
|
);
|
|
|
|
// NB: this matches the encoding expected by `lookup` below.
|
|
let amt = u32::try_from(self.traps.len()).unwrap();
|
|
obj.append_section_data(section, &amt.to_le_bytes(), 1);
|
|
obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1);
|
|
obj.append_section_data(section, &self.traps, 1);
|
|
}
|
|
}
|
|
|
|
/// Decodes the provided trap information section and attempts to find the trap
|
|
/// code corresponding to the `offset` specified.
|
|
///
|
|
/// The `section` provided is expected to have been built by
|
|
/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
|
|
/// offset within the text section of the compilation image.
|
|
pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
|
|
let mut section = Bytes(section);
|
|
// NB: this matches the encoding written by `append_to` above.
|
|
let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
|
|
let count = usize::try_from(count.get(LittleEndian)).ok()?;
|
|
let (offsets, traps) =
|
|
object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
|
|
debug_assert_eq!(traps.len(), count);
|
|
|
|
// The `offsets` table is sorted in the trap section so perform a binary
|
|
// search of the contents of this section to find whether `offset` is an
|
|
// entry in the section. Note that this is a precise search because trap pcs
|
|
// should always be precise as well as our metadata about them, which means
|
|
// we expect an exact match to correspond to a trap opcode.
|
|
//
|
|
// Once an index is found within the `offsets` array then that same index is
|
|
// used to lookup from the `traps` list of bytes to get the trap code byte
|
|
// corresponding to this offset.
|
|
let offset = u32::try_from(offset).ok()?;
|
|
let index = offsets
|
|
.binary_search_by_key(&offset, |val| val.get(LittleEndian))
|
|
.ok()?;
|
|
debug_assert!(index < traps.len());
|
|
let trap = *traps.get(index)?;
|
|
|
|
// FIXME: this could use some sort of derive-like thing to avoid having to
|
|
// deduplicate the names here.
|
|
//
|
|
// This simply converts from the `trap`, a `u8`, to the `Trap` enum.
|
|
macro_rules! check {
|
|
($($name:ident)*) => ($(if trap == Trap::$name as u8 {
|
|
return Some(Trap::$name);
|
|
})*);
|
|
}
|
|
|
|
check! {
|
|
StackOverflow
|
|
MemoryOutOfBounds
|
|
HeapMisaligned
|
|
TableOutOfBounds
|
|
IndirectCallToNull
|
|
BadSignature
|
|
IntegerOverflow
|
|
IntegerDivisionByZero
|
|
BadConversionToInteger
|
|
UnreachableCodeReached
|
|
Interrupt
|
|
AlwaysTrapAdapter
|
|
OutOfFuel
|
|
AtomicWaitNonSharedMemory
|
|
}
|
|
|
|
if cfg!(debug_assertions) {
|
|
panic!("missing mapping for {}", trap);
|
|
} else {
|
|
None
|
|
}
|
|
}
|