Skip to content

Commit

Permalink
Support no_std in the wasmparser crate (#1493)
Browse files Browse the repository at this point in the history
* Add `#![no_std]` support to the `wasmparser` crate

This commit adds a `std` feature to the `wasmparser` crate, enables it
by default, and then enables building the crate when the feature is
disabled. This works by switching imports of `wasmparser` from `std` to
either the `core` or `alloc` crates which are available in the `no_std`
context. The main change made in this PR is that maps used by
`wasmparser`, e.g. `HashMap` and `IndexMap`, are now abstracted with
type aliases in `wasmparser` which unconditionally customize a hash
algorithm parameter. When the `std` feature is enabled this parameter is
implemented using the standard library's default hashing algorithm. When
`std` is disabled then this uses the `ahash` crate internally.

This commit additionally updates CI to both check that wasmparser builds
when the `std` feature is disabled and that it builds on a target
without `std`.

* Fix rebase conflict
  • Loading branch information
alexcrichton committed Apr 25, 2024
1 parent 7003a7f commit d1eeff9
Show file tree
Hide file tree
Showing 39 changed files with 309 additions and 130 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Expand Up @@ -152,6 +152,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup target add x86_64-unknown-none
- run: cargo check --benches -p wasm-smith
- run: cargo check --no-default-features
- run: cargo check --no-default-features --features print
Expand All @@ -176,6 +177,9 @@ jobs:
- run: cargo check --no-default-features -p wit-parser --features serde
- run: cargo check --no-default-features -p wit-parser --features decoding
- run: cargo check --no-default-features -p wit-parser --features serde,decoding,wat
- run: cargo check --no-default-features -p wasmparser
- run: cargo check --no-default-features -p wasmparser --target x86_64-unknown-none
- run: cargo check --no-default-features -p wasmparser --features std
- run: |
if cargo tree -p wasm-smith --no-default-features -e no-dev | grep wasmparser; then
echo wasm-smith without default features should not depend on wasmparser
Expand Down
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.

6 changes: 4 additions & 2 deletions Cargo.toml
Expand Up @@ -42,7 +42,7 @@ clap = { version = "4.0.0", features = ["derive"] }
clap_complete = "4.4.7"
criterion = "0.3.3"
env_logger = "0.11"
indexmap = "2.0.0"
indexmap = { version = "2.0.0", default-features = false }
leb128 = "0.2.4"
libfuzzer-sys = "0.4.0"
log = "0.4.17"
Expand All @@ -55,10 +55,12 @@ serde_json = { version = "1" }
wasmtime = { version = "20.0.0", default-features = false, features = ['cranelift', 'component-model', 'runtime', 'gc'] }
url = "2.0.0"
pretty_assertions = "1.3.0"
semver = "1.0.0"
semver = { version = "1.0.0", default-features = false }
smallvec = "1.11.1"
libtest-mimic = "0.7.0"
bitflags = "2.5.0"
hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] }
ahash = { version = "0.8.11", default-features = false }

wasm-compose = { version = "0.205.0", path = "crates/wasm-compose" }
wasm-encoder = { version = "0.205.0", path = "crates/wasm-encoder" }
Expand Down
4 changes: 2 additions & 2 deletions crates/wasm-compose/src/encoding.rs
Expand Up @@ -730,7 +730,7 @@ impl<'a> TypeEncoder<'a> {
index
}

fn flags(encodable: &mut Encodable, names: &IndexSet<KebabString>) -> u32 {
fn flags(encodable: &mut Encodable, names: &wasmparser::map::IndexSet<KebabString>) -> u32 {
let index = encodable.type_count();
encodable
.ty()
Expand All @@ -739,7 +739,7 @@ impl<'a> TypeEncoder<'a> {
index
}

fn enum_type(encodable: &mut Encodable, cases: &IndexSet<KebabString>) -> u32 {
fn enum_type(encodable: &mut Encodable, cases: &wasmparser::map::IndexSet<KebabString>) -> u32 {
let index = encodable.type_count();
encodable
.ty()
Expand Down
6 changes: 6 additions & 0 deletions crates/wasmparser/Cargo.toml
Expand Up @@ -19,6 +19,8 @@ workspace = true
bitflags = "2.4.1"
indexmap = { workspace = true }
semver = { workspace = true }
hashbrown = { workspace = true }
ahash = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
Expand All @@ -34,3 +36,7 @@ log.workspace = true
[[bench]]
name = "benchmark"
harness = false

[features]
default = ['std']
std = ['indexmap/std']
1 change: 0 additions & 1 deletion crates/wasmparser/LICENSE

This file was deleted.

15 changes: 8 additions & 7 deletions crates/wasmparser/src/binary_reader.rs
Expand Up @@ -13,12 +13,12 @@
* limitations under the License.
*/

use crate::prelude::*;
use crate::{limits::*, *};
use std::error::Error;
use std::fmt;
use std::marker;
use std::ops::Range;
use std::str;
use core::fmt;
use core::marker;
use core::ops::Range;
use core::str;

pub(crate) const WASM_MAGIC_NUMBER: &[u8; 4] = b"\0asm";

Expand All @@ -39,9 +39,10 @@ pub(crate) struct BinaryReaderErrorInner {
}

/// The result for `BinaryReader` operations.
pub type Result<T, E = BinaryReaderError> = std::result::Result<T, E>;
pub type Result<T, E = BinaryReaderError> = core::result::Result<T, E>;

impl Error for BinaryReaderError {}
#[cfg(feature = "std")]
impl std::error::Error for BinaryReaderError {}

impl fmt::Display for BinaryReaderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expand Down
24 changes: 24 additions & 0 deletions crates/wasmparser/src/lib.rs
Expand Up @@ -27,6 +27,28 @@
//! the examples documented for [`Parser::parse`] or [`Parser::parse_all`].

#![deny(missing_docs)]
#![no_std]

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

/// A small "prelude" to use throughout this crate.
///
/// This crate is tagged with `#![no_std]` meaning that we get libcore's prelude
/// by default. This crate also uses `alloc`, however, and common types there
/// like `String`. This custom prelude helps bring those types into scope to
/// avoid having to import each of them manually.
mod prelude {
pub use alloc::borrow::ToOwned;
pub use alloc::boxed::Box;
pub use alloc::format;
pub use alloc::string::{String, ToString};
pub use alloc::vec;
pub use alloc::vec::Vec;

pub use crate::map::{HashMap, HashSet, IndexMap, IndexSet};
}

/// A helper macro to conveniently iterate over all opcodes recognized by this
/// crate. This can be used to work with either the [`Operator`] enumeration or
Expand Down Expand Up @@ -770,3 +792,5 @@ mod parser;
mod readers;
mod resources;
mod validator;

pub mod map;
126 changes: 126 additions & 0 deletions crates/wasmparser/src/map.rs
@@ -0,0 +1,126 @@
//! Type aliases for maps used by `wasmparser`
//!
//! This module contains type aliases used for [`HashMap`], [`HashSet`],
//! [`IndexMap`], and [`IndexSet`]. Note that these differ from upstream types
//! in the `indexmap` crate and the standard library due to customization of the
//! hash algorithm type parameter.

use core::hash::{BuildHasher, Hasher};

/// Wasmparser-specific type alias for an ordered map.
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, RandomState>;

/// Wasmparser-specific type alias for an ordered set.
pub type IndexSet<K> = indexmap::IndexSet<K, RandomState>;

/// Wasmparser-specific type alias for hash map.
pub type HashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;

/// Wasmparser-specific type alias for hash set.
pub type HashSet<K> = hashbrown::HashSet<K, RandomState>;

/// Wasmparser's hashing state stored per-map.
///
/// This is DoS-resistant when the `std` feature is activated and still somewhat
/// resistant when it's not active but not as secure.
#[derive(Clone, Debug)]
pub struct RandomState(RandomStateImpl);

impl Default for RandomState {
#[inline]
fn default() -> RandomState {
RandomState(RandomStateImpl::default())
}
}

impl BuildHasher for RandomState {
type Hasher = RandomStateHasher;

#[inline]
fn build_hasher(&self) -> RandomStateHasher {
RandomStateHasher(self.0.build_hasher())
}
}

/// Wasmparser's hasher type used with [`RandomState`].
pub struct RandomStateHasher(<RandomStateImpl as BuildHasher>::Hasher);

impl Hasher for RandomStateHasher {
#[inline]
fn finish(&self) -> u64 {
self.0.finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.0.write_u8(i)
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.0.write_u16(i)
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.0.write_u32(i)
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i)
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i)
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i)
}
#[inline]
fn write_i8(&mut self, i: i8) {
self.0.write_i8(i)
}
#[inline]
fn write_i16(&mut self, i: i16) {
self.0.write_i16(i)
}
#[inline]
fn write_i32(&mut self, i: i32) {
self.0.write_i32(i)
}
#[inline]
fn write_i64(&mut self, i: i64) {
self.0.write_i64(i)
}
#[inline]
fn write_i128(&mut self, i: i128) {
self.0.write_i128(i)
}
#[inline]
fn write_isize(&mut self, i: isize) {
self.0.write_isize(i)
}
}

// When the `std` feature is active reuse the standard library's implementation
// of hash state and hasher.
#[cfg(feature = "std")]
use std::collections::hash_map::RandomState as RandomStateImpl;

// When the `std` feature is NOT active then rely on `ahash::RandomState`. That
// relies on ASLR by default for randomness.
#[derive(Default, Clone, Debug)]
#[cfg(not(feature = "std"))]
struct RandomStateImpl;

#[cfg(not(feature = "std"))]
impl BuildHasher for RandomStateImpl {
type Hasher = ahash::AHasher;

#[inline]
fn build_hasher(&self) -> ahash::AHasher {
ahash::RandomState::new().build_hasher()
}
}
9 changes: 5 additions & 4 deletions crates/wasmparser/src/parser.rs
@@ -1,4 +1,5 @@
use crate::binary_reader::WASM_MAGIC_NUMBER;
use crate::prelude::*;
use crate::CoreTypeSectionReader;
use crate::{
limits::MAX_WASM_MODULE_SIZE, BinaryReader, BinaryReaderError, ComponentCanonicalSectionReader,
Expand All @@ -8,9 +9,9 @@ use crate::{
GlobalSectionReader, ImportSectionReader, InstanceSectionReader, MemorySectionReader, Result,
SectionLimited, TableSectionReader, TagSectionReader, TypeSectionReader,
};
use std::fmt;
use std::iter;
use std::ops::Range;
use core::fmt;
use core::iter;
use core::ops::Range;

pub(crate) const WASM_MODULE_VERSION: u16 = 0x1;

Expand Down Expand Up @@ -952,7 +953,7 @@ impl Parser {
///
/// ```
/// use wasmparser::{Result, Parser, Chunk, Payload::*};
/// use std::ops::Range;
/// use core::ops::Range;
///
/// fn objdump_headers(mut wasm: &[u8]) -> Result<()> {
/// let mut parser = Parser::new(0);
Expand Down
6 changes: 3 additions & 3 deletions crates/wasmparser/src/readers.rs
Expand Up @@ -14,9 +14,9 @@
*/

use crate::{BinaryReader, BinaryReaderError, Result};
use std::fmt;
use std::marker;
use std::ops::Range;
use ::core::fmt;
use ::core::marker;
use ::core::ops::Range;

mod component;
mod core;
Expand Down
1 change: 1 addition & 0 deletions crates/wasmparser/src/readers/component/canonicals.rs
@@ -1,4 +1,5 @@
use crate::limits::MAX_WASM_CANONICAL_OPTIONS;
use crate::prelude::*;
use crate::{BinaryReader, FromReader, Result, SectionLimited};

/// Represents options for component functions.
Expand Down
1 change: 1 addition & 0 deletions crates/wasmparser/src/readers/component/instances.rs
@@ -1,4 +1,5 @@
use crate::limits::{MAX_WASM_INSTANTIATION_ARGS, MAX_WASM_INSTANTIATION_EXPORTS};
use crate::prelude::*;
use crate::{
BinaryReader, ComponentExport, ComponentExternalKind, Export, FromReader, Result,
SectionLimited,
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmparser/src/readers/component/names.rs
@@ -1,5 +1,5 @@
use crate::{BinaryReader, BinaryReaderError, NameMap, Result, Subsection, Subsections};
use std::ops::Range;
use core::ops::Range;

/// Type used to iterate and parse the contents of the `component-name` custom
/// section in compnents, similar to the `name` section of core modules.
Expand Down
1 change: 1 addition & 0 deletions crates/wasmparser/src/readers/component/start.rs
@@ -1,4 +1,5 @@
use crate::limits::{MAX_WASM_FUNCTION_RETURNS, MAX_WASM_START_ARGS};
use crate::prelude::*;
use crate::{BinaryReader, FromReader, Result};

/// Represents the start function in a WebAssembly component.
Expand Down
5 changes: 3 additions & 2 deletions crates/wasmparser/src/readers/component/types.rs
@@ -1,9 +1,10 @@
use crate::limits::*;
use crate::prelude::*;
use crate::{
BinaryReader, ComponentAlias, ComponentExportName, ComponentImport, ComponentTypeRef,
FromReader, Import, Result, SectionLimited, SubType, TypeRef, ValType,
};
use std::fmt;
use core::fmt;

/// Represents the kind of an outer core alias in a WebAssembly component.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -420,7 +421,7 @@ impl ComponentFuncResult<'_> {
}

match self {
Self::Unnamed(ty) => Either::Left(std::iter::once(ty).map(|ty| (None, ty))),
Self::Unnamed(ty) => Either::Left(core::iter::once(ty).map(|ty| (None, ty))),
Self::Named(vec) => Either::Right(vec.iter().map(|(n, ty)| (Some(*n), ty))),
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmparser/src/readers/core/code.rs
Expand Up @@ -14,7 +14,7 @@
*/

use crate::{BinaryReader, FromReader, OperatorsReader, Result, SectionLimited, ValType};
use std::ops::Range;
use core::ops::Range;

/// A reader for the code section of a WebAssembly module.
pub type CodeSectionReader<'a> = SectionLimited<'a, FunctionBody<'a>>;
Expand Down
1 change: 1 addition & 0 deletions crates/wasmparser/src/readers/core/coredumps.rs
@@ -1,3 +1,4 @@
use crate::prelude::*;
use crate::{BinaryReader, FromReader, Result};

/// The data portion of a custom section representing a core dump. Per the
Expand Down

0 comments on commit d1eeff9

Please sign in to comment.