Skip to content

Commit 7d3d314

Browse files
committed
perf: keep each value stack's height outside its Vec
The height of a value-stack lane is its own field, over a Vec that holds every slot the lane has reached. Slots above the height hold stale values that are written before they are read again. The tail-call handlers can then pass the height between them in a register and write it back without unsafe code. Entering a function writes its locals, and its operand-stack reservation if that is at most 64 slots, so later entries at that height compare once. A larger reservation stays capacity, and a push writes each slot the first time it reaches it (on the tail-call build with push_within_capacity, which makes no call), so a function whose deep branch rarely runs keeps no pages for it. With 64 stores of a module whose untaken branch declares a 30000-deep i64 stack, each store keeps next's footprint (35 KB on macOS, 16-19 KB on glibc) instead of 273 KB and 250 KB. v128.any_true and i8x16.all_true test the vector with integer arithmetic: LLVM scalarized their iterator forms once pushes gained that path. Only builds with debug assertions check indices against the height. CI also runs the tests optimized with them, keeping the release profile's wrapping arithmetic. Entering a function zeroes one or two locals with direct stores, and only the ones in between with memset.
1 parent 3a1723e commit 7d3d314

4 files changed

Lines changed: 204 additions & 48 deletions

File tree

‎.github/workflows/test.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ jobs:
4646
name: "Linux x86 (nightly tail calls)"
4747
target: x86_64-unknown-linux-gnu
4848
args: "--features tinywasm/nightly-tail-calls"
49+
- os: ubuntu-26.04
50+
rust: nightly
51+
name: "Linux x86 (nightly tail calls, release with debug assertions)"
52+
target: x86_64-unknown-linux-gnu
53+
args: "--release --config profile.release.debug-assertions=true --features tinywasm/nightly-tail-calls"
4954
- os: ubuntu-26.04
5055
rust: stable
5156
name: "Linux x86 (stable, no default features)"

‎crates/tinywasm/src/interpreter/simd/instructions.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Value128 {
3030
pub(crate) fn v128_any_true(self) -> bool {
3131
simd_impl! {
3232
wasm => { wasm::v128_any_true(self.to_wasm_v128()) }
33-
generic => { self.0.iter().any(|&b| b != 0) }
33+
generic => { u128::from_ne_bytes(self.0) != 0 }
3434
}
3535
}
3636

@@ -498,7 +498,11 @@ impl Value128 {
498498
"f64x2.extract_lane" => extract_lane_f64(self, lane: u8) -> f64 => f64::from_bits(self.extract_lane_i64(lane) as u64);
499499

500500
// Truth checks, popcount, and averaging
501-
"i8x16.all_true" => i8x16_all_true(self) -> bool => self.0.iter().all(|&b| b != 0);
501+
"i8x16.all_true" => i8x16_all_true(self) -> bool => {
502+
// No byte is zero: the classic zero-byte test on the whole vector.
503+
let v = u128::from_ne_bytes(self.0);
504+
v.wrapping_sub(u128::from_ne_bytes([1; 16])) & !v & u128::from_ne_bytes([0x80; 16]) == 0
505+
};
502506
"i16x8.all_true" => i16x8_all_true(self) -> bool => self.as_i16x8().iter().all(|&x| x != 0);
503507
"i32x4.all_true" => i32x4_all_true(self) -> bool => self.as_i32x4().iter().all(|&x| x != 0);
504508
"i64x2.all_true" => i64x2_all_true(self) -> bool => self.as_i64x2().iter().all(|&x| x != 0);

0 commit comments

Comments
 (0)