* Implement interrupting wasm code, reimplement stack overflow This commit is a relatively large change for wasmtime with two main goals: * Primarily this enables interrupting executing wasm code with a trap, preventing infinite loops in wasm code. Note that resumption of the wasm code is not a goal of this commit. * Additionally this commit reimplements how we handle stack overflow to ensure that host functions always have a reasonable amount of stack to run on. This fixes an issue where we might longjmp out of a host function, skipping destructors. Lots of various odds and ends end up falling out in this commit once the two goals above were implemented. The strategy for implementing this was also lifted from Spidermonkey and existing functionality inside of Cranelift. I've tried to write up thorough documentation of how this all works in `crates/environ/src/cranelift.rs` where gnarly-ish bits are. A brief summary of how this works is that each function and each loop header now checks to see if they're interrupted. Interrupts and the stack overflow check are actually folded into one now, where function headers check to see if they've run out of stack and the sentinel value used to indicate an interrupt, checked in loop headers, tricks functions into thinking they're out of stack. An interrupt is basically just writing a value to a location which is read by JIT code. When interrupts are delivered and what triggers them has been left up to embedders of the `wasmtime` crate. The `wasmtime::Store` type has a method to acquire an `InterruptHandle`, where `InterruptHandle` is a `Send` and `Sync` type which can travel to other threads (or perhaps even a signal handler) to get notified from. It's intended that this provides a good degree of flexibility when interrupting wasm code. Note though that this does have a large caveat where interrupts don't work when you're interrupting host code, so if you've got a host import blocking for a long time an interrupt won't actually be received until the wasm starts running again. Some fallout included from this change is: * Unix signal handlers are no longer registered with `SA_ONSTACK`. Instead they run on the native stack the thread was already using. This is possible since stack overflow isn't handled by hitting the guard page, but rather it's explicitly checked for in wasm now. Native stack overflow will continue to abort the process as usual. * Unix sigaltstack management is now no longer necessary since we don't use it any more. * Windows no longer has any need to reset guard pages since we no longer try to recover from faults on guard pages. * On all targets probestack intrinsics are disabled since we use a different mechanism for catching stack overflow. * The C API has been updated with interrupts handles. An example has also been added which shows off how to interrupt a module. Closes #139 Closes #860 Closes #900 * Update comment about magical interrupt value * Store stack limit as a global value, not a closure * Run rustfmt * Handle review comments * Add a comment about SA_ONSTACK * Use `usize` for type of `INTERRUPTED` * Parse human-readable durations * Bring back sigaltstack handling Allows libstd to print out stack overflow on failure still. * Add parsing and emission of stack limit-via-preamble * Fix new example for new apis * Fix host segfault test in release mode * Fix new doc example
523 lines
18 KiB
Rust
523 lines
18 KiB
Rust
//! Cranelift IR entity references.
|
|
//!
|
|
//! Instructions in Cranelift IR need to reference other entities in the function. This can be other
|
|
//! parts of the function like basic blocks or stack slots, or it can be external entities
|
|
//! that are declared in the function preamble in the text format.
|
|
//!
|
|
//! These entity references in instruction operands are not implemented as Rust references both
|
|
//! because Rust's ownership and mutability rules make it difficult, and because 64-bit pointers
|
|
//! take up a lot of space, and we want a compact in-memory representation. Instead, entity
|
|
//! references are structs wrapping a `u32` index into a table in the `Function` main data
|
|
//! structure. There is a separate index type for each entity type, so we don't lose type safety.
|
|
//!
|
|
//! The `entities` module defines public types for the entity references along with constants
|
|
//! representing an invalid reference. We prefer to use `Option<EntityRef>` whenever possible, but
|
|
//! unfortunately that type is twice as large as the 32-bit index type on its own. Thus, compact
|
|
//! data structures use the `PackedOption<EntityRef>` representation, while function arguments and
|
|
//! return values prefer the more Rust-like `Option<EntityRef>` variant.
|
|
//!
|
|
//! The entity references all implement the `Display` trait in a way that matches the textual IR
|
|
//! format.
|
|
|
|
use crate::entity::entity_impl;
|
|
use core::fmt;
|
|
use core::u32;
|
|
#[cfg(feature = "enable-serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// An opaque reference to a [basic block](https://en.wikipedia.org/wiki/Basic_block) in a
|
|
/// [`Function`](super::function::Function).
|
|
///
|
|
/// You can get a `Block` using
|
|
/// [`FunctionBuilder::create_block`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_block)
|
|
///
|
|
/// While the order is stable, it is arbitrary and does not necessarily resemble the layout order.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Block(u32);
|
|
entity_impl!(Block, "block");
|
|
|
|
impl Block {
|
|
/// Create a new block reference from its number. This corresponds to the `blockNN` representation.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to an SSA value.
|
|
///
|
|
/// You can get a constant `Value` from the following
|
|
/// [`InstBuilder`](super::InstBuilder) instructions:
|
|
///
|
|
/// - [`iconst`](super::InstBuilder::iconst) for integer constants
|
|
/// - [`f32const`](super::InstBuilder::f32const) for 32-bit float constants
|
|
/// - [`f64const`](super::InstBuilder::f64const) for 64-bit float constants
|
|
/// - [`bconst`](super::InstBuilder::bconst) for boolean constants
|
|
/// - [`vconst`](super::InstBuilder::vconst) for vector constants
|
|
/// - [`null`](super::InstBuilder::null) for null reference constants
|
|
///
|
|
/// Any `InstBuilder` instruction that has an output will also return a `Value`.
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Value(u32);
|
|
entity_impl!(Value, "v");
|
|
|
|
impl Value {
|
|
/// Create a value from its number representation.
|
|
/// This is the number in the `vNN` notation.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX / 2 {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to an instruction in a [`Function`](super::Function).
|
|
///
|
|
/// Most usage of `Inst` is internal. `Inst`ructions are returned by
|
|
/// [`InstBuilder`](super::InstBuilder) instructions that do not return a
|
|
/// [`Value`], such as control flow and trap instructions.
|
|
///
|
|
/// If you look around the API, you can find many inventive uses for `Inst`,
|
|
/// such as [annotating specific instructions with a comment][inst_comment]
|
|
/// or [performing reflection at compile time](super::DataFlowGraph::analyze_branch)
|
|
/// on the type of instruction.
|
|
///
|
|
/// [inst_comment]: https://github.com/bjorn3/rustc_codegen_cranelift/blob/0f8814fd6da3d436a90549d4bb19b94034f2b19c/src/pretty_clif.rs
|
|
///
|
|
/// While the order is stable, it is arbitrary and does not necessarily resemble the layout order.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Inst(u32);
|
|
entity_impl!(Inst, "inst");
|
|
|
|
/// An opaque reference to a stack slot.
|
|
///
|
|
/// Stack slots represent an address on the
|
|
/// [call stack](https://en.wikipedia.org/wiki/Call_stack).
|
|
///
|
|
/// `StackSlot`s can be created with
|
|
/// [`FunctionBuilder::create_stackslot`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_stack_slot).
|
|
///
|
|
/// `StackSlot`s are most often used with
|
|
/// [`stack_addr`](super::InstBuilder::stack_addr),
|
|
/// [`stack_load`](super::InstBuilder::stack_load), and
|
|
/// [`stack_store`](super::InstBuilder::stack_store).
|
|
///
|
|
/// While the order is stable, it is arbitrary and does not necessarily resemble the stack order.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
|
pub struct StackSlot(u32);
|
|
entity_impl!(StackSlot, "ss");
|
|
|
|
impl StackSlot {
|
|
/// Create a new stack slot reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a global value.
|
|
///
|
|
/// A `GlobalValue` is a [`Value`](Value) that will be live across the entire
|
|
/// function lifetime. It can be preloaded from other global values.
|
|
///
|
|
/// You can create a `GlobalValue` in the following ways:
|
|
///
|
|
/// - When compiling to WASM, you can use it to load values from a
|
|
/// [`VmContext`](super::GlobalValueData::VMContext) using
|
|
/// [`FuncEnvironment::make_global`](https://docs.rs/cranelift-wasm/*/cranelift_wasm/trait.FuncEnvironment.html#tymethod.make_global).
|
|
/// - When compiling to native code, you can use it for objects in static memory with
|
|
/// [`Module::declare_data_in_func`](https://docs.rs/cranelift-module/*/cranelift_module/struct.Module.html#method.declare_data_in_func).
|
|
/// - For any compilation target, it can be registered with
|
|
/// [`FunctionBuilder::create_global_value`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_global_value).
|
|
///
|
|
/// `GlobalValue`s can be retrieved with
|
|
/// [`InstBuilder:global_value`](super::InstBuilder::global_value).
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct GlobalValue(u32);
|
|
entity_impl!(GlobalValue, "gv");
|
|
|
|
impl GlobalValue {
|
|
/// Create a new global value reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a constant.
|
|
///
|
|
/// You can store [`ConstantData`](super::ConstantData) in a
|
|
/// [`ConstantPool`](super::ConstantPool) for efficient storage and retrieval.
|
|
/// See [`ConstantPool::insert`](super::ConstantPool::insert).
|
|
///
|
|
/// While the order is stable, it is arbitrary and does not necessarily resemble the order in which
|
|
/// the constants are written in the constant pool.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
|
pub struct Constant(u32);
|
|
entity_impl!(Constant, "const");
|
|
|
|
impl Constant {
|
|
/// Create a const reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to an immediate.
|
|
///
|
|
/// Some immediates (e.g. SIMD shuffle masks) are too large to store in the
|
|
/// [`InstructionData`](super::instructions::InstructionData) struct and therefore must be
|
|
/// tracked separately in [`DataFlowGraph::immediates`](super::dfg::DataFlowGraph). `Immediate`
|
|
/// provides a way to reference values stored there.
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Immediate(u32);
|
|
entity_impl!(Immediate, "imm");
|
|
|
|
impl Immediate {
|
|
/// Create an immediate reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a [jump table](https://en.wikipedia.org/wiki/Branch_table).
|
|
///
|
|
/// `JumpTable`s are used for indirect branching and are specialized for dense,
|
|
/// 0-based jump offsets. If you want a jump table which doesn't start at 0,
|
|
/// or is not contiguous, consider using a [`Switch`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.Switch.html) instead.
|
|
///
|
|
/// `JumpTable` are used with [`br_table`](super::InstBuilder::br_table).
|
|
///
|
|
/// `JumpTable`s can be created with
|
|
/// [`create_jump_table`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_jump_table).
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
|
pub struct JumpTable(u32);
|
|
entity_impl!(JumpTable, "jt");
|
|
|
|
impl JumpTable {
|
|
/// Create a new jump table reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to another [`Function`](super::Function).
|
|
///
|
|
/// `FuncRef`s are used for [direct](super::InstBuilder::call) function calls
|
|
/// and by [`func_addr`](super::InstBuilder::func_addr) for use in
|
|
/// [indirect](super::InstBuilder::call_indirect) function calls.
|
|
///
|
|
/// `FuncRef`s can be created with
|
|
///
|
|
/// - [`FunctionBuilder::import_function`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.import_function)
|
|
/// for external functions
|
|
/// - [`Module::declare_func_in_func`](https://docs.rs/cranelift-module/*/cranelift_module/struct.Module.html#method.declare_func_in_func)
|
|
/// for functions declared elsewhere in the same native
|
|
/// [`Module`](https://docs.rs/cranelift-module/*/cranelift_module/struct.Module.html)
|
|
/// - [`FuncEnvironment::make_direct_func`](https://docs.rs/cranelift-wasm/*/cranelift_wasm/trait.FuncEnvironment.html#tymethod.make_direct_func)
|
|
/// for functions declared in the same WebAssembly
|
|
/// [`FuncEnvironment`](https://docs.rs/cranelift-wasm/*/cranelift_wasm/trait.FuncEnvironment.html#tymethod.make_direct_func)
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct FuncRef(u32);
|
|
entity_impl!(FuncRef, "fn");
|
|
|
|
impl FuncRef {
|
|
/// Create a new external function reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a function [`Signature`](super::Signature).
|
|
///
|
|
/// `SigRef`s are used to declare a function with
|
|
/// [`FunctionBuiler::import_function`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.import_function)
|
|
/// as well as to make an [indirect function call](super::InstBuilder::call_indirect).
|
|
///
|
|
/// `SigRef`s can be created with
|
|
/// [`FunctionBuilder::import_signature`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.import_signature).
|
|
///
|
|
/// You can retrieve the [`Signature`](super::Signature) that was used to create a `SigRef` with
|
|
/// [`FunctionBuilder::signature`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.signature) or
|
|
/// [`func.dfg.signatures`](super::dfg::DataFlowGraph::signatures).
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct SigRef(u32);
|
|
entity_impl!(SigRef, "sig");
|
|
|
|
impl SigRef {
|
|
/// Create a new function signature reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a [heap](https://en.wikipedia.org/wiki/Memory_management#DYNAMIC).
|
|
///
|
|
/// Heaps are used to access dynamically allocated memory through
|
|
/// [`heap_addr`](super::InstBuilder::heap_addr).
|
|
///
|
|
/// To create a heap, use [`FunctionBuilder::create_heap`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_heap).
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Heap(u32);
|
|
entity_impl!(Heap, "heap");
|
|
|
|
impl Heap {
|
|
/// Create a new heap reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to a [WebAssembly
|
|
/// table](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#WebAssembly_tables).
|
|
///
|
|
/// `Table`s are used to store a list of function references.
|
|
/// They can be created with [`FuncEnvironment::make_table`](https://docs.rs/cranelift-wasm/*/cranelift_wasm/trait.FuncEnvironment.html#tymethod.make_table).
|
|
/// They can be used with
|
|
/// [`FuncEnvironment::translate_call_indirect`](https://docs.rs/cranelift-wasm/*/cranelift_wasm/trait.FuncEnvironment.html#tymethod.translate_call_indirect).
|
|
///
|
|
/// While the order is stable, it is arbitrary.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Table(u32);
|
|
entity_impl!(Table, "table");
|
|
|
|
impl Table {
|
|
/// Create a new table reference from its number.
|
|
///
|
|
/// This method is for use by the parser.
|
|
pub fn with_number(n: u32) -> Option<Self> {
|
|
if n < u32::MAX {
|
|
Some(Self(n))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An opaque reference to any of the entities defined in this module that can appear in CLIF IR.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub enum AnyEntity {
|
|
/// The whole function.
|
|
Function,
|
|
/// a basic block.
|
|
Block(Block),
|
|
/// An instruction.
|
|
Inst(Inst),
|
|
/// An SSA value.
|
|
Value(Value),
|
|
/// A stack slot.
|
|
StackSlot(StackSlot),
|
|
/// A Global value.
|
|
GlobalValue(GlobalValue),
|
|
/// A jump table.
|
|
JumpTable(JumpTable),
|
|
/// A constant.
|
|
Constant(Constant),
|
|
/// An external function.
|
|
FuncRef(FuncRef),
|
|
/// A function call signature.
|
|
SigRef(SigRef),
|
|
/// A heap.
|
|
Heap(Heap),
|
|
/// A table.
|
|
Table(Table),
|
|
/// A function's stack limit
|
|
StackLimit,
|
|
}
|
|
|
|
impl fmt::Display for AnyEntity {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
Self::Function => write!(f, "function"),
|
|
Self::Block(r) => r.fmt(f),
|
|
Self::Inst(r) => r.fmt(f),
|
|
Self::Value(r) => r.fmt(f),
|
|
Self::StackSlot(r) => r.fmt(f),
|
|
Self::GlobalValue(r) => r.fmt(f),
|
|
Self::JumpTable(r) => r.fmt(f),
|
|
Self::Constant(r) => r.fmt(f),
|
|
Self::FuncRef(r) => r.fmt(f),
|
|
Self::SigRef(r) => r.fmt(f),
|
|
Self::Heap(r) => r.fmt(f),
|
|
Self::Table(r) => r.fmt(f),
|
|
Self::StackLimit => write!(f, "stack_limit"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for AnyEntity {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
(self as &dyn fmt::Display).fmt(f)
|
|
}
|
|
}
|
|
|
|
impl From<Block> for AnyEntity {
|
|
fn from(r: Block) -> Self {
|
|
Self::Block(r)
|
|
}
|
|
}
|
|
|
|
impl From<Inst> for AnyEntity {
|
|
fn from(r: Inst) -> Self {
|
|
Self::Inst(r)
|
|
}
|
|
}
|
|
|
|
impl From<Value> for AnyEntity {
|
|
fn from(r: Value) -> Self {
|
|
Self::Value(r)
|
|
}
|
|
}
|
|
|
|
impl From<StackSlot> for AnyEntity {
|
|
fn from(r: StackSlot) -> Self {
|
|
Self::StackSlot(r)
|
|
}
|
|
}
|
|
|
|
impl From<GlobalValue> for AnyEntity {
|
|
fn from(r: GlobalValue) -> Self {
|
|
Self::GlobalValue(r)
|
|
}
|
|
}
|
|
|
|
impl From<JumpTable> for AnyEntity {
|
|
fn from(r: JumpTable) -> Self {
|
|
Self::JumpTable(r)
|
|
}
|
|
}
|
|
|
|
impl From<Constant> for AnyEntity {
|
|
fn from(r: Constant) -> Self {
|
|
Self::Constant(r)
|
|
}
|
|
}
|
|
|
|
impl From<FuncRef> for AnyEntity {
|
|
fn from(r: FuncRef) -> Self {
|
|
Self::FuncRef(r)
|
|
}
|
|
}
|
|
|
|
impl From<SigRef> for AnyEntity {
|
|
fn from(r: SigRef) -> Self {
|
|
Self::SigRef(r)
|
|
}
|
|
}
|
|
|
|
impl From<Heap> for AnyEntity {
|
|
fn from(r: Heap) -> Self {
|
|
Self::Heap(r)
|
|
}
|
|
}
|
|
|
|
impl From<Table> for AnyEntity {
|
|
fn from(r: Table) -> Self {
|
|
Self::Table(r)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use alloc::string::ToString;
|
|
use core::u32;
|
|
|
|
#[test]
|
|
fn value_with_number() {
|
|
assert_eq!(Value::with_number(0).unwrap().to_string(), "v0");
|
|
assert_eq!(Value::with_number(1).unwrap().to_string(), "v1");
|
|
|
|
assert_eq!(Value::with_number(u32::MAX / 2), None);
|
|
assert!(Value::with_number(u32::MAX / 2 - 1).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn memory() {
|
|
use crate::packed_option::PackedOption;
|
|
use core::mem;
|
|
// This is the whole point of `PackedOption`.
|
|
assert_eq!(
|
|
mem::size_of::<Value>(),
|
|
mem::size_of::<PackedOption<Value>>()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn constant_with_number() {
|
|
assert_eq!(Constant::with_number(0).unwrap().to_string(), "const0");
|
|
assert_eq!(Constant::with_number(1).unwrap().to_string(), "const1");
|
|
}
|
|
}
|