winch_codegen/codegen/control.rs
1//! Data structures for control flow emission.
2//!
3//! Winch currently doesn't apply any sort of optimizations to control flow, but
4//! as a future optimization, for starters, we could perform a look ahead to the
5//! next instruction when reaching any of the comparison instructions. If the
6//! next instruction is a control instruction, we could avoid emitting
7//! a [`crate::masm::MacroAssembler::cmp_with_set`] and instead emit
8//! a conditional jump inline when emitting the control flow instruction.
9use super::{
10 CodeGenContext, CodeGenError, Emission, OperandSize, Reg, TypedReg, exceptions::TryTableInfo,
11};
12use crate::{
13 CallingConvention, Result,
14 abi::{ABI, ABIOperand, ABIResults, ABISig, RetArea},
15 bail, ensure, format_err,
16 masm::{IntCmpKind, MacroAssembler, MemMoveDirection, RegImm, SPOffset},
17 reg::writable,
18 stack::Val,
19};
20use cranelift_codegen::MachLabel;
21use wasmtime_environ::{WasmFuncType, WasmValType, collections::TryClone as _};
22
23/// Categorization of the type of the block.
24#[derive(Debug)]
25pub(crate) enum BlockType {
26 /// Doesn't produce or consume any values.
27 Void,
28 /// Produces a single value.
29 Single(WasmValType),
30 /// Consumes multiple values and produces multiple values.
31 Func(WasmFuncType),
32 /// An already resolved ABI signature.
33 ABISig(ABISig),
34}
35
36impl Clone for BlockType {
37 fn clone(&self) -> Self {
38 match self {
39 Self::Void => Self::Void,
40 Self::Single(x) => Self::Single(*x),
41 Self::ABISig(x) => Self::ABISig(x.clone()),
42 Self::Func(f) => Self::Func(f.clone_panic_on_oom()),
43 }
44 }
45}
46
47/// Holds all the information about the signature of the block.
48#[derive(Debug, Clone)]
49pub(crate) struct BlockSig {
50 /// The type of the block.
51 pub ty: BlockType,
52 /// ABI representation of the results of the block.
53 results: Option<ABIResults>,
54 /// ABI representation of the params of the block interpreted as results.
55 params: Option<ABIResults>,
56}
57
58impl BlockSig {
59 /// Create a new [BlockSig].
60 pub fn new(ty: BlockType) -> Self {
61 Self {
62 ty,
63 results: None,
64 params: None,
65 }
66 }
67
68 /// Create a new [BlockSig] from an [ABISig].
69 pub fn from_sig(sig: ABISig) -> Self {
70 Self {
71 ty: BlockType::sig(sig),
72 results: None,
73 params: None,
74 }
75 }
76
77 /// Return the ABI representation of the results of the block.
78 /// This method will lazily initialize the results if not present.
79 pub fn results<M>(&mut self) -> Result<&mut ABIResults>
80 where
81 M: MacroAssembler,
82 {
83 if self.ty.is_sig() {
84 return match &mut self.ty {
85 BlockType::ABISig(sig) => Ok(&mut sig.results),
86 _ => unreachable!(),
87 };
88 }
89
90 if self.results.is_some() {
91 return Ok(self.results.as_mut().unwrap());
92 }
93
94 let results = match &self.ty {
95 BlockType::Void => <M::ABI as ABI>::abi_results(&[], &CallingConvention::Default),
96 BlockType::Single(ty) => {
97 <M::ABI as ABI>::abi_results(&[*ty], &CallingConvention::Default)
98 }
99 BlockType::Func(f) => {
100 <M::ABI as ABI>::abi_results(f.results(), &CallingConvention::Default)
101 }
102 BlockType::ABISig(_) => unreachable!(),
103 };
104
105 self.results = Some(results?);
106 Ok(self.results.as_mut().unwrap())
107 }
108
109 /// Construct an ABI result representation of the params of the block.
110 /// This is needed for loops and for handling cases in which params flow as
111 /// the block's results, i.e. in the presence of an empty then or else.
112 pub fn params<M>(&mut self) -> Result<&mut ABIResults>
113 where
114 M: MacroAssembler,
115 {
116 if self.params.is_some() {
117 return Ok(self.params.as_mut().unwrap());
118 }
119
120 let params_as_results = match &self.ty {
121 BlockType::Void | BlockType::Single(_) => {
122 <M::ABI as ABI>::abi_results(&[], &CallingConvention::Default)
123 }
124 BlockType::Func(f) => {
125 <M::ABI as ABI>::abi_results(f.params(), &CallingConvention::Default)
126 }
127 // Once we have created a block type from a known signature, we
128 // can't modify its meaning. This should only be used for the
129 // function body block, in which case there's no need for treating
130 // params as results.
131 BlockType::ABISig(_) => unreachable!(),
132 };
133
134 self.params = Some(params_as_results?);
135 Ok(self.params.as_mut().unwrap())
136 }
137
138 /// Returns the signature param count.
139 pub fn param_count(&self) -> usize {
140 match &self.ty {
141 BlockType::Void | BlockType::Single(_) => 0,
142 BlockType::Func(f) => f.params().len(),
143 BlockType::ABISig(sig) => sig.params_without_retptr().len(),
144 }
145 }
146
147 /// Returns the signature return count.
148 pub fn return_count(&self) -> usize {
149 match &self.ty {
150 BlockType::Void => 0,
151 BlockType::Single(_) => 1,
152 BlockType::Func(f) => f.results().len(),
153 BlockType::ABISig(sig) => sig.results().len(),
154 }
155 }
156}
157
158impl BlockType {
159 /// Create a [BlockType::Void].
160 pub fn void() -> Self {
161 Self::Void
162 }
163
164 /// Create a [BlockType::Single] from the given [WasmType].
165 pub fn single(ty: WasmValType) -> Self {
166 Self::Single(ty)
167 }
168
169 /// Create a [BlockType::Func] from the given [WasmFuncType].
170 pub fn func(ty: WasmFuncType) -> Self {
171 Self::Func(ty)
172 }
173
174 /// Create a [BlockType::ABISig].
175 pub fn sig(sig: ABISig) -> Self {
176 Self::ABISig(sig)
177 }
178
179 /// Returns true if the type of the block is [BlockType::ABISig].
180 pub fn is_sig(&self) -> bool {
181 match self {
182 Self::ABISig(_) => true,
183 _ => false,
184 }
185 }
186}
187
188/// The expected value and machine stack state when entering and exiting the block.
189#[derive(Debug, Default, Copy, Clone)]
190pub(crate) struct StackState {
191 /// The base stack pointer offset.
192 /// This offset is set when entering the block, after saving any live
193 /// registers and locals.
194 /// It is calculated by subtracting the size, in bytes, of any block params
195 /// to the current stack pointer offset.
196 pub base_offset: SPOffset,
197 /// The target stack pointer offset.
198 /// This offset is calculated by adding the size of the stack results
199 /// to the base stack pointer offset.
200 pub target_offset: SPOffset,
201 /// The base length of the value stack when entering the block.
202 /// Which is the current length of the value stack minus any block parameters.
203 pub base_len: usize,
204 /// The target length of the value stack when exiting the block.
205 /// Calculate by adding the number of results to the base value stack
206 /// length.
207 pub target_len: usize,
208}
209
210/// Holds the all the metadata to support the emission
211/// of control flow instructions.
212#[derive(Debug)]
213pub(crate) enum ControlStackFrame {
214 If {
215 /// The if continuation label.
216 cont: MachLabel,
217 /// The exit label of the block.
218 exit: MachLabel,
219 /// The signature of the block.
220 sig: BlockSig,
221 /// The stack state of the block.
222 stack_state: StackState,
223 /// Local reachability state when entering the block.
224 reachable: bool,
225 },
226 Else {
227 /// The exit label of the block.
228 exit: MachLabel,
229 /// The signature of the block.
230 sig: BlockSig,
231 /// The stack state of the block.
232 stack_state: StackState,
233 /// Local reachability state when entering the block.
234 reachable: bool,
235 },
236 Block {
237 /// The block exit label.
238 exit: MachLabel,
239 /// The signature of the block.
240 sig: BlockSig,
241 /// The stack state of the block.
242 stack_state: StackState,
243 /// Exit state of the block.
244 ///
245 /// This flag is used to determine if a block is a branch
246 /// target. By default, this is false, and it's updated when
247 /// emitting a `br` or `br_if`.
248 is_branch_target: bool,
249 /// Exception-handling information when this block is a `try_table`.
250 try_table_info: Option<TryTableInfo>,
251 },
252 Loop {
253 /// The start of the Loop.
254 head: MachLabel,
255 /// The stack state of the block.
256 stack_state: StackState,
257 /// The signature of the block.
258 sig: BlockSig,
259 },
260}
261
262impl ControlStackFrame {
263 /// Returns [`ControlStackFrame`] for an if.
264 pub fn r#if<M: MacroAssembler>(
265 sig: BlockSig,
266 masm: &mut M,
267 context: &mut CodeGenContext<Emission>,
268 ) -> Result<Self> {
269 let mut control = Self::If {
270 cont: masm.get_label()?,
271 exit: masm.get_label()?,
272 sig,
273 reachable: context.reachable,
274 stack_state: Default::default(),
275 };
276
277 control.emit(masm, context)?;
278 Ok(control)
279 }
280
281 /// Returns [`ControlStackFrame`] for a block.
282 pub fn block<M: MacroAssembler>(
283 sig: BlockSig,
284 masm: &mut M,
285 context: &mut CodeGenContext<Emission>,
286 ) -> Result<Self> {
287 Self::block_impl(sig, None, masm, context)
288 }
289
290 /// Returns a block control frame with exception-handler information.
291 pub fn try_table<M: MacroAssembler>(
292 sig: BlockSig,
293 info: TryTableInfo,
294 masm: &mut M,
295 context: &mut CodeGenContext<Emission>,
296 ) -> Result<Self> {
297 Self::block_impl(sig, Some(info), masm, context)
298 }
299
300 fn block_impl<M: MacroAssembler>(
301 sig: BlockSig,
302 try_table_info: Option<TryTableInfo>,
303 masm: &mut M,
304 context: &mut CodeGenContext<Emission>,
305 ) -> Result<Self> {
306 let mut control = Self::Block {
307 sig,
308 is_branch_target: false,
309 exit: masm.get_label()?,
310 stack_state: Default::default(),
311 try_table_info,
312 };
313
314 control.emit(masm, context)?;
315 Ok(control)
316 }
317
318 /// Returns this block's try-table information, if present.
319 pub fn try_table_info(&self) -> Option<&TryTableInfo> {
320 match self {
321 Self::Block { try_table_info, .. } => try_table_info.as_ref(),
322 _ => None,
323 }
324 }
325
326 /// Takes this block's try-table information, if present.
327 pub fn take_try_table_info(&mut self) -> Option<TryTableInfo> {
328 match self {
329 Self::Block { try_table_info, .. } => try_table_info.take(),
330 _ => None,
331 }
332 }
333
334 /// Returns [`ControlStackFrame`] for a loop.
335 pub fn r#loop<M: MacroAssembler>(
336 sig: BlockSig,
337 masm: &mut M,
338 context: &mut CodeGenContext<Emission>,
339 ) -> Result<Self> {
340 let mut control = Self::Loop {
341 stack_state: Default::default(),
342 sig,
343 head: masm.get_label()?,
344 };
345
346 control.emit(masm, context)?;
347 Ok(control)
348 }
349
350 fn init<M: MacroAssembler>(
351 &mut self,
352 masm: &mut M,
353 context: &mut CodeGenContext<Emission>,
354 ) -> Result<()> {
355 self.calculate_stack_state(context, masm)?;
356 // If the block has stack results, immediately resolve the return area
357 // base.
358 if self.results::<M>()?.on_stack() {
359 let results_base = self.stack_state().target_offset;
360 self.results::<M>()?.set_ret_area(RetArea::sp(results_base));
361 }
362
363 if self.is_if() || self.is_loop() {
364 // Preemptively handle block params as results so that the params
365 // are correctly placed in memory. This is especially
366 // important for control flow joins with empty blocks:
367 //
368 //(module
369 // (func (export "params") (param i32) (result i32)
370 // (i32.const 2)
371 // (if (param i32) (result i32) (local.get 0)
372 // (then))
373 // (i32.const 3)
374 // (i32.add)
375 // )
376 //)
377 let base_offset = self.stack_state().base_offset;
378 if self.params::<M>()?.on_stack() {
379 let offset = base_offset.as_u32() + self.params::<M>()?.size();
380 self.params::<M>()?
381 .set_ret_area(RetArea::sp(SPOffset::from_u32(offset)));
382 }
383 Self::top_abi_results_impl(
384 self.params::<M>()?,
385 context,
386 masm,
387 |params: &ABIResults, _, _| Ok(params.ret_area().copied()),
388 )?;
389 }
390 Ok(())
391 }
392
393 /// Calculates the [StackState] of the block.
394 fn calculate_stack_state<M: MacroAssembler>(
395 &mut self,
396 context: &mut CodeGenContext<Emission>,
397 masm: &mut M,
398 ) -> Result<()> {
399 use ControlStackFrame::*;
400 let sig = self.sig();
401 // If the block type contains a full [ABISig], do not take into account
402 // the params, since these are the params of the function that is
403 // currently being compiled and the value stack doesn't currently
404 // contain any values anyway.
405 let param_count = if sig.ty.is_sig() {
406 0
407 } else {
408 sig.param_count()
409 };
410 let return_count = sig.return_count();
411 ensure!(
412 context.stack.len() >= param_count,
413 CodeGenError::missing_values_in_stack()
414 );
415 let results_size = self.results::<M>()?.size();
416
417 // Save any live registers and locals.
418 context.spill(masm)?;
419
420 let base_len = context.stack.len() - param_count;
421 let stack_consumed = context.stack.sizeof(param_count);
422 let current_sp = masm.sp_offset()?;
423 let base_offset = SPOffset::from_u32(current_sp.as_u32() - stack_consumed);
424
425 match self {
426 If { stack_state, .. } | Block { stack_state, .. } | Loop { stack_state, .. } => {
427 stack_state.base_offset = base_offset;
428 stack_state.base_len = base_len;
429 stack_state.target_offset = SPOffset::from_u32(base_offset.as_u32() + results_size);
430 stack_state.target_len = base_len + return_count;
431 }
432 _ => {}
433 }
434 Ok(())
435 }
436
437 /// This function ensures that the state of the -- machine and value --
438 /// stack is the right one when reaching a control frame branch in which
439 /// reachability is restored or when reaching the end of a function in an
440 /// unreachable state. This function is intended to be called when handling
441 /// an unreachable else or end.
442 //
443 /// This function will truncate the value stack to the base length of
444 /// the control frame and will also set the stack pointer offset to reflect
445 /// the offset expected by the target branch.
446 ///
447 // NB: This method is assumed to be called *before* pushing any block
448 // results to the value stack, so that any excess values are cleaned up.
449 pub fn ensure_stack_state<M: MacroAssembler>(
450 &mut self,
451 masm: &mut M,
452 context: &mut CodeGenContext<Emission>,
453 ) -> Result<()> {
454 let state = self.stack_state();
455 // This assumes that at jump sites, the machine stack pointer will be
456 // adjusted to match the expectations of the target branch (e.g.
457 // `target_offset`); after performing the jump, the MacroAssembler
458 // implementation will soft-reset the stack pointer offset to its
459 // original offset, ensure that other parts of the program have access
460 // to the right offset, this is especially important in conditional
461 // branches.
462 // When restoring reachability we ensure that the MacroAssembler offset
463 // is set to match the expectations of the target branch, similar to how
464 // the machine stack pointer was adjusted at jump sites.
465 masm.reset_stack_pointer(state.target_offset)?;
466 // We use the base length, because this function is assumed to be called
467 // *before* pushing any results to the value stack. This way, any excess
468 // values will be discarded.
469 context.truncate_stack_to(state.base_len)
470 }
471
472 /// Return the type information of the block.
473 pub fn sig(&self) -> &BlockSig {
474 use ControlStackFrame::*;
475 match self {
476 If { sig, .. } | Else { sig, .. } | Loop { sig, .. } | Block { sig, .. } => sig,
477 }
478 }
479
480 fn emit<M: MacroAssembler>(
481 &mut self,
482 masm: &mut M,
483 context: &mut CodeGenContext<Emission>,
484 ) -> Result<()> {
485 use ControlStackFrame::*;
486
487 // Do not perform any emissions if we are in an unreachable state.
488 if !context.reachable {
489 return Ok(());
490 }
491
492 match *self {
493 If { cont, .. } => {
494 // Pop the condition value.
495 // Because in the case of Self::If, Self::init, will top the
496 // branch params, we exclude any result registers from being
497 // used as the branch test.
498 let top = context.without::<Result<TypedReg>, _, _>(
499 self.params::<M>()?.regs(),
500 masm,
501 |cx, masm| cx.pop_to_reg(masm, None),
502 )??;
503 self.init(masm, context)?;
504 masm.branch(
505 IntCmpKind::Eq,
506 top.reg,
507 top.reg.into(),
508 cont,
509 OperandSize::S32,
510 )?;
511 context.free_reg(top);
512 Ok(())
513 }
514 Block { .. } => self.init(masm, context),
515 Loop { head, .. } => {
516 self.init(masm, context)?;
517 masm.bind(head)?;
518 Ok(())
519 }
520 _ => Err(format_err!(CodeGenError::if_control_frame_expected())),
521 }
522 }
523
524 /// Handles the else branch if the current control stack frame is
525 /// [`ControlStackFrame::If`].
526 pub fn emit_else<M: MacroAssembler>(
527 &mut self,
528 masm: &mut M,
529 context: &mut CodeGenContext<Emission>,
530 ) -> Result<()> {
531 ensure!(self.is_if(), CodeGenError::if_control_frame_expected());
532 let state = self.stack_state();
533
534 ensure!(
535 state.target_len == context.stack.len(),
536 CodeGenError::control_frame_state_mismatch()
537 );
538 self.pop_abi_results(context, masm, |results, _, _| {
539 Ok(results.ret_area().copied())
540 })?;
541 masm.jmp(*self.exit_label().unwrap())?;
542 self.bind_else(masm, context)?;
543 Ok(())
544 }
545
546 /// Binds the else branch label and converts `self` to
547 /// [`ControlStackFrame::Else`].
548 pub fn bind_else<M: MacroAssembler>(
549 &mut self,
550 masm: &mut M,
551 context: &mut CodeGenContext<Emission>,
552 ) -> Result<()> {
553 use ControlStackFrame::*;
554 match self {
555 If {
556 cont,
557 sig,
558 stack_state,
559 exit,
560 ..
561 } => {
562 // Bind the else branch.
563 masm.bind(*cont)?;
564
565 // Push the abi results to the value stack, so that they are
566 // used as params for the else branch. At the beginning of the
567 // if block, any params are preemptively resolved as results;
568 // when reaching the else all params are already materialized as
569 // stack results. As part of ensuring the right state when
570 // entering the else branch, the following snippet also soft
571 // resets the stack pointer so that it matches the expectations
572 // of the else branch: the stack pointer is expected to be at
573 // the base stack pointer, plus the params stack size in bytes.
574 let params_size = sig.params::<M>()?.size();
575 context.push_abi_results::<M, _>(sig.params::<M>()?, masm, |params, _, _| {
576 params.ret_area().copied()
577 })?;
578 masm.reset_stack_pointer(SPOffset::from_u32(
579 stack_state.base_offset.as_u32() + params_size,
580 ))?;
581
582 // Update the stack control frame with an else control frame.
583 *self = ControlStackFrame::Else {
584 exit: *exit,
585 stack_state: *stack_state,
586 reachable: context.reachable,
587 sig: sig.clone(),
588 };
589 }
590 _ => bail!(CodeGenError::if_control_frame_expected()),
591 }
592 Ok(())
593 }
594
595 /// Handles the end of a control stack frame.
596 pub fn emit_end<M: MacroAssembler>(
597 &mut self,
598 masm: &mut M,
599 context: &mut CodeGenContext<Emission>,
600 ) -> Result<()> {
601 use ControlStackFrame::*;
602 match self {
603 If { stack_state, .. } | Else { stack_state, .. } | Block { stack_state, .. } => {
604 ensure!(
605 stack_state.target_len == context.stack.len(),
606 CodeGenError::control_frame_state_mismatch()
607 );
608 // Before binding the exit label, we handle the block results.
609 self.pop_abi_results(context, masm, |results, _, _| {
610 Ok(results.ret_area().copied())
611 })?;
612 self.bind_end(masm, context)?;
613 }
614 Loop { stack_state, .. } => {
615 ensure!(
616 stack_state.target_len == context.stack.len(),
617 CodeGenError::control_frame_state_mismatch()
618 );
619 }
620 };
621
622 Ok(())
623 }
624
625 /// Binds the exit label of the current control stack frame and pushes the
626 /// ABI results to the value stack.
627 pub fn bind_end<M: MacroAssembler>(
628 &mut self,
629 masm: &mut M,
630 context: &mut CodeGenContext<Emission>,
631 ) -> Result<()> {
632 self.push_abi_results(context, masm)?;
633 self.bind_exit_label(masm)
634 }
635
636 /// Binds the exit label of the control stack frame.
637 pub fn bind_exit_label<M: MacroAssembler>(&self, masm: &mut M) -> Result<()> {
638 use ControlStackFrame::*;
639 match self {
640 // We use an explicit label to track the exit of an if block. In case there's no
641 // else, we bind the if's continuation block to make sure that any jumps from the if
642 // condition are reachable and we bind the explicit exit label as well to ensure that any
643 // branching instructions are able to correctly reach the block's end.
644 If { cont, .. } => masm.bind(*cont)?,
645 _ => {}
646 }
647 if let Some(label) = self.exit_label() {
648 masm.bind(*label)?;
649 }
650 Ok(())
651 }
652
653 /// Returns the continuation label of the current control stack frame.
654 pub fn label(&self) -> &MachLabel {
655 use ControlStackFrame::*;
656
657 match self {
658 If { exit, .. } | Else { exit, .. } | Block { exit, .. } => exit,
659 Loop { head, .. } => head,
660 }
661 }
662
663 /// Returns the exit label of the current control stack frame. Note that
664 /// this is similar to [`ControlStackFrame::label`], with the only difference that it
665 /// returns `None` for `Loop` since its label doesn't represent an exit.
666 pub fn exit_label(&self) -> Option<&MachLabel> {
667 use ControlStackFrame::*;
668
669 match self {
670 If { exit, .. } | Else { exit, .. } | Block { exit, .. } => Some(exit),
671 Loop { .. } => None,
672 }
673 }
674
675 /// Set the current control stack frame as a branch target.
676 pub fn set_as_target(&mut self) {
677 match self {
678 ControlStackFrame::Block {
679 is_branch_target, ..
680 } => {
681 *is_branch_target = true;
682 }
683 _ => {}
684 }
685 }
686
687 /// Returns [`crate::abi::ABIResults`] of the control stack frame
688 /// block.
689 pub fn results<M>(&mut self) -> Result<&mut ABIResults>
690 where
691 M: MacroAssembler,
692 {
693 use ControlStackFrame::*;
694
695 match self {
696 If { sig, .. } | Else { sig, .. } | Block { sig, .. } => sig.results::<M>(),
697 Loop { sig, .. } => sig.params::<M>(),
698 }
699 }
700
701 /// Returns the block params interpreted as [crate::abi::ABIResults].
702 pub fn params<M>(&mut self) -> Result<&mut ABIResults>
703 where
704 M: MacroAssembler,
705 {
706 use ControlStackFrame::*;
707 match self {
708 If { sig, .. } | Else { sig, .. } | Block { sig, .. } | Loop { sig, .. } => {
709 sig.params::<M>()
710 }
711 }
712 }
713
714 /// Orchestrates how block results are handled.
715 /// Results are handled in reverse order, starting from register results
716 /// continuing to memory values. This guarantees that the stack ordering
717 /// invariant is maintained. See [ABIResults] for more details.
718 ///
719 /// This function will iterate through each result and invoke the provided
720 /// callback if there are results on the stack.
721 ///
722 /// Calculating the return area involves ensuring that there's enough stack
723 /// space to store the block's results. To make the process of handling
724 /// multiple results easier, this function will save all live registers and
725 /// locals right after handling any register results. This will ensure that
726 /// the top `n` values in the value stack are correctly placed in the memory
727 /// locations corresponding to multiple stack results. Once the iteration
728 /// over all the results is done, the stack result area of the block will be
729 /// updated.
730 pub fn pop_abi_results<M, F>(
731 &mut self,
732 context: &mut CodeGenContext<Emission>,
733 masm: &mut M,
734 calculate_ret_area: F,
735 ) -> Result<()>
736 where
737 M: MacroAssembler,
738 F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Result<Option<RetArea>>,
739 {
740 Self::pop_abi_results_impl(self.results::<M>()?, context, masm, calculate_ret_area)
741 }
742
743 /// Shared implementation for popping the ABI results.
744 /// This is needed because, in some cases, params must be interpreted and
745 /// used as the results of the block. When emitting code at control flow
746 /// joins, the block params are interpreted as results, to ensure that they
747 /// can correctly "flow" as the results of the block. This is especially
748 /// important in the presence of empty then, else and loop blocks. This
749 /// interpretation is an internal detail of the control module, and having
750 /// a shared implementation allows the caller to decide how the
751 /// results should be interpreted.
752 pub fn pop_abi_results_impl<M, F>(
753 results: &mut ABIResults,
754 context: &mut CodeGenContext<Emission>,
755 masm: &mut M,
756 mut calculate_ret_area: F,
757 ) -> Result<()>
758 where
759 M: MacroAssembler,
760 F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Result<Option<RetArea>>,
761 {
762 let mut iter = results.operands().iter().rev().peekable();
763
764 while let Some(ABIOperand::Reg { reg, .. }) = iter.peek() {
765 let TypedReg { reg, .. } = context.pop_to_reg(masm, Some(*reg))?;
766 context.free_reg(reg);
767 iter.next().unwrap();
768 }
769
770 let ret_area = calculate_ret_area(results, context, masm)?;
771
772 let retptr = Self::maybe_load_retptr(ret_area.as_ref(), &results, context, masm)?;
773 if let Some(area) = ret_area {
774 if area.is_sp() {
775 Self::ensure_ret_area(&area, context, masm)?;
776 }
777 }
778
779 if let Some(retptr) = retptr {
780 while let Some(ABIOperand::Stack { offset, .. }) = iter.peek() {
781 let addr = masm.address_at_reg(retptr, *offset)?;
782 context.pop_to_addr(masm, addr)?;
783 iter.next().unwrap();
784 }
785 context.free_reg(retptr);
786 }
787
788 if let Some(area) = ret_area {
789 if area.is_sp() {
790 Self::adjust_stack_results(area, results, context, masm)?;
791 }
792 }
793
794 Ok(())
795 }
796
797 /// Convenience wrapper around [CodeGenContext::push_abi_results] using the
798 /// results of the current frame.
799 fn push_abi_results<M>(
800 &mut self,
801 context: &mut CodeGenContext<Emission>,
802 masm: &mut M,
803 ) -> Result<()>
804 where
805 M: MacroAssembler,
806 {
807 context.push_abi_results(self.results::<M>()?, masm, |results, _, _| {
808 results.ret_area().copied()
809 })
810 }
811
812 /// Preemptively handles the ABI results of the current frame.
813 /// This function is meant to be used when emitting control flow with joins,
814 /// in which it's not possible to know at compile time which branch will be
815 /// taken.
816 pub fn top_abi_results<M, F>(
817 &mut self,
818 context: &mut CodeGenContext<Emission>,
819 masm: &mut M,
820 calculate_ret_area: F,
821 ) -> Result<()>
822 where
823 M: MacroAssembler,
824 F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Result<Option<RetArea>>,
825 {
826 Self::top_abi_results_impl::<M, _>(self.results::<M>()?, context, masm, calculate_ret_area)
827 }
828
829 /// Internal implementation of [Self::top_abi_results].
830 /// See [Self::pop_abi_results_impl] on why an internal implementation is
831 /// needed.
832 fn top_abi_results_impl<M, F>(
833 results: &mut ABIResults,
834 context: &mut CodeGenContext<Emission>,
835 masm: &mut M,
836 mut calculate_ret_area: F,
837 ) -> Result<()>
838 where
839 M: MacroAssembler,
840 F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Result<Option<RetArea>>,
841 {
842 let mut area = None;
843 Self::pop_abi_results_impl::<M, _>(results, context, masm, |r, context, masm| {
844 area = calculate_ret_area(r, context, masm)?;
845 Ok(area)
846 })?;
847 // Use the previously calculated area to ensure that the ret area is
848 // kept in sync between both operations.
849 context.push_abi_results::<M, _>(results, masm, |_, _, _| area)
850 }
851
852 // If the results on the stack are handled via the stack pointer, ensure
853 // that the stack results are correctly located. In general, since values in
854 // the value stack are spilled when exiting the block, the top `n` entries
855 // in the value stack, representing the `n` stack results of the block are
856 // almost correctly located. However, since constants are not
857 // spilled, their presence complicate block exits. For this reason, the
858 // last step for finalizing multiple block results involves:
859 // * Scanning the value stack from oldest to newest memory values and
860 // calculating the source and destination of each value, if the source
861 // is closer to the stack pointer (greater) than the destination,
862 // perform a memory move of the bytes to its destination, else stop,
863 // because the memory values are in place.
864 // * Scanning the value stack from newest to oldest and calculating the
865 // source and destination of each value, if the source is closer to the
866 // frame pointer (less) than the destination, perform a memory move of
867 // the bytes to its destination, else stop, because the memory values
868 // are in place.
869 // * Lastly, iterate over the top `n` elements of the value stack,
870 // and spill any constant values, placing them in their respective
871 // memory location.
872 //
873 // The implementation in Winch is inspired by how this is handled in
874 // SpiderMonkey's WebAssembly Baseline Compiler:
875 // https://wingolog.org/archives/2020/04/03/multi-value-webassembly-in-firefox-from-1-to-n
876 fn adjust_stack_results<M>(
877 ret_area: RetArea,
878 results: &ABIResults,
879 context: &mut CodeGenContext<Emission>,
880 masm: &mut M,
881 ) -> Result<()>
882 where
883 M: MacroAssembler,
884 {
885 ensure!(ret_area.is_sp(), CodeGenError::sp_addressing_expected());
886 let results_offset = ret_area.unwrap_sp();
887
888 // Start iterating from memory values that are closer to the
889 // frame pointer (oldest entries first).
890 for (i, operand) in results.operands().iter().enumerate() {
891 if operand.is_reg() {
892 break;
893 }
894
895 let value_index = (context.stack.len() - results.stack_operands_len()) + i;
896 let val = context.stack.inner()[value_index];
897
898 match (val, operand) {
899 (Val::Memory(mem), ABIOperand::Stack { offset, size, .. }) => {
900 let dst = results_offset.as_u32() - *offset;
901 let src = mem.slot.offset;
902
903 // Values are moved from lower (SP) to higher (FP)
904 // addresses.
905 if src.as_u32() <= dst {
906 break;
907 }
908
909 masm.memmove(
910 src,
911 SPOffset::from_u32(dst),
912 *size,
913 MemMoveDirection::LowToHigh,
914 )?;
915 }
916 _ => {}
917 }
918 }
919
920 // Start iterating from memory values that are closer to the
921 // stack pointer (newest entries first).
922 for (i, operand) in results
923 .operands()
924 .iter()
925 .rev()
926 // Skip any register results.
927 .skip(results.regs().len())
928 .enumerate()
929 {
930 let value_index = context.stack.len() - i - 1;
931 let val = context.stack.inner()[value_index];
932 match (val, operand) {
933 (Val::Memory(mem), ABIOperand::Stack { offset, size, .. }) => {
934 let dst = results_offset.as_u32() - *offset;
935 let src = mem.slot.offset;
936
937 // Values are moved from higher (FP) to lower (SP)
938 // addresses.
939 if src.as_u32() >= dst {
940 break;
941 }
942
943 masm.memmove(
944 src,
945 SPOffset::from_u32(dst),
946 *size,
947 MemMoveDirection::HighToLow,
948 )?;
949 }
950 _ => {}
951 }
952 }
953
954 // Finally store any constants in the value stack in their respective
955 // locations.
956 for operand in results
957 .operands()
958 .iter()
959 .take(results.stack_operands_len())
960 .rev()
961 {
962 // If we want to do this, we should start from newest, essentially from top to
963 // bottom in the iteration of the operands.
964 match (operand, context.stack.peek().unwrap()) {
965 (ABIOperand::Stack { ty, offset, .. }, Val::I32(v)) => {
966 let addr = masm
967 .address_from_sp(SPOffset::from_u32(results_offset.as_u32() - *offset))?;
968 masm.store(RegImm::i32(*v), addr, (*ty).try_into()?)?;
969 }
970 (ABIOperand::Stack { ty, offset, .. }, Val::I64(v)) => {
971 let addr = masm
972 .address_from_sp(SPOffset::from_u32(results_offset.as_u32() - *offset))?;
973 masm.store(RegImm::i64(*v), addr, (*ty).try_into()?)?;
974 }
975 (ABIOperand::Stack { ty, offset, .. }, Val::F32(v)) => {
976 let addr = masm
977 .address_from_sp(SPOffset::from_u32(results_offset.as_u32() - *offset))?;
978 masm.store(RegImm::f32(v.bits()), addr, (*ty).try_into()?)?;
979 }
980 (ABIOperand::Stack { ty, offset, .. }, Val::F64(v)) => {
981 let addr = masm
982 .address_from_sp(SPOffset::from_u32(results_offset.as_u32() - *offset))?;
983 masm.store(RegImm::f64(v.bits()), addr, (*ty).try_into()?)?;
984 }
985 (ABIOperand::Stack { ty, offset, .. }, Val::V128(v)) => {
986 let addr = masm
987 .address_from_sp(SPOffset::from_u32(results_offset.as_u32() - *offset))?;
988 masm.store(RegImm::v128(*v), addr, (*ty).try_into()?)?;
989 }
990 (_, v) => debug_assert!(v.is_mem()),
991 }
992
993 let _ = context.stack.pop().unwrap();
994 }
995
996 // Adjust any excess stack space: the stack space after handling the
997 // block's results should be the exact amount needed by the return area.
998 ensure!(
999 masm.sp_offset()?.as_u32() >= results_offset.as_u32(),
1000 CodeGenError::invalid_sp_offset()
1001 );
1002 masm.free_stack(masm.sp_offset()?.as_u32() - results_offset.as_u32())?;
1003 Ok(())
1004 }
1005
1006 /// Ensures that there is enough space for return values on the stack.
1007 /// This function is called at the end of all blocks and when branching from
1008 /// within blocks.
1009 fn ensure_ret_area<M>(
1010 ret_area: &RetArea,
1011 context: &mut CodeGenContext<Emission>,
1012 masm: &mut M,
1013 ) -> Result<()>
1014 where
1015 M: MacroAssembler,
1016 {
1017 ensure!(ret_area.is_sp(), CodeGenError::sp_addressing_expected());
1018 // Save any live registers and locals when exiting the block to ensure
1019 // that the respective values are correctly located in memory.
1020 // See [Self::adjust_stack_results] for more details.
1021 context.spill(masm)?;
1022 if ret_area.unwrap_sp() > masm.sp_offset()? {
1023 masm.reserve_stack(ret_area.unwrap_sp().as_u32() - masm.sp_offset()?.as_u32())?
1024 }
1025
1026 Ok(())
1027 }
1028
1029 /// Loads the return pointer, if it exists, into the next available register.
1030 fn maybe_load_retptr<M>(
1031 ret_area: Option<&RetArea>,
1032 results: &ABIResults,
1033 context: &mut CodeGenContext<Emission>,
1034 masm: &mut M,
1035 ) -> Result<Option<Reg>>
1036 where
1037 M: MacroAssembler,
1038 {
1039 if let Some(area) = ret_area {
1040 match area {
1041 RetArea::Slot(slot) => {
1042 let base = context.without::<Result<Reg>, M, _>(
1043 results.regs(),
1044 masm,
1045 |cx, masm| cx.any_gpr(masm),
1046 )??;
1047 let local_addr = masm.local_address(&slot)?;
1048 masm.load_ptr(local_addr, writable!(base))?;
1049 Ok(Some(base))
1050 }
1051 _ => Ok(None),
1052 }
1053 } else {
1054 Ok(None)
1055 }
1056 }
1057
1058 /// This function is used at the end of unreachable code handling
1059 /// to determine if the reachability status should be updated.
1060 pub fn is_next_sequence_reachable(&self) -> bool {
1061 use ControlStackFrame::*;
1062
1063 match self {
1064 // For if/else, the reachability of the next sequence is determined
1065 // by the reachability state at the start of the block. An else
1066 // block will be reachable if the if block is also reachable at
1067 // entry.
1068 If { reachable, .. } | Else { reachable, .. } => *reachable,
1069 // For blocks, the reachability of the next sequence is determined
1070 // if they're a branch target.
1071 Block {
1072 is_branch_target, ..
1073 } => *is_branch_target,
1074 // Loops are not used for reachability analysis,
1075 // given that they don't have exit branches.
1076 Loop { .. } => false,
1077 }
1078 }
1079
1080 /// Returns a reference to the [StackState] of the block.
1081 pub fn stack_state(&self) -> &StackState {
1082 use ControlStackFrame::*;
1083 match self {
1084 If { stack_state, .. }
1085 | Else { stack_state, .. }
1086 | Block { stack_state, .. }
1087 | Loop { stack_state, .. } => stack_state,
1088 }
1089 }
1090
1091 /// Returns true if the current frame is [ControlStackFrame::If].
1092 pub fn is_if(&self) -> bool {
1093 match self {
1094 Self::If { .. } => true,
1095 _ => false,
1096 }
1097 }
1098
1099 /// Returns true if the current frame is [ControlStackFrame::Loop].
1100 pub fn is_loop(&self) -> bool {
1101 match self {
1102 Self::Loop { .. } => true,
1103 _ => false,
1104 }
1105 }
1106
1107 /// Returns true if the current stack pointer is unbalanced
1108 /// relative to the the expected control frame stack pointer
1109 /// offset. The stack pointer is considered unbalanced relative
1110 /// to the control frame if the stack pointer is greater than the
1111 /// the target stack pointer offset expected by the control frame.
1112 pub fn unbalanced<M: MacroAssembler>(&self, masm: &mut M) -> Result<bool> {
1113 Ok(masm.sp_offset()? > self.stack_state().target_offset)
1114 }
1115}