Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions crates/cli/src/wast_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,14 @@ impl WastRunner {
);
continue;
};
if !message.starts_with(trap.message()) && !trap.message().starts_with(message) {
// The core test suite's "call stack exhausted" text denotes stack exhaustion,
// not which internal stack reaches its configured limit first. Function-entry
// operand reservation can exhaust a value lane before the call-frame stack.
if !matches!(trap, tinywasm::Trap::CallStackOverflow | tinywasm::Trap::ValueStackOverflow) {
test_group.add_result(
&format!("AssertExhaustion({i})"),
span.linecol_in(wast_raw),
Err(anyhow!("expected trap: {}, got: {}", message, trap.message())),
Err(anyhow!("expected stack exhaustion ({message}), got: {}", trap.message())),
);
continue;
}
Expand Down
35 changes: 18 additions & 17 deletions crates/parser/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,34 +197,35 @@ pub(crate) fn convert_module_code(
}

#[cfg(feature = "validate")]
let (body, data, uses_local_memory, validator_allocs, reader_allocs) = match validator {
let (body, data, uses_local_memory, max_stack, validator_allocs, reader_allocs) = match validator {
Some(validator) => {
let (body, data, uses_local_memory, validator_allocs, reader_allocs) = process_operators_and_validate(
validator,
func,
(local_types, local_addr_map),
metadata,
context,
reader_allocs,
options,
)?;
(body, data, uses_local_memory, Some(validator_allocs), reader_allocs)
let (body, data, uses_local_memory, max_stack, validator_allocs, reader_allocs) =
process_operators_and_validate(
validator,
func,
(local_types, local_addr_map),
metadata,
context,
reader_allocs,
options,
)?;
(body, data, uses_local_memory, max_stack, Some(validator_allocs), reader_allocs)
}
None => {
let (body, data, uses_local_memory, reader_allocs) =
let (body, data, uses_local_memory, max_stack, reader_allocs) =
process_operators(func, (local_types, local_addr_map), metadata, context, reader_allocs, options)?;
(body, data, uses_local_memory, None, reader_allocs)
(body, data, uses_local_memory, max_stack, None, reader_allocs)
}
};
#[cfg(not(feature = "validate"))]
let (body, data, uses_local_memory, validator_allocs, reader_allocs) = {
let (body, data, uses_local_memory, max_stack, validator_allocs, reader_allocs) = {
let _ = validator;
let (body, data, uses_local_memory, reader_allocs) =
let (body, data, uses_local_memory, max_stack, reader_allocs) =
process_operators(func, (local_types, local_addr_map), metadata, context, reader_allocs, options)?;
(body, data, uses_local_memory, None, reader_allocs)
(body, data, uses_local_memory, max_stack, None, reader_allocs)
};
Ok((
FunctionCode { instructions: body, data: data.finish(), locals: local_counts, uses_local_memory },
FunctionCode { instructions: body, data: data.finish(), locals: local_counts, max_stack, uses_local_memory },
validator_allocs,
reader_allocs,
))
Expand Down
2 changes: 2 additions & 0 deletions crates/parser/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) struct FunctionCode {
pub instructions: Vec<Instruction>,
pub data: WasmFunctionData,
pub locals: ValueCounts,
pub max_stack: ValueCounts,
pub uses_local_memory: bool,
}

Expand Down Expand Up @@ -553,6 +554,7 @@ impl<'a> ModuleReader<'a> {
locals: code.locals,
params,
results,
max_stack: code.max_stack,
}))
})
.collect::<Result<_>>()?;
Expand Down
72 changes: 56 additions & 16 deletions crates/parser/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub(crate) struct FunctionBuilder<'a> {
control_stack: Vec<ControlFrame<'a>>,
operand_stack: Vec<ValueLane>,
lane_counts: ValueCounts,
max_lane_counts: ValueCounts,
metadata: &'a ModuleMetadata,
local_types: Vec<ValueLane>,
local_addr_map: Vec<u16>,
Expand Down Expand Up @@ -223,6 +224,7 @@ impl<'a> FunctionBuilder<'a> {
}],
operand_stack: Vec::new(),
lane_counts: ValueCounts::default(),
max_lane_counts: ValueCounts::default(),
uses_local_memory: false,
}
}
Expand Down Expand Up @@ -436,7 +438,7 @@ pub(crate) fn process_operators(
context: FunctionLoweringContext,
allocs: OperatorsReaderAllocations,
options: &ParserOptions,
) -> Result<(Vec<Instruction>, FunctionDataBuilder, bool, OperatorsReaderAllocations)> {
) -> Result<(Vec<Instruction>, FunctionDataBuilder, bool, ValueCounts, OperatorsReaderAllocations)> {
let (local_types, local_addr_map) = locals;
let body_size = body.as_bytes().len();
let reader = body.get_binary_reader_for_operators()?;
Expand All @@ -459,7 +461,7 @@ pub(crate) fn process_operators(

reader.finish()?;
let instructions = builder.emitter.finish(&mut builder.data)?;
Ok((instructions, builder.data, builder.uses_local_memory, reader.into_allocations()))
Ok((instructions, builder.data, builder.uses_local_memory, builder.max_lane_counts, reader.into_allocations()))
}

#[cfg(feature = "validate")]
Expand All @@ -471,7 +473,14 @@ pub(crate) fn process_operators_and_validate(
context: FunctionLoweringContext,
allocs: OperatorsReaderAllocations,
options: &ParserOptions,
) -> Result<(Vec<Instruction>, FunctionDataBuilder, bool, FuncValidatorAllocations, OperatorsReaderAllocations)> {
) -> Result<(
Vec<Instruction>,
FunctionDataBuilder,
bool,
ValueCounts,
FuncValidatorAllocations,
OperatorsReaderAllocations,
)> {
let (local_types, local_addr_map) = locals;
let body_size = body.as_bytes().len();
let reader = body.get_binary_reader_for_operators()?;
Expand All @@ -494,7 +503,14 @@ pub(crate) fn process_operators_and_validate(

reader.finish()?;
let instructions = builder.emitter.finish(&mut builder.data)?;
Ok((instructions, builder.data, builder.uses_local_memory, validator.into_allocations(), reader.into_allocations()))
Ok((
instructions,
builder.data,
builder.uses_local_memory,
builder.max_lane_counts,
validator.into_allocations(),
reader.into_allocations(),
))
}

impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
Expand Down Expand Up @@ -859,11 +875,23 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
wasmparser::Catch::All { label } => (None, label, false),
wasmparser::Catch::AllRef { label } => (None, label, true),
};
if let Some(tag) = tag {
self.metadata.tag_signature(tag)?;
}
let target_idx = self.get_ctx_idx(depth)?;
let target_base = self.control_stack[target_idx].base;
// The runtime truncates to the target base, then injects the exception payload and
// optional reference before executing this landing pad. These pushes do not pass
// through the ordinary logical operand stack, but still need function-entry capacity.
let mut landing_counts = target_base;
if let Some(tag) = tag {
for &lane in &self.metadata.tag_signature(tag)?.params {
Self::increment_lane(&mut landing_counts, lane)?;
}
}
if with_ref {
Self::increment_lane(&mut landing_counts, ValueLane::S32)?;
}
self.max_lane_counts.c32 = self.max_lane_counts.c32.max(landing_counts.c32);
self.max_lane_counts.c64 = self.max_lane_counts.c64.max(landing_counts.c64);
self.max_lane_counts.c128 = self.max_lane_counts.c128.max(landing_counts.c128);
let landing_label = self.emitter.new_label();
self.emitter.bind(landing_label)?;
self.emit_branch_jump_or_return(depth)?;
Expand Down Expand Up @@ -1432,17 +1460,29 @@ impl<'a> FunctionBuilder<'a> {
Ok((size, addr))
}

/// Pushes logical operands while maintaining the lane counts used by `DropKeep`.
/// Increments a physical lane count, rejecting functions too large for the encoded count.
fn increment_lane(counts: &mut ValueCounts, lane: ValueLane) -> Result<()> {
let count = match lane {
ValueLane::S32 => &mut counts.c32,
ValueLane::S64 => &mut counts.c64,
ValueLane::S128 => &mut counts.c128,
};
*count = count
.checked_add(1)
.ok_or_else(|| crate::ParseError::Other("logical operand lane count is too large".into()))?;
Ok(())
}

/// Pushes logical operands while maintaining the lane counts used by `DropKeep` and their
/// maximum, which the runtime reserves when it enters the function.
fn push_sizes(&mut self, sizes: &[ValueLane]) -> Result<()> {
for &size in sizes {
let count = match size {
ValueLane::S32 => &mut self.lane_counts.c32,
ValueLane::S64 => &mut self.lane_counts.c64,
ValueLane::S128 => &mut self.lane_counts.c128,
};
*count = count
.checked_add(1)
.ok_or_else(|| crate::ParseError::Other("logical operand lane count is too large".into()))?;
Self::increment_lane(&mut self.lane_counts, size)?;
match size {
ValueLane::S32 => self.max_lane_counts.c32 = self.max_lane_counts.c32.max(self.lane_counts.c32),
ValueLane::S64 => self.max_lane_counts.c64 = self.max_lane_counts.c64.max(self.lane_counts.c64),
ValueLane::S128 => self.max_lane_counts.c128 = self.max_lane_counts.c128.max(self.lane_counts.c128),
}
self.operand_stack.push(size);
}
Ok(())
Expand Down
14 changes: 6 additions & 8 deletions crates/tinywasm/src/func/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,14 @@ impl Function {
let host = store.state.funcs.host(self.addr()).func.clone();
host.call_values(store, self.module_id, type_addr, params, results)
} else {
let (wasm_params, wasm_locals) = {
let (wasm_params, wasm_locals, wasm_max_stack) = {
let wasm = store.state.funcs.wasm(self.addr());
let wasm_params = wasm.func.params;
let wasm_locals = wasm.func.locals;
(wasm_params, wasm_locals)
(wasm.func.params, wasm.func.locals, wasm.func.max_stack)
};
store.push_wasm_values(params).inspect_err(|_| store.value_stack.truncate_to_base(value_stack_base))?;
let locals_base = store
.value_stack
.enter_locals(&wasm_params, &wasm_locals)
.enter_locals(&wasm_params, &wasm_locals, &wasm_max_stack)
.inspect_err(|_| store.value_stack.truncate_to_base(value_stack_base))?;
let callframe = CallFrame::new(self.addr(), locals_base, wasm_locals);
InterpreterRuntime::exec(store, callframe, call_stack_base).inspect_err(|_| {
Expand Down Expand Up @@ -213,14 +211,14 @@ impl Function {
};
}

let (type_addr, wasm_params, wasm_locals) = {
let (type_addr, wasm_params, wasm_locals, wasm_max_stack) = {
let wasm = store.state.funcs.wasm(self.addr());
(wasm.type_addr, wasm.func.params, wasm.func.locals)
(wasm.type_addr, wasm.func.params, wasm.func.locals, wasm.func.max_stack)
};
store.push_typed_values::<false>(type_addr, params, value_stack_base)?;
let locals_base = store
.value_stack
.enter_locals(&wasm_params, &wasm_locals)
.enter_locals(&wasm_params, &wasm_locals, &wasm_max_stack)
.inspect_err(|_| store.value_stack.truncate_to_base(value_stack_base))?;
let callframe = CallFrame::new(self.addr(), locals_base, wasm_locals);
InterpreterRuntime::exec(store, callframe, call_stack_base).inspect_err(|_| {
Expand Down
12 changes: 6 additions & 6 deletions crates/tinywasm/src/func/resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ impl Function {
return Ok(ExecState::Completed(Some(CallResult::Written)));
}

let (wasm_params, wasm_locals) = {
let (wasm_params, wasm_locals, wasm_max_stack) = {
let wasm = store.state.funcs.wasm(self.addr());
(wasm.func.params, wasm.func.locals)
(wasm.func.params, wasm.func.locals, wasm.func.max_stack)
};

store.call_stack.clear();
store.value_stack.clear();
store.push_wasm_values(params)?;
let locals_base = store.value_stack.enter_locals(&wasm_params, &wasm_locals)?;
let locals_base = store.value_stack.enter_locals(&wasm_params, &wasm_locals, &wasm_max_stack)?;
let callframe = CallFrame::new(self.addr(), locals_base, wasm_locals);

Ok(ExecState::Running { callframe, root_func_addr: self.addr() })
Expand Down Expand Up @@ -224,9 +224,9 @@ impl<P: IntoWasmValues, R: FromWasmValues> FunctionTyped<P, R> {
let execution = ExecutionInner { store, state: ExecState::Completed(None) };
return Ok(FuncExecutionTyped { execution, result: Some(result) });
}
let (type_addr, wasm_params, wasm_locals) = {
let (type_addr, wasm_params, wasm_locals, wasm_max_stack) = {
let wasm = store.state.funcs.wasm(self.func.addr());
(wasm.type_addr, wasm.func.params, wasm.func.locals)
(wasm.type_addr, wasm.func.params, wasm.func.locals, wasm.func.max_stack)
};

store.enter_execution()?;
Expand All @@ -236,7 +236,7 @@ impl<P: IntoWasmValues, R: FromWasmValues> FunctionTyped<P, R> {
store.push_typed_values::<false>(type_addr, params.into_wasm_values(), StackBase::default())?;
let locals_base = store
.value_stack
.enter_locals(&wasm_params, &wasm_locals)
.enter_locals(&wasm_params, &wasm_locals, &wasm_max_stack)
.inspect_err(|_| store.value_stack.clear())?;
let callframe = CallFrame::new(self.func.addr(), locals_base, wasm_locals);
Ok(ExecState::Running { callframe, root_func_addr: self.func.addr() })
Expand Down
18 changes: 8 additions & 10 deletions crates/tinywasm/src/interpreter/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ impl<'store> Executor<'store> {
let Store { state, value_stack, .. } = self.store;
let object = state.gc.get(exception).ok_or(Trap::InvalidReference)?;
for value in object.values.iter().copied() {
value_stack.push_dyn(value)?;
value_stack.push_reserved(value)?;
}
}
if with_ref {
Expand Down Expand Up @@ -706,9 +706,8 @@ impl<'store> Executor<'store> {

fn exec_call_self(&mut self, return_instr_ptr: usize) -> ExecResult<()> {
self.charge_call_fuel(FUEL_COST_CALL_TOTAL);
let Ok(locals_base) = self.store.value_stack.enter_locals(&self.func.params, &self.func.locals) else {
return cold!(Err(Trap::CallStackOverflow.into()));
};
let locals_base =
self.store.value_stack.enter_locals(&self.func.params, &self.func.locals, &self.func.max_stack)?;
let new = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals);
self.store.call_stack.push(core::mem::replace(&mut self.cf, new), return_instr_ptr)?;
Ok(())
Expand All @@ -718,9 +717,8 @@ impl<'store> Executor<'store> {
self.charge_call_fuel(FUEL_COST_CALL_TOTAL);

self.store.value_stack.truncate_keep_counts(self.cf.locals_base, self.func.params);
let Ok(locals_base) = self.store.value_stack.enter_locals(&self.func.params, &self.func.locals) else {
return cold!(Err(Trap::CallStackOverflow.into()));
};
let locals_base =
self.store.value_stack.enter_locals(&self.func.params, &self.func.locals, &self.func.max_stack)?;
self.cf = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals);
Ok(())
}
Expand Down Expand Up @@ -780,14 +778,14 @@ impl<'store> Executor<'store> {
return_instr_ptr: usize,
) -> ExecResult<ExecFlow> {
let wasm_func = self.store.state.funcs.wasm(func_addr);
let (params, locals, owner, next_func) = {
let (params, locals, max_stack, owner, next_func) = {
let next_func = (!Shared::ptr_eq(&self.func, &wasm_func.func)).then(|| wasm_func.func.clone());
(wasm_func.func.params, wasm_func.func.locals, wasm_func.owner, next_func)
(wasm_func.func.params, wasm_func.func.locals, wasm_func.func.max_stack, wasm_func.owner, next_func)
};
if TAIL {
self.store.value_stack.truncate_keep_counts(self.cf.locals_base, params);
}
let locals_base = self.store.value_stack.enter_locals(&params, &locals)?;
let locals_base = self.store.value_stack.enter_locals(&params, &locals, &max_stack)?;
if TAIL {
self.cf = CallFrame::new(func_addr, locals_base, locals);
} else {
Expand Down
Loading
Loading