Skip to content

Commit 899f989

Browse files
improve leb128 handling, move sidetable to using arrays, label error creation as cold paths
1 parent 5f4af0c commit 899f989

9 files changed

Lines changed: 446 additions & 303 deletions

File tree

bench.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
This file documents the coremark bench results to document performance improvements over time.
22
- ac6b813: avg ≈ 500 (introduced benchmarking without criterion)
3-
- current: avg = 529.773949, n = 20 (improved leb128 handling with unsafe)
3+
- 5f4af0c: avg = 529.773949, n = 20 (improved leb128 handling with unsafe)
44
- we try to avoid unsafe code in module/validator/instance directly
5+
- current: avg = 810.367084, n = 20 (use a dense offset table instead of hashmap, better leb128, and cold path hinting)
6+
- rust hashmaps are apparently very bad, this might be because of our memory access pattern, when we interpret the control op codes (corresponding to side table entries) they are kind of accessed sequentially, so even btreemap yields better results
7+
- current design: two-level indirection (one array covering all possible code indices and directing them to a densely packed side table)
8+
- we also try to not use nightly features... making error creation cold path is a really elegant solution in this regard
9+
- repr(C) for the SideTableEntry struct caused mysterious improvements, not sure if it is a fluke
510

611

712
Hardware Overview:

src/byte_iter.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::error::Error::*;
21
use crate::error::*;
32

43
#[derive(Clone, Copy)]
@@ -20,14 +19,14 @@ impl<'a> ByteIter<'a> {
2019
pub fn advance(&mut self, n: usize) { self.idx += n; }
2120
#[inline]
2221
pub fn read_u8(&mut self) -> Result<u8, Error> {
23-
if self.idx >= self.bytes.len() { return Err(Malformed(UNEXPECTED_END)); }
22+
if self.idx >= self.bytes.len() { return Err(Error::malformed(UNEXPECTED_END)); }
2423
let b = self.bytes[self.idx];
2524
self.idx += 1;
2625
Ok(b)
2726
}
2827
#[inline]
2928
pub fn peek_u8(&self) -> Result<u8, Error> {
30-
if self.idx >= self.bytes.len() { return Err(Malformed(UNEXPECTED_END)); }
29+
if self.idx >= self.bytes.len() { return Err(Error::malformed(UNEXPECTED_END)); }
3130
Ok(self.bytes[self.idx])
3231
}
3332
}

src/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ impl Display for Error {
2323

2424
impl std::error::Error for Error {}
2525

26+
impl Error {
27+
#[cold] #[inline(never)]
28+
pub fn malformed(msg: &'static str) -> Self { Error::Malformed(msg) }
29+
#[cold] #[inline(never)]
30+
pub fn validation(msg: &'static str) -> Self { Error::Validation(msg) }
31+
#[cold] #[inline(never)]
32+
pub fn trap(msg: &'static str) -> Self { Error::Trap(msg) }
33+
#[cold] #[inline(never)]
34+
pub fn link(msg: &'static str) -> Self { Error::Link(msg) }
35+
#[cold] #[inline(never)]
36+
pub fn uninstantiable(msg: &'static str) -> Self { Error::Uninstantiable(msg) }
37+
}
38+
2639
// Malformed errors
2740
pub const END_EXPECTED: &str = "END opcode expected";
2841
pub const FUNC_CODE_INCONSISTENT: &str = "function and code section have inconsistent lengths";

src/instance.rs

Lines changed: 89 additions & 121 deletions
Large diffs are not rendered by default.

src/leb128.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::error::Error::*;
21
use crate::error::*;
32

43
#[inline]
@@ -10,7 +9,7 @@ where T: TryFrom<u64> {
109
let mut byte: u8;
1110
unsafe {
1211
loop {
13-
if end >= bytes.len() { return Err(Malformed(UNEXPECTED_END)); }
12+
if end >= bytes.len() { return Err(Error::malformed(UNEXPECTED_END)); }
1413
byte = *bytes.get_unchecked(end);
1514
end += 1;
1615
result |= ((byte & 0x7f) as u64) << shift;
@@ -19,16 +18,16 @@ where T: TryFrom<u64> {
1918
}
2019
}
2120
let consumed = end - *pc;
22-
if consumed > (bits as usize).div_ceil(7) { return Err(Malformed(INT_TOO_LONG)); }
21+
if consumed > (bits as usize).div_ceil(7) { return Err(Error::malformed(INT_TOO_LONG)); }
2322

2423
// Only bits=1 and bits=32 are used
25-
if (bits == 1 && result > 1) || (bits == 32 && result > 0xFFFFFFFF) { return Err(Malformed(INT_TOO_LARGE)); }
24+
if (bits == 1 && result > 1) || (bits == 32 && result > 0xFFFFFFFF) { return Err(Error::malformed(INT_TOO_LARGE)); }
2625

2726
if consumed > 1 {
2827
let used = (consumed - 1) * 7;
2928
if used < bits as usize {
3029
let rem = bits as usize - used;
31-
if rem < 32 && (bytes[end - 1] as u32) >> rem != 0 { return Err(Malformed(INT_TOO_LARGE)); }
30+
if rem < 32 && (bytes[end - 1] as u32) >> rem != 0 { return Err(Error::malformed(INT_TOO_LARGE)); }
3231
}
3332
}
3433
*pc = end;
@@ -44,7 +43,7 @@ where T: TryFrom<i64> {
4443
let mut byte: u8;
4544
unsafe {
4645
loop {
47-
if end >= bytes.len() { return Err(Malformed(UNEXPECTED_END)); }
46+
if end >= bytes.len() { return Err(Error::malformed(UNEXPECTED_END)); }
4847
byte = *bytes.get_unchecked(end);
4948
end += 1;
5049
if shift < 63 {
@@ -63,17 +62,17 @@ where T: TryFrom<i64> {
6362
32 | 33 => {
6463
const MIN_I32: i128 = -(1i128 << 31);
6564
const MAX_I32: i128 = (1i128 << 31) - 1;
66-
if (result as i128) < MIN_I32 || (result as i128) > MAX_I32 { return Err(Malformed(INT_TOO_LARGE)); }
65+
if (result as i128) < MIN_I32 || (result as i128) > MAX_I32 { return Err(Error::malformed(INT_TOO_LARGE)); }
6766
}
6867
64 => {} // Already i64
6968
_ => unreachable!()
7069
}
7170

72-
if consumed > (bits as usize).div_ceil(7) { return Err(Malformed(INT_TOO_LONG)); }
71+
if consumed > (bits as usize).div_ceil(7) { return Err(Error::malformed(INT_TOO_LONG)); }
7372
if consumed >= 1 {
7473
let last = bytes[end - 1];
7574
if ((last != 0 && last != 127) as usize + (consumed - 1) * 7) >= bits as usize {
76-
return Err(Malformed(INT_TOO_LARGE));
75+
return Err(Error::malformed(INT_TOO_LARGE));
7776
}
7877
}
7978
*pc = end;
@@ -92,7 +91,7 @@ where T: TryFrom<u64> {
9291
*pc += 1;
9392
result |= ((byte & 0x7f) as u64) << shift;
9493
if byte & 0x80 == 0 {
95-
return Ok(T::try_from(result).ok().unwrap_unchecked());
94+
return Ok(T::try_from(result).unwrap_unchecked());
9695
}
9796
shift += 7;
9897
}
@@ -109,15 +108,13 @@ where T: TryFrom<i64> {
109108
loop {
110109
byte = *bytes.get_unchecked(*pc);
111110
*pc += 1;
112-
if shift < 63 {
113-
result |= ((byte & 0x7f) as i64) << shift;
114-
}
115-
shift = (shift + 7).min(63);
111+
result |= ((byte & 0x7f) as i64) << shift;
112+
shift += 7;
116113
if byte & 0x80 == 0 { break; }
117114
}
118115
if shift < 64 && (byte & 0x40) != 0 {
119116
result |= (!0i64) << shift;
120117
}
121-
Ok(T::try_from(result).ok().unwrap_unchecked())
118+
Ok(T::try_from(result).unwrap_unchecked())
122119
}
123120
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ mod error;
1616
pub use signature::{Signature, ValType};
1717

1818
// Runtime types
19-
pub use instance::{ExportValue, RuntimeFunction, Imports, Instance, RuntimeSignature, WasmGlobal, WasmTable, WasmValue};
19+
pub use instance::{ExportValue, RuntimeFunction, Imports, Instance, WasmGlobal, WasmTable, WasmValue};
20+
pub use signature::RuntimeSignature;
2021

2122
// Main API types
2223
pub use module::Module;

0 commit comments

Comments
 (0)