Skip to content

Commit 89ff546

Browse files
authored
wasmparser: Create abstraction for local initialization (simplify future optimizations) (#1870)
* put local_inits and inits into new type This will make it simpler to optimize the internals since it won't change the usage sites. * add first_non_default_local optimization * use likely check directly in set_init * add branching hints Benchmarks showed that they have a minor effect. * move hint.rs into validator sub module * apply rustfmt * add docs to LocalInits * is_init -> is_uninit removes negation at call-site * use is_uninit in set_init this way we do not push the same local index multiple times. This is the same behavior as on `main`. * apply rustfmt * refactor LocalInits::set_init * remove hint since there no longer are perf improvements
1 parent bae057e commit 89ff546

File tree

1 file changed

+115
-31
lines changed

1 file changed

+115
-31
lines changed

crates/wasmparser/src/validator/operators.rs

Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use core::ops::{Deref, DerefMut};
3333

3434
pub(crate) struct OperatorValidator {
3535
pub(super) locals: Locals,
36-
pub(super) local_inits: Vec<bool>,
36+
local_inits: LocalInits,
3737

3838
// This is a list of flags for wasm features which are used to gate various
3939
// instructions.
@@ -46,9 +46,6 @@ pub(crate) struct OperatorValidator {
4646
control: Vec<Frame>,
4747
/// The `operands` is the current type stack.
4848
operands: Vec<MaybeType>,
49-
/// When local_inits is modified, the relevant index is recorded here to be
50-
/// undone when control pops
51-
inits: Vec<u32>,
5249

5350
/// Offset of the `end` instruction which emptied the `control` stack, which
5451
/// must be the end of the function.
@@ -61,6 +58,104 @@ pub(crate) struct OperatorValidator {
6158
pub(crate) pop_push_count: (u32, u32),
6259
}
6360

61+
/// Captures the initialization of non-defaultable locals.
62+
struct LocalInits {
63+
/// Records if a local is already initialized.
64+
local_inits: Vec<bool>,
65+
/// When `local_inits` is modified, the relevant `index` is recorded
66+
/// here to be undone when control pops.
67+
inits: Vec<u32>,
68+
/// The index of the first non-defaultable local.
69+
///
70+
/// # Note
71+
///
72+
/// This is an optimization so that we only have to perform expensive
73+
/// look-ups for locals that have a local index equal to or higher than this.
74+
first_non_default_local: u32,
75+
}
76+
77+
impl Default for LocalInits {
78+
fn default() -> Self {
79+
Self {
80+
local_inits: Vec::default(),
81+
inits: Vec::default(),
82+
first_non_default_local: u32::MAX,
83+
}
84+
}
85+
}
86+
87+
impl LocalInits {
88+
/// Defines new function local parameters.
89+
pub fn define_params(&mut self, count: usize) {
90+
let Some(new_len) = self.local_inits.len().checked_add(count) else {
91+
panic!("tried to define too many function locals as parameters: {count}");
92+
};
93+
self.local_inits.resize(new_len, true);
94+
}
95+
96+
/// Defines `count` function locals of type `ty`.
97+
pub fn define_locals(&mut self, count: u32, ty: ValType) {
98+
let Ok(count) = usize::try_from(count) else {
99+
panic!("tried to define too many function locals: {count}");
100+
};
101+
let len = self.local_inits.len();
102+
let Some(new_len) = len.checked_add(count) else {
103+
panic!("tried to define too many function locals: {count}");
104+
};
105+
let is_defaultable = ty.is_defaultable();
106+
if !is_defaultable && self.first_non_default_local == u32::MAX {
107+
self.first_non_default_local = len as u32;
108+
}
109+
self.local_inits.resize(new_len, is_defaultable);
110+
}
111+
112+
/// Returns `true` if the local at `local_index` has already been initialized.
113+
#[inline]
114+
pub fn is_uninit(&self, local_index: u32) -> bool {
115+
if local_index < self.first_non_default_local {
116+
return false;
117+
}
118+
!self.local_inits[local_index as usize]
119+
}
120+
121+
/// Marks the local at `local_index` as initialized.
122+
#[inline]
123+
pub fn set_init(&mut self, local_index: u32) {
124+
if self.is_uninit(local_index) {
125+
self.local_inits[local_index as usize] = true;
126+
self.inits.push(local_index);
127+
}
128+
}
129+
130+
/// Registers a new control frame and returns its `height`.
131+
pub fn push_ctrl(&mut self) -> usize {
132+
self.inits.len()
133+
}
134+
135+
/// Pops a control frame via its `height`.
136+
///
137+
/// This uninitializes all locals that have been initialized within it.
138+
pub fn pop_ctrl(&mut self, height: usize) {
139+
for local_index in self.inits.split_off(height) {
140+
self.local_inits[local_index as usize] = false;
141+
}
142+
}
143+
144+
/// Clears the [`LocalInits`].
145+
///
146+
/// After this operation `self` will be empty and ready for reuse.
147+
pub fn clear(&mut self) {
148+
self.local_inits.clear();
149+
self.inits.clear();
150+
self.first_non_default_local = u32::MAX;
151+
}
152+
153+
/// Returns `true` if `self` is empty.
154+
pub fn is_empty(&self) -> bool {
155+
self.local_inits.is_empty()
156+
}
157+
}
158+
64159
// No science was performed in the creation of this number, feel free to change
65160
// it if you so like.
66161
const MAX_LOCALS_TO_TRACK: usize = 50;
@@ -120,8 +215,7 @@ pub struct OperatorValidatorAllocations {
120215
popped_types_tmp: Vec<MaybeType>,
121216
control: Vec<Frame>,
122217
operands: Vec<MaybeType>,
123-
local_inits: Vec<bool>,
124-
inits: Vec<u32>,
218+
local_inits: LocalInits,
125219
locals_first: Vec<ValType>,
126220
locals_all: Vec<(u32, ValType)>,
127221
}
@@ -226,15 +320,14 @@ impl OperatorValidator {
226320
control,
227321
operands,
228322
local_inits,
229-
inits,
230323
locals_first,
231324
locals_all,
232325
} = allocs;
233326
debug_assert!(popped_types_tmp.is_empty());
234327
debug_assert!(control.is_empty());
235328
debug_assert!(operands.is_empty());
236329
debug_assert!(local_inits.is_empty());
237-
debug_assert!(inits.is_empty());
330+
debug_assert!(local_inits.is_empty());
238331
debug_assert!(locals_first.is_empty());
239332
debug_assert!(locals_all.is_empty());
240333
OperatorValidator {
@@ -244,7 +337,6 @@ impl OperatorValidator {
244337
all: locals_all,
245338
},
246339
local_inits,
247-
inits,
248340
features: *features,
249341
popped_types_tmp,
250342
operands,
@@ -293,8 +385,8 @@ impl OperatorValidator {
293385
if let CompositeInnerType::Func(func_ty) = &sub_ty.composite_type.inner {
294386
for ty in func_ty.params() {
295387
ret.locals.define(1, *ty);
296-
ret.local_inits.push(true);
297388
}
389+
ret.local_inits.define_params(func_ty.params().len());
298390
} else {
299391
bail!(offset, "expected func type at index {ty}, found {sub_ty}")
300392
}
@@ -343,8 +435,7 @@ impl OperatorValidator {
343435
offset,
344436
));
345437
}
346-
self.local_inits
347-
.resize(self.local_inits.len() + count as usize, ty.is_defaultable());
438+
self.local_inits.define_locals(count, ty);
348439
Ok(())
349440
}
350441

@@ -418,7 +509,7 @@ impl OperatorValidator {
418509
format_err!(offset, "operators remaining after end of function")
419510
}
420511

421-
pub fn into_allocations(self) -> OperatorValidatorAllocations {
512+
pub fn into_allocations(mut self) -> OperatorValidatorAllocations {
422513
fn clear<T>(mut tmp: Vec<T>) -> Vec<T> {
423514
tmp.clear();
424515
tmp
@@ -427,8 +518,10 @@ impl OperatorValidator {
427518
popped_types_tmp: clear(self.popped_types_tmp),
428519
control: clear(self.control),
429520
operands: clear(self.operands),
430-
local_inits: clear(self.local_inits),
431-
inits: clear(self.inits),
521+
local_inits: {
522+
self.local_inits.clear();
523+
self.local_inits
524+
},
432525
locals_first: clear(self.locals.first),
433526
locals_all: clear(self.locals.all),
434527
}
@@ -816,7 +909,7 @@ where
816909
// Push a new frame which has a snapshot of the height of the current
817910
// operand stack.
818911
let height = self.operands.len();
819-
let init_height = self.inits.len();
912+
let init_height = self.local_inits.push_ctrl();
820913
self.control.push(Frame {
821914
kind,
822915
block_type: ty,
@@ -848,9 +941,7 @@ where
848941
let init_height = frame.init_height;
849942

850943
// reset_locals in the spec
851-
for init in self.inits.split_off(init_height) {
852-
self.local_inits[init as usize] = false;
853-
}
944+
self.local_inits.pop_ctrl(init_height);
854945

855946
// Pop all the result types, in reverse order, from the operand stack.
856947
// These types will, possibly, be transferred to the next frame.
@@ -2034,7 +2125,7 @@ where
20342125
fn visit_local_get(&mut self, local_index: u32) -> Self::Output {
20352126
let ty = self.local(local_index)?;
20362127
debug_assert_type_indices_are_ids(ty);
2037-
if !self.local_inits[local_index as usize] {
2128+
if self.local_inits.is_uninit(local_index) {
20382129
bail!(self.offset, "uninitialized local: {}", local_index);
20392130
}
20402131
self.push_operand(ty)?;
@@ -2043,20 +2134,13 @@ where
20432134
fn visit_local_set(&mut self, local_index: u32) -> Self::Output {
20442135
let ty = self.local(local_index)?;
20452136
self.pop_operand(Some(ty))?;
2046-
if !self.local_inits[local_index as usize] {
2047-
self.local_inits[local_index as usize] = true;
2048-
self.inits.push(local_index);
2049-
}
2137+
self.local_inits.set_init(local_index);
20502138
Ok(())
20512139
}
20522140
fn visit_local_tee(&mut self, local_index: u32) -> Self::Output {
20532141
let expected_ty = self.local(local_index)?;
20542142
self.pop_operand(Some(expected_ty))?;
2055-
if !self.local_inits[local_index as usize] {
2056-
self.local_inits[local_index as usize] = true;
2057-
self.inits.push(local_index);
2058-
}
2059-
2143+
self.local_inits.set_init(local_index);
20602144
self.push_operand(expected_ty)?;
20612145
Ok(())
20622146
}
@@ -4841,7 +4925,7 @@ where
48414925
}
48424926
// Start a new frame and push `exnref` value.
48434927
let height = self.operands.len();
4844-
let init_height = self.inits.len();
4928+
let init_height = self.local_inits.push_ctrl();
48454929
self.control.push(Frame {
48464930
kind: FrameKind::LegacyCatch,
48474931
block_type: frame.block_type,
@@ -4890,7 +4974,7 @@ where
48904974
bail!(self.offset, "catch_all found outside of a `try` block");
48914975
}
48924976
let height = self.operands.len();
4893-
let init_height = self.inits.len();
4977+
let init_height = self.local_inits.push_ctrl();
48944978
self.control.push(Frame {
48954979
kind: FrameKind::LegacyCatchAll,
48964980
block_type: frame.block_type,

0 commit comments

Comments
 (0)