Skip to content

Commit 2004def

Browse files
committed
Add WebAssembly SIMD Support
Chrome 91 just released yesterday with stable support for WASM SIMD. Firefox will shortly follow as well. Rust also intends to stabilize the intrinsics soon. So I decided to go ahead and already port SIMD support for a couple of crates including now hashbrown.
1 parent 805b5e2 commit 2004def

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
maybe_uninit_array_assume_init
2525
)
2626
)]
27+
#![cfg_attr(
28+
all(
29+
feature = "nightly",
30+
target_feature = "simd128",
31+
any(target_arch = "wasm32", target_arch = "wasm64"),
32+
),
33+
feature(wasm_simd)
34+
)]
2735
#![allow(
2836
clippy::doc_markdown,
2937
clippy::module_name_repetitions,

src/raw/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ cfg_if! {
2828
))] {
2929
mod sse2;
3030
use sse2 as imp;
31+
} else if #[cfg(all(
32+
target_feature = "simd128",
33+
any(target_arch = "wasm32", target_arch = "wasm64"),
34+
not(miri)
35+
))] {
36+
mod wasm;
37+
use wasm as imp;
3138
} else {
3239
#[path = "generic.rs"]
3340
mod generic;

src/raw/wasm.rs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use super::{bitmask::BitMask, EMPTY};
2+
use core::{mem, ptr};
3+
4+
#[cfg(target_arch = "wasm32")]
5+
use core::arch::wasm32 as wasm;
6+
#[cfg(target_arch = "wasm64")]
7+
use core::arch::wasm64 as wasm;
8+
9+
pub type BitMaskWord = u16;
10+
pub const BITMASK_STRIDE: usize = 1;
11+
pub const BITMASK_MASK: BitMaskWord = 0xffff;
12+
13+
/// Abstraction over a group of control bytes which can be scanned in
14+
/// parallel.
15+
///
16+
/// This implementation uses a 128-bit SIMD value.
17+
/// It uses repr(transparent) so the v128 can be passed around directly.
18+
#[derive(Copy, Clone)]
19+
#[repr(transparent)]
20+
pub struct Group(wasm::v128);
21+
22+
impl Group {
23+
/// Number of bytes in the group.
24+
pub const WIDTH: usize = mem::size_of::<Self>();
25+
26+
/// Returns a full group of empty bytes, suitable for use as the initial
27+
/// value for an empty hash table.
28+
///
29+
/// This is guaranteed to be aligned to the group size.
30+
pub const fn static_empty() -> &'static [u8; Group::WIDTH] {
31+
#[repr(C)]
32+
struct AlignedBytes {
33+
_align: [Group; 0],
34+
bytes: [u8; Group::WIDTH],
35+
}
36+
const ALIGNED_BYTES: AlignedBytes = AlignedBytes {
37+
_align: [],
38+
bytes: [EMPTY; Group::WIDTH],
39+
};
40+
&ALIGNED_BYTES.bytes
41+
}
42+
43+
/// Loads a group of bytes starting at the given address.
44+
#[inline]
45+
pub unsafe fn load(ptr: *const u8) -> Self {
46+
Group(ptr::read_unaligned(ptr.cast()))
47+
}
48+
49+
/// Loads a group of bytes starting at the given address, which must be
50+
/// aligned to `mem::align_of::<Group>()`.
51+
#[inline]
52+
pub unsafe fn load_aligned(ptr: *const u8) -> Self {
53+
// FIXME: use align_offset once it stabilizes
54+
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
55+
Group(ptr::read(ptr.cast()))
56+
}
57+
58+
/// Stores the group of bytes to the given address, which must be
59+
/// aligned to `mem::align_of::<Group>()`.
60+
#[inline]
61+
pub unsafe fn store_aligned(self, ptr: *mut u8) {
62+
// FIXME: use align_offset once it stabilizes
63+
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
64+
ptr::write(ptr.cast(), self.0);
65+
}
66+
67+
/// Returns a `BitMask` indicating all bytes in the group which have
68+
/// the given value.
69+
#[inline]
70+
pub fn match_byte(self, byte: u8) -> BitMask {
71+
unsafe {
72+
let cmp = wasm::i8x16_eq(self.0, wasm::u8x16_splat(byte));
73+
BitMask(wasm::i8x16_bitmask(cmp) as u16)
74+
}
75+
}
76+
77+
/// Returns a `BitMask` indicating all bytes in the group which are
78+
/// `EMPTY`.
79+
#[inline]
80+
pub fn match_empty(self) -> BitMask {
81+
self.match_byte(EMPTY)
82+
}
83+
84+
/// Returns a `BitMask` indicating all bytes in the group which are
85+
/// `EMPTY` or `DELETED`.
86+
#[inline]
87+
pub fn match_empty_or_deleted(self) -> BitMask {
88+
unsafe {
89+
// A byte is EMPTY or DELETED iff the high bit is set
90+
BitMask(wasm::i8x16_bitmask(self.0) as u16)
91+
}
92+
}
93+
94+
/// Returns a `BitMask` indicating all bytes in the group which are full.
95+
#[inline]
96+
pub fn match_full(&self) -> BitMask {
97+
self.match_empty_or_deleted().invert()
98+
}
99+
100+
/// Performs the following transformation on all bytes in the group:
101+
/// - `EMPTY => EMPTY`
102+
/// - `DELETED => EMPTY`
103+
/// - `FULL => DELETED`
104+
#[inline]
105+
pub fn convert_special_to_empty_and_full_to_deleted(self) -> Self {
106+
// Map high_bit = 1 (EMPTY or DELETED) to 1111_1111
107+
// and high_bit = 0 (FULL) to 1000_0000
108+
//
109+
// Here's this logic expanded to concrete values:
110+
// let special = 0 > byte = 1111_1111 (true) or 0000_0000 (false)
111+
// 1111_1111 | 1000_0000 = 1111_1111
112+
// 0000_0000 | 1000_0000 = 1000_0000
113+
unsafe {
114+
let zero = wasm::u8x16_splat(0);
115+
let special = wasm::i8x16_gt(zero, self.0);
116+
Group(wasm::v128_or(special, wasm::u8x16_splat(0x80)))
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)