wasm/execution/interpreter_loop/
control.rs1use core::ops::ControlFlow;
2
3use crate::{
4 assert_validated::UnwrapValidatedExt,
5 core::{
6 error::DecodingError,
7 indices::{read_label_idx_unchecked, FuncIdx, TableIdx, TypeIdx},
8 reader::types::{opcode, BlockType},
9 utils::ToUsizeExt,
10 },
11 execution::interpreter_loop::{
12 define_instruction_fn, do_sidetable_control_transfer, Args, InterpreterLoopOutcome,
13 },
14 instances::FuncInst,
15 unreachable_validated,
16 value::Ref,
17 TrapError,
18};
19
20define_instruction_fn! {nop, fuel_check = flat(opcode::NOP), |_args| Ok(
21 ControlFlow::Continue(())
22)}
23
24define_instruction_fn! {
25 unreachable,
26 fuel_check = flat(opcode::UNREACHABLE),
27 |Args { .. }| { Err(TrapError::ReachedUnreachable.into()) }
28}
29
30define_instruction_fn! {
31 block,
32 fuel_check = flat(opcode::BLOCK),
33 |Args { wasm, .. }| {
34 let _ = unsafe { BlockType::read_unchecked(wasm) };
37 Ok(ControlFlow::Continue(()))
38 }
39}
40
41define_instruction_fn! {
42 end,
43 fuel_check = flat(opcode::END),
44 |Args {
45 store_inner,
46 modules,
47 resumable,
48 wasm,
49 current_module,
50 current_function_end_marker,
51 current_sidetable,
52 ..
53 }| {
54 if wasm.pc != *current_function_end_marker {
57 return Ok(ControlFlow::Continue(()));
58 }
59
60 let Some((maybe_return_func_addr, maybe_return_address, maybe_return_stp)) =
61 resumable.stack.pop_call_frame()
62 else {
63 return Ok(ControlFlow::Break(
65 InterpreterLoopOutcome::ExecutionReturned,
66 ));
67 };
68 trace!("end of function reached, returning to previous call frame");
72 resumable.current_func_addr = maybe_return_func_addr;
73
74 let current_function = unsafe { store_inner.functions.get(resumable.current_func_addr) };
81 let FuncInst::WasmFunc(current_wasm_func_inst) = current_function else {
82 unreachable!(
83 "function addresses on the stack always correspond to native wasm functions"
84 )
85 };
86 *current_module = current_wasm_func_inst.module_addr;
87
88 let module = unsafe { modules.get(*current_module) };
93
94 wasm.full_wasm_binary = module.wasm_bytecode;
95 wasm.pc = maybe_return_address;
96 resumable.stp = maybe_return_stp;
97
98 *current_sidetable = &module.sidetable;
99
100 *current_function_end_marker =
101 current_wasm_func_inst.code_expr.from() + current_wasm_func_inst.code_expr.len();
102
103 trace!("Instruction: END");
104
105 Ok(ControlFlow::Continue(()))
106 }
107}
108
109define_instruction_fn! {
110 r#loop,
111 fuel_check = flat(opcode::LOOP),
112 |Args { wasm, .. }| {
113 let _ = unsafe { BlockType::read_unchecked(wasm) };
116 Ok(ControlFlow::Continue(()))
117 }
118}
119
120define_instruction_fn! {
121 r#if,
122 fuel_check = flat(opcode::IF),
123 |Args {
124 resumable,
125 wasm,
126 current_sidetable,
127 ..
128 }| {
129 let _block_type = unsafe { BlockType::read_unchecked(wasm) };
132
133 let test_val: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
134
135 if test_val != 0 {
136 resumable.stp += 1;
137 } else {
138 do_sidetable_control_transfer(
139 wasm,
140 &mut resumable.stack,
141 &mut resumable.stp,
142 current_sidetable,
143 )?;
144 }
145 trace!("Instruction: IF");
146
147 Ok(ControlFlow::Continue(()))
148 }
149}
150
151define_instruction_fn! {
152 r#else,
153 fuel_check = flat(opcode::ELSE),
154 |Args {
155 wasm,
156 resumable,
157 current_sidetable,
158 ..
159 }| {
160 do_sidetable_control_transfer(
161 wasm,
162 &mut resumable.stack,
163 &mut resumable.stp,
164 current_sidetable,
165 )?;
166 Ok(ControlFlow::Continue(()))
167 }
168}
169
170define_instruction_fn! {
171 br,
172 fuel_check = flat(opcode::BR),
173 |Args {
174 resumable,
175 wasm,
176 current_sidetable,
177 ..
178 }| {
179 let _label_idx = unsafe { read_label_idx_unchecked(wasm) };
182 do_sidetable_control_transfer(
183 wasm,
184 &mut resumable.stack,
185 &mut resumable.stp,
186 current_sidetable,
187 )?;
188 Ok(ControlFlow::Continue(()))
189 }
190}
191
192define_instruction_fn! {
193 br_if,
194 fuel_check = flat(opcode::BR_IF),
195 |Args {
196 resumable,
197 wasm,
198 current_sidetable,
199 ..
200 }| {
201 let _label_idx = unsafe { read_label_idx_unchecked(wasm) };
204
205 let test_val: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
206
207 if test_val != 0 {
208 do_sidetable_control_transfer(
209 wasm,
210 &mut resumable.stack,
211 &mut resumable.stp,
212 current_sidetable,
213 )?;
214 } else {
215 resumable.stp += 1;
216 }
217 trace!("Instruction: BR_IF");
218 Ok(ControlFlow::Continue(()))
219 }
220}
221
222define_instruction_fn! {
223 br_table,
224 fuel_check = flat(opcode::BR_TABLE),
225 |Args {
226 resumable,
227 wasm,
228 current_sidetable,
229 ..
230 }| {
231 let label_vec = wasm
232 .read_vec::<_, _, DecodingError>(|wasm| {
233 Ok(unsafe { read_label_idx_unchecked(wasm) })
236 }).unwrap();
237
238 let _default_label_idx = unsafe { read_label_idx_unchecked(wasm) };
241
242 let case_val_i32: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
244 let case_val = case_val_i32.cast_unsigned().into_usize();
245
246 if case_val >= label_vec.len() {
247 resumable.stp += label_vec.len();
248 } else {
249 resumable.stp += case_val;
250 }
251
252 do_sidetable_control_transfer(
253 wasm,
254 &mut resumable.stack,
255 &mut resumable.stp,
256 current_sidetable,
257 )?;
258 Ok(ControlFlow::Continue(()))
259 }
260}
261
262define_instruction_fn! {
263 r#return,
264 fuel_check = flat(opcode::RETURN),
265 |Args {
266 resumable,
267 wasm,
268 current_sidetable,
269 ..
270 }| {
271 do_sidetable_control_transfer(
273 wasm,
274 &mut resumable.stack,
275 &mut resumable.stp,
276 current_sidetable,
277 )?;
278 Ok(ControlFlow::Continue(()))
279 }
280}
281
282define_instruction_fn! {
283 call,
284 fuel_check = flat(opcode::CALL),
285 |Args {
286 store_inner,
287 modules,
288 resumable,
289 wasm,
290 current_module,
291 current_function_end_marker,
292 current_sidetable,
293 ..
294 }| {
295 let func_idx = unsafe { FuncIdx::read_unchecked(wasm) };
298
299 let FuncInst::WasmFunc(current_wasm_func_inst) =
306 (unsafe { store_inner.functions.get(resumable.current_func_addr) })
307 else {
308 unreachable!()
309 };
310
311 let current_module_inst = unsafe { modules.get(current_wasm_func_inst.module_addr) };
316
317 let func_to_call_addr = unsafe { current_module_inst.func_addrs.get(func_idx) };
320
321 let func_to_call_inst = unsafe { store_inner.functions.get(*func_to_call_addr) };
324
325 trace!("Instruction: call [{func_to_call_addr:?}]");
326
327 match func_to_call_inst {
328 FuncInst::HostFunc(host_func_to_call_inst) => {
329 let params = resumable
330 .stack
331 .pop_tail_iter(host_func_to_call_inst.function_type.params.valtypes.len());
332
333 return Ok(ControlFlow::Break(InterpreterLoopOutcome::HostCalled {
334 params,
335 func_addr: *func_to_call_addr,
336 hostcode: host_func_to_call_inst.hostcode,
337 }));
338 }
339 FuncInst::WasmFunc(wasm_func_to_call_inst) => {
340 let remaining_locals = &wasm_func_to_call_inst.locals;
341
342 resumable.stack.push_call_frame::<T>(
343 resumable.current_func_addr,
344 &wasm_func_to_call_inst.function_type,
345 remaining_locals,
346 wasm.pc,
347 resumable.stp,
348 )?;
349
350 resumable.current_func_addr = *func_to_call_addr;
351 *current_module = wasm_func_to_call_inst.module_addr;
352
353 let module = unsafe { modules.get(*current_module) };
358
359 wasm.full_wasm_binary = module.wasm_bytecode;
360 wasm.move_start_to(wasm_func_to_call_inst.code_expr)
361 .expect("code expression spans to always be valid");
362
363 resumable.stp = wasm_func_to_call_inst.stp;
364 *current_sidetable = &module.sidetable;
365 *current_function_end_marker = wasm_func_to_call_inst.code_expr.from()
366 + wasm_func_to_call_inst.code_expr.len();
367 }
368 }
369 trace!("Instruction: CALL");
370
371 Ok(ControlFlow::Continue(()))
372 }
373}
374
375define_instruction_fn! {
377 call_indirect,
378 fuel_check = flat(opcode::CALL_INDIRECT),
379 |Args {
380 store_inner,
381 modules,
382 resumable,
383 wasm,
384 current_module,
385 current_function_end_marker,
386 current_sidetable,
387 ..
388 }| {
389 let given_type_idx = unsafe { TypeIdx::read_unchecked(wasm) };
392 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
395
396 let module = unsafe { modules.get(*current_module) };
401
402 let table_addr = unsafe { module.table_addrs.get(table_idx) };
405 let tab = unsafe { store_inner.tables.get(*table_addr) };
408 let func_ty = unsafe { module.types.get(given_type_idx) };
411
412 let i: u32 = resumable.stack.pop_value().try_into().unwrap_validated();
413
414 let r = tab
415 .elem
416 .get(i.into_usize())
417 .ok_or(TrapError::TableAccessOutOfBounds)
418 .and_then(|r| {
419 if matches!(r, Ref::Null(_)) {
420 trace!("table_idx ({table_idx}) --- element index in table ({i})");
421 Err(TrapError::UninitializedElement)
422 } else {
423 Ok(r)
424 }
425 })?;
426
427 let func_to_call_addr = match *r {
428 Ref::Func(func_addr) => func_addr,
429 Ref::Null(_) => return Err(TrapError::IndirectCallNullFuncRef.into()),
430 Ref::Extern(_) => unreachable_validated!(),
431 };
432
433 let func_to_call_inst = unsafe { store_inner.functions.get(func_to_call_addr) };
437
438 if func_ty != func_to_call_inst.ty() {
439 return Err(TrapError::SignatureMismatch.into());
440 }
441
442 trace!("Instruction: call [{func_to_call_addr:?}]");
443
444 match func_to_call_inst {
445 FuncInst::HostFunc(host_func_to_call_inst) => {
446 let params = resumable
447 .stack
448 .pop_tail_iter(host_func_to_call_inst.function_type.params.valtypes.len());
449
450 return Ok(ControlFlow::Break(InterpreterLoopOutcome::HostCalled {
451 params,
452 func_addr: func_to_call_addr,
453 hostcode: host_func_to_call_inst.hostcode,
454 }));
455 }
456 FuncInst::WasmFunc(wasm_func_to_call_inst) => {
457 let remaining_locals = &wasm_func_to_call_inst.locals;
458
459 resumable.stack.push_call_frame::<T>(
460 resumable.current_func_addr,
461 &wasm_func_to_call_inst.function_type,
462 remaining_locals,
463 wasm.pc,
464 resumable.stp,
465 )?;
466
467 resumable.current_func_addr = func_to_call_addr;
468 *current_module = wasm_func_to_call_inst.module_addr;
469
470 let module = unsafe { modules.get(*current_module) };
475 wasm.full_wasm_binary = module.wasm_bytecode;
476 wasm.move_start_to(wasm_func_to_call_inst.code_expr)
477 .expect("code expression spans to always be valid");
478
479 resumable.stp = wasm_func_to_call_inst.stp;
480 *current_sidetable = &module.sidetable;
481 *current_function_end_marker = wasm_func_to_call_inst.code_expr.from()
482 + wasm_func_to_call_inst.code_expr.len();
483 }
484 }
485 trace!("Instruction: CALL_INDIRECT");
486 Ok(ControlFlow::Continue(()))
487 }
488}