Skip to content

Commit 725ae03

Browse files
authored
Improve time complexity of handling large WIT types (#2167)
Update a few places in tooling which iterate over the structure of a WIT type to contain early-returns or similar to ensure that processing these large types doesn't take an exponential amount of time. Closes #2164
1 parent 7133059 commit 725ae03

13 files changed

+245
-77
lines changed

crates/wasmparser/src/validator/component_types.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,9 +1144,6 @@ impl ComponentDefinedType {
11441144
),
11451145
Self::List(_) => lowered_types.push(ValType::I32) && lowered_types.push(ValType::I32),
11461146
Self::FixedSizeList(ty, length) => {
1147-
if *length as usize > lowered_types.max {
1148-
return false;
1149-
}
11501147
(0..*length).all(|_n| ty.push_wasm_types(types, lowered_types))
11511148
}
11521149
Self::Tuple(t) => t

crates/wit-parser/src/abi.rs

Lines changed: 104 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,40 @@ pub enum AbiVariant {
132132
GuestExportAsyncStackful,
133133
}
134134

135+
pub struct FlatTypes<'a> {
136+
types: &'a mut [WasmType],
137+
cur: usize,
138+
overflow: bool,
139+
}
140+
141+
impl<'a> FlatTypes<'a> {
142+
pub fn new(types: &'a mut [WasmType]) -> FlatTypes<'a> {
143+
FlatTypes {
144+
types,
145+
cur: 0,
146+
overflow: false,
147+
}
148+
}
149+
150+
pub fn push(&mut self, ty: WasmType) -> bool {
151+
match self.types.get_mut(self.cur) {
152+
Some(next) => {
153+
*next = ty;
154+
self.cur += 1;
155+
true
156+
}
157+
None => {
158+
self.overflow = true;
159+
false
160+
}
161+
}
162+
}
163+
164+
pub fn to_vec(&self) -> Vec<WasmType> {
165+
self.types[..self.cur].to_vec()
166+
}
167+
}
168+
135169
impl Resolve {
136170
const MAX_FLAT_PARAMS: usize = 16;
137171
const MAX_FLAT_RESULTS: usize = 1;
@@ -150,16 +184,17 @@ impl Resolve {
150184
};
151185
}
152186

153-
let mut params = Vec::new();
154-
let mut indirect_params = false;
155-
for (_, param) in func.params.iter() {
156-
self.push_flat(param, &mut params);
157-
}
158-
159-
if params.len() > Self::MAX_FLAT_PARAMS {
160-
params.truncate(0);
161-
params.push(WasmType::Pointer);
162-
indirect_params = true;
187+
// Note that one extra parameter is allocated in case a return pointer
188+
// is needed down below for imports.
189+
let mut storage = [WasmType::I32; Self::MAX_FLAT_PARAMS + 1];
190+
let mut params = FlatTypes::new(&mut storage);
191+
let ok = self.push_flat_list(func.params.iter().map(|(_, param)| param), &mut params);
192+
assert_eq!(ok, !params.overflow);
193+
194+
let indirect_params = !ok || params.cur > Self::MAX_FLAT_PARAMS;
195+
if indirect_params {
196+
params.types[0] = WasmType::Pointer;
197+
params.cur = 1;
163198
} else {
164199
if matches!(
165200
(&func.kind, variant),
@@ -176,23 +211,23 @@ impl Resolve {
176211
// resource Handles and then use either I32 or Pointer in abi::push_flat().
177212
// But this contextual information isn't available, yet.
178213
// See https://github.com/bytecodealliance/wasm-tools/pull/1438 for more details.
179-
assert!(matches!(params[0], WasmType::I32));
180-
params[0] = WasmType::Pointer;
214+
assert!(matches!(params.types[0], WasmType::I32));
215+
params.types[0] = WasmType::Pointer;
181216
}
182217
}
183218

184219
match variant {
185220
AbiVariant::GuestExportAsync => {
186221
return WasmSignature {
187-
params,
222+
params: params.to_vec(),
188223
indirect_params,
189224
results: vec![WasmType::Pointer],
190225
retptr: false,
191226
};
192227
}
193228
AbiVariant::GuestExportAsyncStackful => {
194229
return WasmSignature {
195-
params,
230+
params: params.to_vec(),
196231
indirect_params,
197232
results: Vec::new(),
198233
retptr: false,
@@ -201,42 +236,50 @@ impl Resolve {
201236
_ => {}
202237
}
203238

204-
let mut results = Vec::new();
239+
let mut storage = [WasmType::I32; Self::MAX_FLAT_RESULTS];
240+
let mut results = FlatTypes::new(&mut storage);
205241
if let Some(ty) = &func.result {
206-
self.push_flat(ty, &mut results)
242+
self.push_flat(ty, &mut results);
207243
}
208244

209-
let mut retptr = false;
245+
let retptr = results.overflow;
210246

211247
// Rust/C don't support multi-value well right now, so if a function
212248
// would have multiple results then instead truncate it. Imports take a
213249
// return pointer to write into and exports return a pointer they wrote
214250
// into.
215-
if results.len() > Self::MAX_FLAT_RESULTS {
216-
retptr = true;
217-
results.truncate(0);
251+
if retptr {
252+
results.cur = 0;
218253
match variant {
219254
AbiVariant::GuestImport => {
220-
params.push(WasmType::Pointer);
255+
assert!(params.push(WasmType::Pointer));
221256
}
222257
AbiVariant::GuestExport => {
223-
results.push(WasmType::Pointer);
258+
assert!(results.push(WasmType::Pointer));
224259
}
225260
_ => unreachable!(),
226261
}
227262
}
228263

229264
WasmSignature {
230-
params,
265+
params: params.to_vec(),
231266
indirect_params,
232-
results,
267+
results: results.to_vec(),
233268
retptr,
234269
}
235270
}
236271

272+
fn push_flat_list<'a>(
273+
&self,
274+
mut list: impl Iterator<Item = &'a Type>,
275+
result: &mut FlatTypes<'_>,
276+
) -> bool {
277+
list.all(|ty| self.push_flat(ty, result))
278+
}
279+
237280
/// Appends the flat wasm types representing `ty` onto the `result`
238281
/// list provided.
239-
pub fn push_flat(&self, ty: &Type, result: &mut Vec<WasmType>) {
282+
pub fn push_flat(&self, ty: &Type, result: &mut FlatTypes<'_>) -> bool {
240283
match ty {
241284
Type::Bool
242285
| Type::S8
@@ -251,76 +294,53 @@ impl Resolve {
251294
Type::U64 | Type::S64 => result.push(WasmType::I64),
252295
Type::F32 => result.push(WasmType::F32),
253296
Type::F64 => result.push(WasmType::F64),
254-
Type::String => {
255-
result.push(WasmType::Pointer);
256-
result.push(WasmType::Length);
257-
}
297+
Type::String => result.push(WasmType::Pointer) && result.push(WasmType::Length),
258298

259299
Type::Id(id) => match &self.types[*id].kind {
260300
TypeDefKind::Type(t) => self.push_flat(t, result),
261301

262302
TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
263-
result.push(WasmType::I32);
303+
result.push(WasmType::I32)
264304
}
265305

266306
TypeDefKind::Resource => todo!(),
267307

268308
TypeDefKind::Record(r) => {
269-
for field in r.fields.iter() {
270-
self.push_flat(&field.ty, result);
271-
}
309+
self.push_flat_list(r.fields.iter().map(|f| &f.ty), result)
272310
}
273311

274-
TypeDefKind::Tuple(t) => {
275-
for ty in t.types.iter() {
276-
self.push_flat(ty, result);
277-
}
278-
}
312+
TypeDefKind::Tuple(t) => self.push_flat_list(t.types.iter(), result),
279313

280314
TypeDefKind::Flags(r) => {
281-
for _ in 0..r.repr().count() {
282-
result.push(WasmType::I32);
283-
}
315+
self.push_flat_list((0..r.repr().count()).map(|_| &Type::U32), result)
284316
}
285317

286318
TypeDefKind::List(_) => {
287-
result.push(WasmType::Pointer);
288-
result.push(WasmType::Length);
319+
result.push(WasmType::Pointer) && result.push(WasmType::Length)
289320
}
290321

291322
TypeDefKind::FixedSizeList(ty, size) => {
292-
for _ in 0..usize::try_from(*size)
293-
.unwrap_or(Self::MAX_FLAT_PARAMS)
294-
.min(Self::MAX_FLAT_PARAMS)
295-
{
296-
self.push_flat(ty, result);
297-
}
323+
self.push_flat_list((0..*size).map(|_| ty), result)
298324
}
299325

300326
TypeDefKind::Variant(v) => {
301-
result.push(v.tag().into());
302-
self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result);
327+
result.push(v.tag().into())
328+
&& self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result)
303329
}
304330

305331
TypeDefKind::Enum(e) => result.push(e.tag().into()),
306332

307333
TypeDefKind::Option(t) => {
308-
result.push(WasmType::I32);
309-
self.push_flat_variants([None, Some(t)], result);
334+
result.push(WasmType::I32) && self.push_flat_variants([None, Some(t)], result)
310335
}
311336

312337
TypeDefKind::Result(r) => {
313-
result.push(WasmType::I32);
314-
self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result);
338+
result.push(WasmType::I32)
339+
&& self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result)
315340
}
316341

317-
TypeDefKind::Future(_) => {
318-
result.push(WasmType::I32);
319-
}
320-
321-
TypeDefKind::Stream(_) => {
322-
result.push(WasmType::I32);
323-
}
342+
TypeDefKind::Future(_) => result.push(WasmType::I32),
343+
TypeDefKind::Stream(_) => result.push(WasmType::I32),
324344

325345
TypeDefKind::Unknown => unreachable!(),
326346
},
@@ -330,10 +350,11 @@ impl Resolve {
330350
fn push_flat_variants<'a>(
331351
&self,
332352
tys: impl IntoIterator<Item = Option<&'a Type>>,
333-
result: &mut Vec<WasmType>,
334-
) {
335-
let mut temp = Vec::new();
336-
let start = result.len();
353+
result: &mut FlatTypes<'_>,
354+
) -> bool {
355+
let mut temp = result.types[result.cur..].to_vec();
356+
let mut temp = FlatTypes::new(&mut temp);
357+
let start = result.cur;
337358

338359
// Push each case's type onto a temporary vector, and then
339360
// merge that vector into our final list starting at
@@ -343,15 +364,27 @@ impl Resolve {
343364
// `i32` might be the `f32` bitcasted.
344365
for ty in tys {
345366
if let Some(ty) = ty {
346-
self.push_flat(ty, &mut temp);
367+
if !self.push_flat(ty, &mut temp) {
368+
result.overflow = true;
369+
return false;
370+
}
347371

348-
for (i, ty) in temp.drain(..).enumerate() {
349-
match result.get_mut(start + i) {
350-
Some(prev) => *prev = join(*prev, ty),
351-
None => result.push(ty),
372+
for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
373+
let i = i + start;
374+
if i < result.cur {
375+
result.types[i] = join(result.types[i], *ty);
376+
} else if result.cur == result.types.len() {
377+
result.overflow = true;
378+
return false;
379+
} else {
380+
result.types[i] = *ty;
381+
result.cur += 1;
352382
}
353383
}
384+
temp.cur = 0;
354385
}
355386
}
387+
388+
true
356389
}
357390
}

crates/wit-parser/src/ast/resolve.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,10 @@ impl<'a> Resolver<'a> {
13391339
if !matches!(&ty.stability, Stability::Unknown) {
13401340
Some(&ty.stability)
13411341
} else {
1342-
find_in_kind(types, &ty.kind)
1342+
// Note that this type isn't searched recursively since the
1343+
// creation of `id` should already have searched its
1344+
// recursive edges, so there's no need to search again.
1345+
None
13431346
}
13441347
} else {
13451348
None

src/bin/wasm-tools/component.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use wasm_encoder::reencode::{Error, Reencode, ReencodeComponent, RoundtripReenco
1010
use wasm_encoder::ModuleType;
1111
use wasm_tools::Output;
1212
use wasmparser::types::{CoreTypeId, EntityType, Types};
13-
use wasmparser::{Payload, ValidPayload};
13+
use wasmparser::{Payload, ValidPayload, WasmFeatures};
1414
use wat::Detect;
1515
use wit_component::{
1616
embed_component_metadata, metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding,
@@ -831,7 +831,7 @@ impl WitOpts {
831831
let decoded_package = decoded.package();
832832
let bytes = wit_component::encode(decoded.resolve(), decoded_package)?;
833833
if !self.skip_validation {
834-
wasmparser::Validator::new().validate_all(&bytes)?;
834+
wasmparser::Validator::new_with_features(WasmFeatures::all()).validate_all(&bytes)?;
835835
}
836836
self.output.output_wasm(&self.general, &bytes, self.wat)?;
837837
Ok(())

tests/cli/wit-deep-list.wit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN[parse]: component wit % -t | validate -f cm-fixed-size-list
2+
// RUN[abi]: component embed --dummy % | component new | validate -f cm-fixed-size-list
3+
4+
package a:b;
5+
6+
world lists {
7+
type t = list<t2, 16>;
8+
type t2 = list<t3, 16>;
9+
type t3 = list<t4, 16>;
10+
type t4 = list<t5, 16>;
11+
type t5 = list<t6, 16>;
12+
type t6 = list<t7, 16>;
13+
type t7 = list<t8, 16>;
14+
type t8 = list<u8, 16>;
15+
16+
import x: func(t: t);
17+
}
18+

0 commit comments

Comments
 (0)