Summary
Throughout select_with_stack in crates/synth-synthesis/src/instruction_selector.rs, stack operands are retrieved via stack.pop().unwrap_or(Reg::R0) (or similar default registers). On stack underflow, this silently substitutes an arbitrary register instead of reporting an error.
Problem
Stack underflow during instruction selection means one of:
- Malformed WASM: the input module was not properly validated (should have been caught by the frontend)
- Compiler bug: the instruction selector has a bug in stack tracking
In either case, silently defaulting to R0 (or another arbitrary register) produces ARM code that reads from an unrelated register, computes a wrong result, and continues execution with corrupted state. There is no diagnostic, no warning, and no way to detect this happened.
This is especially dangerous in safety-critical embedded contexts where silent wrong-code generation is worse than a crash.
Scope
There are 40+ instances of this pattern across the instruction selector. Examples:
let rn = stack.pop().unwrap_or(Reg::R0);
let rm = stack.pop().unwrap_or(Reg::R1);
Recommendation
Replace unwrap_or(Reg::RX) with proper error handling:
let rn = stack.pop().ok_or_else(|| {
CompileError::internal("stack underflow: expected operand for I32Add")
})?;
Or at minimum, add debug_assert!(!stack.is_empty()) before each pop so that underflow is caught during testing even if the release build continues.
This pairs with #72 — once the instruction selector has an explicit ValueStack type, the pop method can return Result<Reg, CompileError> directly.
Summary
Throughout
select_with_stackincrates/synth-synthesis/src/instruction_selector.rs, stack operands are retrieved viastack.pop().unwrap_or(Reg::R0)(or similar default registers). On stack underflow, this silently substitutes an arbitrary register instead of reporting an error.Problem
Stack underflow during instruction selection means one of:
In either case, silently defaulting to R0 (or another arbitrary register) produces ARM code that reads from an unrelated register, computes a wrong result, and continues execution with corrupted state. There is no diagnostic, no warning, and no way to detect this happened.
This is especially dangerous in safety-critical embedded contexts where silent wrong-code generation is worse than a crash.
Scope
There are 40+ instances of this pattern across the instruction selector. Examples:
Recommendation
Replace
unwrap_or(Reg::RX)with proper error handling:Or at minimum, add
debug_assert!(!stack.is_empty())before each pop so that underflow is caught during testing even if the release build continues.This pairs with #72 — once the instruction selector has an explicit
ValueStacktype, the pop method can returnResult<Reg, CompileError>directly.