cranelift_frontend/frontend/
safepoints.rs

1//! Support for safepoints and stack maps.
2
3use super::*;
4use crate::{HashMap, HashSet};
5use core::ops::{Index, IndexMut};
6
7#[derive(Clone, Copy)]
8#[repr(u8)]
9enum SlotSize {
10    Size8 = 0,
11    Size16 = 1,
12    Size32 = 2,
13    Size64 = 3,
14    Size128 = 4,
15    // If adding support for more slot sizes, update `SLOT_SIZE_LEN` below.
16}
17const SLOT_SIZE_LEN: usize = 5;
18
19impl TryFrom<ir::Type> for SlotSize {
20    type Error = &'static str;
21
22    fn try_from(ty: ir::Type) -> Result<Self, Self::Error> {
23        Self::new(ty.bytes()).ok_or("type is not supported in stack maps")
24    }
25}
26
27impl SlotSize {
28    fn new(bytes: u32) -> Option<Self> {
29        match bytes {
30            1 => Some(Self::Size8),
31            2 => Some(Self::Size16),
32            4 => Some(Self::Size32),
33            8 => Some(Self::Size64),
34            16 => Some(Self::Size128),
35            _ => None,
36        }
37    }
38
39    fn unwrap_new(bytes: u32) -> Self {
40        Self::new(bytes).unwrap_or_else(|| panic!("cannot create a `SlotSize` for {bytes} bytes"))
41    }
42}
43
44/// A map from every `SlotSize` to a `T`.
45struct SlotSizeMap<T>([T; SLOT_SIZE_LEN]);
46
47impl<T> Default for SlotSizeMap<T>
48where
49    T: Default,
50{
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl<T> Index<SlotSize> for SlotSizeMap<T> {
57    type Output = T;
58    fn index(&self, index: SlotSize) -> &Self::Output {
59        self.get(index)
60    }
61}
62
63impl<T> IndexMut<SlotSize> for SlotSizeMap<T> {
64    fn index_mut(&mut self, index: SlotSize) -> &mut Self::Output {
65        self.get_mut(index)
66    }
67}
68
69impl<T> SlotSizeMap<T> {
70    fn new() -> Self
71    where
72        T: Default,
73    {
74        Self([
75            T::default(),
76            T::default(),
77            T::default(),
78            T::default(),
79            T::default(),
80        ])
81    }
82
83    fn clear(&mut self)
84    where
85        T: Default,
86    {
87        *self = Self::new();
88    }
89
90    fn get(&self, size: SlotSize) -> &T {
91        let index = size as u8 as usize;
92        &self.0[index]
93    }
94
95    fn get_mut(&mut self, size: SlotSize) -> &mut T {
96        let index = size as u8 as usize;
97        &mut self.0[index]
98    }
99}
100
101/// A set of live values.
102///
103/// Make sure to copy to a vec and sort, or something, before iterating over the
104/// values to ensure deterministic output.
105type LiveSet = HashSet<ir::Value>;
106
107/// A worklist of blocks' post-order indices that we need to process.
108#[derive(Default)]
109struct Worklist {
110    /// Stack of blocks to process.
111    stack: Vec<u32>,
112
113    /// The set of blocks that need to be updated.
114    ///
115    /// This is a subset of the elements present in `self.stack`, *not* the
116    /// exact same elements. `self.stack` is allowed to have duplicates, and
117    /// once we pop the first occurrence of a duplicate, we remove it from this
118    /// set, since it no longer needs updates at that point. This potentially
119    /// uses more stack space than necessary, but prefers processing immediate
120    /// predecessors, and therefore inner loop bodies before continuing to
121    /// process outer loop bodies. This ultimately results in fewer iterations
122    /// required to reach a fixed point.
123    need_updates: HashSet<u32>,
124}
125
126impl Extend<u32> for Worklist {
127    fn extend<T>(&mut self, iter: T)
128    where
129        T: IntoIterator<Item = u32>,
130    {
131        for block_index in iter {
132            self.push(block_index);
133        }
134    }
135}
136
137impl Worklist {
138    fn clear(&mut self) {
139        let Worklist {
140            stack,
141            need_updates,
142        } = self;
143        stack.clear();
144        need_updates.clear();
145    }
146
147    fn reserve(&mut self, capacity: usize) {
148        let Worklist {
149            stack,
150            need_updates,
151        } = self;
152        stack.reserve(capacity);
153        need_updates.reserve(capacity);
154    }
155
156    fn push(&mut self, block_index: u32) {
157        // Mark this block as needing an update. If it wasn't in `self.stack`,
158        // now it is and it needs an update. If it was already in `self.stack`,
159        // then pushing this copy logically hoists it to the top of the
160        // stack. See the above note about processing inner-most loops first.
161        self.need_updates.insert(block_index);
162        self.stack.push(block_index);
163    }
164
165    fn pop(&mut self) -> Option<u32> {
166        while let Some(block_index) = self.stack.pop() {
167            // If this block was pushed multiple times, we only need to update
168            // it once, so remove it from the need-updates set. In other words
169            // it was logically hoisted up to the top of the stack, while this
170            // entry was left behind, and we already popped the hoisted
171            // copy. See the above note about processing inner-most loops first.
172            if self.need_updates.remove(&block_index) {
173                return Some(block_index);
174            }
175        }
176        None
177    }
178}
179
180/// A simple liveness analysis.
181///
182/// This analysis is used to determine which needs-stack-map values are live
183/// across safepoint instructions.
184///
185/// This is a backwards analysis, from uses (which mark values live) to defs
186/// (which remove values from the live set) and from successor blocks to
187/// predecessor blocks.
188///
189/// We compute two live sets for each block:
190///
191/// 1. The live-in set, which is the set of values that are live when control
192///    enters the block.
193///
194/// 2. The live-out set, which is the set of values that are live when control
195///    exits the block.
196///
197/// A block's live-out set is the union of its successors' live-in sets. A
198/// block's live-in set is the set of values that are still live after the
199/// block's instructions have been processed.
200///
201/// ```text
202/// live_in(block) = union(live_out(s) for s in successors(block))
203/// live_out(block) = live_in(block) - defs(block) + uses(block)
204/// ```
205///
206/// Whenever we update a block's live-in set, we must reprocess all of its
207/// predecessors, because those predecessors' live-out sets depend on this
208/// block's live-in set. Processing continues until the live sets stop changing
209/// and we've reached a fixed-point. Each time we process a block, its live sets
210/// can only grow monotonically, and therefore we know that the computation will
211/// reach its fixed-point and terminate. This fixed-point is implemented with a
212/// classic worklist algorithm.
213///
214/// The worklist is seeded such that we initially process blocks in post-order,
215/// which ensures that, when we have a loop-free control-flow graph, we only
216/// process each block once. We pop a block off the worklist for
217/// processing. Whenever a block's live-in set is updated during processing, we
218/// push its predecessors onto the worklist so that their live-in sets can be
219/// updated. Once the worklist is empty, there are no more blocks needing
220/// updates, and we've reached the fixed-point.
221///
222/// Note: For simplicity, we do not flow liveness from block parameters back to
223/// branch arguments, and instead always consider branch arguments live.
224///
225/// Furthermore, we do not differentiate between uses of a needs-stack-map value
226/// that ultimately flow into a side-effecting operation versus uses that
227/// themselves are not live. This could be tightened up in the future, but we're
228/// starting with the easiest, simplest thing. It also means that we do not need
229/// `O(all values)` space, only `O(needs-stack-map values)`. Finally, none of
230/// our mid-end optimization passes have run at this point in time yet, so there
231/// probably isn't much, if any, dead code.
232///
233/// After we've computed the live-in and -out sets for each block, we pass once
234/// more over each block, processing its instructions again. This time, we
235/// record the precise set of needs-stack-map values that are live across each
236/// safepoint instruction inside the block, which is the final output of this
237/// analysis.
238pub(crate) struct LivenessAnalysis {
239    /// Reusable depth-first search state for traversing a function's blocks.
240    dfs: Dfs,
241
242    /// The cached post-order traversal of the function's blocks.
243    post_order: Vec<ir::Block>,
244
245    /// A secondary map from each block to its index in `post_order`.
246    block_to_index: SecondaryMap<ir::Block, u32>,
247
248    /// A mapping from each block's post-order index to the post-order indices
249    /// of its direct (non-transitive) predecessors.
250    predecessors: Vec<SmallVec<[u32; 4]>>,
251
252    /// A worklist of blocks to process. Used to determine which blocks need
253    /// updates cascaded to them and when we reach a fixed-point.
254    worklist: Worklist,
255
256    /// A map from a block's post-order index to its live-in set.
257    live_ins: Vec<LiveSet>,
258
259    /// A map from a block's post-order index to its live-out set.
260    live_outs: Vec<LiveSet>,
261
262    /// The set of each needs-stack-map value that is currently live while
263    /// processing a block.
264    currently_live: LiveSet,
265
266    /// A mapping from each safepoint instruction to the set of needs-stack-map
267    /// values that are live across it.
268    safepoints: HashMap<ir::Inst, SmallVec<[ir::Value; 4]>>,
269
270    /// The set of values that are live across *any* safepoint in the function,
271    /// i.e. the union of all the values in the `safepoints` map.
272    live_across_any_safepoint: EntitySet<ir::Value>,
273}
274
275impl Default for LivenessAnalysis {
276    fn default() -> Self {
277        Self {
278            dfs: Default::default(),
279            post_order: Default::default(),
280            block_to_index: SecondaryMap::with_default(u32::MAX),
281            predecessors: Default::default(),
282            worklist: Default::default(),
283            live_ins: Default::default(),
284            live_outs: Default::default(),
285            currently_live: Default::default(),
286            safepoints: Default::default(),
287            live_across_any_safepoint: Default::default(),
288        }
289    }
290}
291
292#[derive(Clone, Copy, PartialEq, Eq)]
293enum RecordSafepoints {
294    Yes,
295    No,
296}
297
298impl LivenessAnalysis {
299    /// Clear and reset all internal state such that this analysis is ready for
300    /// reuse with a new function.
301    pub fn clear(&mut self) {
302        let LivenessAnalysis {
303            dfs,
304            post_order,
305            block_to_index,
306            predecessors,
307            worklist,
308            live_ins,
309            live_outs,
310            currently_live,
311            safepoints,
312            live_across_any_safepoint,
313        } = self;
314        dfs.clear();
315        post_order.clear();
316        block_to_index.clear();
317        predecessors.clear();
318        worklist.clear();
319        live_ins.clear();
320        live_outs.clear();
321        currently_live.clear();
322        safepoints.clear();
323        live_across_any_safepoint.clear();
324    }
325
326    /// Given that we've initialized `self.post_order`, reserve capacity for the
327    /// various data structures we use during our analysis.
328    fn reserve_capacity(&mut self, func: &Function) {
329        let LivenessAnalysis {
330            dfs: _,
331            post_order,
332            block_to_index,
333            predecessors,
334            worklist,
335            live_ins,
336            live_outs,
337            currently_live: _,
338            safepoints: _,
339            live_across_any_safepoint: _,
340        } = self;
341
342        block_to_index.resize(func.dfg.num_blocks());
343
344        let capacity = post_order.len();
345        worklist.reserve(capacity);
346        predecessors.resize(capacity, Default::default());
347        live_ins.resize(capacity, Default::default());
348        live_outs.resize(capacity, Default::default());
349    }
350
351    fn initialize_block_to_index_map(&mut self) {
352        for (block_index, block) in self.post_order.iter().enumerate() {
353            self.block_to_index[*block] = u32::try_from(block_index).unwrap();
354        }
355    }
356
357    fn initialize_predecessors_map(&mut self, func: &Function) {
358        for (block_index, block) in self.post_order.iter().enumerate() {
359            let block_index = u32::try_from(block_index).unwrap();
360            for succ in func.block_successors(*block) {
361                let succ_index = self.block_to_index[succ];
362                debug_assert_ne!(succ_index, u32::MAX);
363                let succ_index = usize::try_from(succ_index).unwrap();
364                self.predecessors[succ_index].push(block_index);
365            }
366        }
367    }
368
369    /// Process a value's definition, removing it from the currently-live set.
370    fn process_def(&mut self, val: ir::Value) {
371        if self.currently_live.remove(&val) {
372            log::trace!("liveness:   defining {val:?}, removing it from the live set");
373        }
374    }
375
376    /// Record the live set of needs-stack-map values at the given safepoint.
377    fn record_safepoint(&mut self, func: &Function, inst: Inst) {
378        log::trace!(
379            "liveness:   found safepoint: {inst:?}: {}",
380            func.dfg.display_inst(inst)
381        );
382        log::trace!("liveness:     live set = {:?}", self.currently_live);
383
384        let mut live = self.currently_live.iter().copied().collect::<SmallVec<_>>();
385        // Keep order deterministic since we add stack map entries in this
386        // order.
387        live.sort();
388
389        self.live_across_any_safepoint.extend(live.iter().copied());
390        self.safepoints.insert(inst, live);
391    }
392
393    /// Process a use of a needs-stack-map value, inserting it into the
394    /// currently-live set.
395    fn process_use(&mut self, func: &Function, inst: Inst, val: Value) {
396        if self.currently_live.insert(val) {
397            log::trace!(
398                "liveness:   found use of {val:?}, marking it live: {inst:?}: {}",
399                func.dfg.display_inst(inst)
400            );
401        }
402    }
403
404    /// Process all the instructions in a block in reverse order.
405    fn process_block(
406        &mut self,
407        func: &mut Function,
408        stack_map_values: &EntitySet<ir::Value>,
409        block_index: usize,
410        record_safepoints: RecordSafepoints,
411    ) {
412        let block = self.post_order[block_index];
413        log::trace!("liveness: traversing {block:?}");
414
415        // Reset the currently-live set to this block's live-out set.
416        self.currently_live.clear();
417        self.currently_live
418            .extend(self.live_outs[block_index].iter().copied());
419
420        // Now process this block's instructions, incrementally building its
421        // live-in set inside the currently-live set.
422        let mut option_inst = func.layout.last_inst(block);
423        while let Some(inst) = option_inst {
424            // Process any needs-stack-map values defined by this instruction.
425            for val in func.dfg.inst_results(inst) {
426                self.process_def(*val);
427            }
428
429            // If this instruction is a safepoint and we've been asked to record
430            // safepoints, then do so.
431            let opcode = func.dfg.insts[inst].opcode();
432            if record_safepoints == RecordSafepoints::Yes && opcode.is_safepoint() {
433                self.record_safepoint(func, inst);
434            }
435
436            // Process any needs-stack-map values used by this instruction.
437            for val in func.dfg.inst_values(inst) {
438                let val = func.dfg.resolve_aliases(val);
439                if stack_map_values.contains(val) {
440                    self.process_use(func, inst, val);
441                }
442            }
443
444            option_inst = func.layout.prev_inst(inst);
445        }
446
447        // After we've processed this block's instructions, remove its
448        // parameters from the live set. This is part of step (1).
449        for val in func.dfg.block_params(block) {
450            self.process_def(*val);
451        }
452    }
453
454    /// Run the liveness analysis on the given function.
455    pub fn run(&mut self, func: &mut Function, stack_map_values: &EntitySet<ir::Value>) {
456        self.clear();
457        self.post_order.extend(self.dfs.post_order_iter(func));
458        self.reserve_capacity(func);
459        self.initialize_block_to_index_map();
460        self.initialize_predecessors_map(func);
461
462        // Initially enqueue all blocks for processing. We push them in reverse
463        // post-order (which yields them in post-order when popped) because if
464        // there are no back-edges in the control-flow graph, post-order will
465        // result in only a single pass over the blocks.
466        self.worklist
467            .extend((0..u32::try_from(self.post_order.len()).unwrap()).rev());
468
469        // Pump the worklist until we reach a fixed-point.
470        while let Some(block_index) = self.worklist.pop() {
471            let block_index = usize::try_from(block_index).unwrap();
472
473            // Because our live sets grow monotonically, we just need to see if
474            // the size changed to determine whether the whole set changed.
475            let initial_live_in_len = self.live_ins[block_index].len();
476
477            // The live-out set for a block is the union of the live-in sets of
478            // its successors.
479            for successor in func.block_successors(self.post_order[block_index]) {
480                let successor_index = self.block_to_index[successor];
481                debug_assert_ne!(successor_index, u32::MAX);
482                let successor_index = usize::try_from(successor_index).unwrap();
483                self.live_outs[block_index].extend(self.live_ins[successor_index].iter().copied());
484            }
485
486            // Process the block to compute its live-in set, but do not record
487            // safepoints yet, as we haven't yet processed loop back edges (see
488            // below).
489            self.process_block(func, stack_map_values, block_index, RecordSafepoints::No);
490
491            // The live-in set for a block is the set of values that are still
492            // live after the block's instructions have been processed.
493            self.live_ins[block_index].extend(self.currently_live.iter().copied());
494
495            // If the live-in set changed, then we need to revisit all this
496            // block's predecessors.
497            if self.live_ins[block_index].len() != initial_live_in_len {
498                self.worklist
499                    .extend(self.predecessors[block_index].iter().copied());
500            }
501        }
502
503        // Once we've reached a fixed-point, compute the actual live set for
504        // each safepoint instruction in each block, backwards from the block's
505        // live-out set.
506        for block_index in 0..self.post_order.len() {
507            self.process_block(func, stack_map_values, block_index, RecordSafepoints::Yes);
508
509            debug_assert_eq!(
510                self.currently_live, self.live_ins[block_index],
511                "when we recompute the live-in set for a block as part of \
512                 computing live sets at each safepoint, we should get the same \
513                 result we computed in the fixed-point"
514            );
515        }
516    }
517}
518
519/// A mapping from each needs-stack-map value to its associated stack slot.
520///
521/// Internally maintains free lists for stack slots that won't be used again, so
522/// that we can reuse them and minimize the number of stack slots we need to
523/// allocate.
524#[derive(Default)]
525struct StackSlots {
526    /// A mapping from each needs-stack-map value that is live across some
527    /// safepoint to the stack slot that it resides within. Note that if a
528    /// needs-stack-map value is never live across a safepoint, then we won't
529    /// ever add it to this map, it can remain in a virtual register for the
530    /// duration of its lifetime, and we won't replace all its uses with reloads
531    /// and all that stuff.
532    stack_slots: HashMap<ir::Value, ir::StackSlot>,
533
534    /// A map from slot size to free stack slots that are not being used
535    /// anymore. This allows us to reuse stack slots across multiple values
536    /// helps cut down on the ultimate size of our stack frames.
537    free_stack_slots: SlotSizeMap<SmallVec<[ir::StackSlot; 4]>>,
538}
539
540impl StackSlots {
541    fn clear(&mut self) {
542        let StackSlots {
543            stack_slots,
544            free_stack_slots,
545        } = self;
546        stack_slots.clear();
547        free_stack_slots.clear();
548    }
549
550    fn get(&self, val: ir::Value) -> Option<ir::StackSlot> {
551        self.stack_slots.get(&val).copied()
552    }
553
554    fn get_or_create_stack_slot(&mut self, func: &mut Function, val: ir::Value) -> ir::StackSlot {
555        *self.stack_slots.entry(val).or_insert_with(|| {
556            log::trace!("rewriting:     {val:?} needs a stack slot");
557            let ty = func.dfg.value_type(val);
558            let size = ty.bytes();
559            match self.free_stack_slots[SlotSize::unwrap_new(size)].pop() {
560                Some(slot) => {
561                    log::trace!("rewriting:       reusing free stack slot {slot:?} for {val:?}");
562                    slot
563                }
564                None => {
565                    debug_assert!(size.is_power_of_two());
566                    let log2_size = size.ilog2();
567                    let slot = func.create_sized_stack_slot(ir::StackSlotData::new(
568                        ir::StackSlotKind::ExplicitSlot,
569                        size,
570                        log2_size.try_into().unwrap(),
571                    ));
572                    log::trace!("rewriting:       created new stack slot {slot:?} for {val:?}");
573                    slot
574                }
575            }
576        })
577    }
578
579    fn free_stack_slot(&mut self, size: SlotSize, slot: ir::StackSlot) {
580        log::trace!("rewriting:     returning {slot:?} to the free list");
581        self.free_stack_slots[size].push(slot);
582    }
583}
584
585/// A pass to rewrite a function's instructions to spill and reload values that
586/// are live across safepoints.
587///
588/// A single `SafepointSpiller` instance may be reused to rewrite many
589/// functions, amortizing the cost of its internal allocations and avoiding
590/// repeated `malloc` and `free` calls.
591#[derive(Default)]
592pub(super) struct SafepointSpiller {
593    liveness: LivenessAnalysis,
594    stack_slots: StackSlots,
595}
596
597impl SafepointSpiller {
598    /// Clear and reset all internal state such that this pass is ready to run
599    /// on a new function.
600    pub fn clear(&mut self) {
601        let SafepointSpiller {
602            liveness,
603            stack_slots,
604        } = self;
605        liveness.clear();
606        stack_slots.clear();
607    }
608
609    /// Identify needs-stack-map values that are live across safepoints, and
610    /// rewrite the function's instructions to spill and reload them as
611    /// necessary.
612    pub fn run(&mut self, func: &mut Function, stack_map_values: &EntitySet<ir::Value>) {
613        log::trace!("values needing inclusion in stack maps: {stack_map_values:?}");
614        log::trace!(
615            "before inserting safepoint spills and reloads:\n{}",
616            func.display()
617        );
618
619        self.clear();
620        self.liveness.run(func, stack_map_values);
621        self.rewrite(func);
622
623        log::trace!(
624            "after inserting safepoint spills and reloads:\n{}",
625            func.display()
626        );
627    }
628
629    /// Spill this value to a stack slot if it has been declared that it must be
630    /// included in stack maps and is live across any safepoints.
631    ///
632    /// The given cursor must point just after this value's definition.
633    fn rewrite_def(&mut self, pos: &mut FuncCursor<'_>, val: ir::Value) {
634        if let Some(slot) = self.stack_slots.get(val) {
635            let i = pos.ins().stack_store(val, slot, 0);
636            log::trace!(
637                "rewriting:   spilling {val:?} to {slot:?}: {}",
638                pos.func.dfg.display_inst(i)
639            );
640
641            // Now that we've defined this value, there cannot be any more uses
642            // of it, and therefore this stack slot is now available for reuse.
643            let ty = pos.func.dfg.value_type(val);
644            let size = SlotSize::try_from(ty).unwrap();
645            self.stack_slots.free_stack_slot(size, slot);
646        }
647    }
648
649    /// Add a stack map entry for each needs-stack-map value that is live across
650    /// the given safepoint instruction.
651    ///
652    /// This will additionally assign stack slots to needs-stack-map values, if
653    /// no such assignment has already been made.
654    fn rewrite_safepoint(&mut self, func: &mut Function, inst: ir::Inst) {
655        log::trace!(
656            "rewriting:   found safepoint: {inst:?}: {}",
657            func.dfg.display_inst(inst)
658        );
659
660        let live = self
661            .liveness
662            .safepoints
663            .get(&inst)
664            .expect("should only call `rewrite_safepoint` on safepoint instructions");
665
666        for val in live {
667            // Get or create the stack slot for this live needs-stack-map value.
668            let slot = self.stack_slots.get_or_create_stack_slot(func, *val);
669
670            log::trace!(
671                "rewriting:     adding stack map entry for {val:?} at {slot:?}: {}",
672                func.dfg.display_inst(inst)
673            );
674            let ty = func.dfg.value_type(*val);
675            func.dfg.append_user_stack_map_entry(
676                inst,
677                ir::UserStackMapEntry {
678                    ty,
679                    slot,
680                    offset: 0,
681                },
682            );
683        }
684    }
685
686    /// If `val` is a needs-stack-map value that has been spilled to a stack
687    /// slot, then rewrite `val` to be a load from its associated stack
688    /// slot.
689    ///
690    /// Returns `true` if `val` was rewritten, `false` if not.
691    ///
692    /// The given cursor must point just before the use of the value that we are
693    /// replacing.
694    fn rewrite_use(&mut self, pos: &mut FuncCursor<'_>, val: &mut ir::Value) -> bool {
695        if !self.liveness.live_across_any_safepoint.contains(*val) {
696            return false;
697        }
698
699        let old_val = *val;
700        log::trace!("rewriting:     found use of {old_val:?}");
701
702        let ty = pos.func.dfg.value_type(*val);
703        let slot = self.stack_slots.get_or_create_stack_slot(pos.func, *val);
704        *val = pos.ins().stack_load(ty, slot, 0);
705
706        log::trace!(
707            "rewriting:     reloading {old_val:?}: {}",
708            pos.func
709                .dfg
710                .display_inst(pos.func.dfg.value_def(*val).unwrap_inst())
711        );
712
713        true
714    }
715
716    /// Rewrite the function's instructions to spill and reload values that are
717    /// live across safepoints:
718    ///
719    /// 1. Definitions of needs-stack-map values that are live across some
720    ///    safepoint need to be spilled to their assigned stack slot.
721    ///
722    /// 2. Instructions that are themselves safepoints must have stack map
723    ///    entries added for the needs-stack-map values that are live across
724    ///    them.
725    ///
726    /// 3. Uses of needs-stack-map values that have been spilled to a stack slot
727    ///    need to be replaced with reloads from the slot.
728    fn rewrite(&mut self, func: &mut Function) {
729        // Shared temporary storage for operand and result lists.
730        let mut vals: SmallVec<[_; 8]> = Default::default();
731
732        // Rewrite the function's instructions in post-order. This ensures that
733        // we rewrite uses before defs, and therefore once we see a def we know
734        // its stack slot will never be used for that value again. Therefore,
735        // the slot can be reappropriated for a new needs-stack-map value with a
736        // non-overlapping live range. See `rewrite_def` and `free_stack_slots`
737        // for more details.
738        for block_index in 0..self.liveness.post_order.len() {
739            let block = self.liveness.post_order[block_index];
740            log::trace!("rewriting: processing {block:?}");
741
742            let mut option_inst = func.layout.last_inst(block);
743            while let Some(inst) = option_inst {
744                // If this instruction defines a needs-stack-map value that is
745                // live across a safepoint, then spill the value to its stack
746                // slot.
747                let mut pos = FuncCursor::new(func).after_inst(inst);
748                vals.extend_from_slice(pos.func.dfg.inst_results(inst));
749                for val in vals.drain(..) {
750                    self.rewrite_def(&mut pos, val);
751                }
752
753                // If this instruction is a safepoint, then we must add stack
754                // map entries for the needs-stack-map values that are live
755                // across it.
756                if self.liveness.safepoints.contains_key(&inst) {
757                    self.rewrite_safepoint(func, inst);
758                }
759
760                // Replace all uses of needs-stack-map values with loads from
761                // the value's associated stack slot.
762                let mut pos = FuncCursor::new(func).at_inst(inst);
763                vals.extend(pos.func.dfg.inst_values(inst));
764                let mut replaced_any = false;
765                for val in &mut vals {
766                    replaced_any |= self.rewrite_use(&mut pos, val);
767                }
768                if replaced_any {
769                    pos.func.dfg.overwrite_inst_values(inst, vals.drain(..));
770                    log::trace!(
771                        "rewriting:     updated {inst:?} operands with reloaded values: {}",
772                        pos.func.dfg.display_inst(inst)
773                    );
774                } else {
775                    vals.clear();
776                }
777
778                option_inst = func.layout.prev_inst(inst);
779            }
780
781            // Spill needs-stack-map values defined by block parameters to their
782            // associated stack slots.
783            let mut pos = FuncCursor::new(func).at_position(CursorPosition::Before(block));
784            pos.next_inst(); // Advance to the first instruction in the block.
785            vals.clear();
786            vals.extend_from_slice(pos.func.dfg.block_params(block));
787            for val in vals.drain(..) {
788                self.rewrite_def(&mut pos, val);
789            }
790        }
791    }
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797    use alloc::string::ToString;
798    use cranelift_codegen::isa::CallConv;
799
800    #[test]
801    fn needs_stack_map_and_loop() {
802        let mut sig = Signature::new(CallConv::SystemV);
803        sig.params.push(AbiParam::new(ir::types::I32));
804        sig.params.push(AbiParam::new(ir::types::I32));
805
806        let mut fn_ctx = FunctionBuilderContext::new();
807        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
808        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
809
810        let name = builder
811            .func
812            .declare_imported_user_function(ir::UserExternalName {
813                namespace: 0,
814                index: 0,
815            });
816        let mut sig = Signature::new(CallConv::SystemV);
817        sig.params.push(AbiParam::new(ir::types::I32));
818        let signature = builder.func.import_signature(sig);
819        let func_ref = builder.import_function(ir::ExtFuncData {
820            name: ir::ExternalName::user(name),
821            signature,
822            colocated: true,
823            patchable: false,
824        });
825
826        // Here the value `v1` is technically not live but our single-pass liveness
827        // analysis treats every branch argument to a block as live to avoid
828        // needing to do a fixed-point loop.
829        //
830        //     block0(v0, v1):
831        //       call $foo(v0)
832        //       jump block0(v0, v1)
833        let block0 = builder.create_block();
834        builder.append_block_params_for_function_params(block0);
835        let a = builder.func.dfg.block_params(block0)[0];
836        let b = builder.func.dfg.block_params(block0)[1];
837        builder.declare_value_needs_stack_map(a);
838        builder.declare_value_needs_stack_map(b);
839        builder.switch_to_block(block0);
840        builder.ins().call(func_ref, &[a]);
841        builder.ins().jump(block0, &[a.into(), b.into()]);
842        builder.seal_all_blocks();
843        builder.finalize();
844
845        assert_eq_output!(
846            func.display().to_string(),
847            r#"
848function %sample(i32, i32) system_v {
849    ss0 = explicit_slot 4, align = 4
850    ss1 = explicit_slot 4, align = 4
851    sig0 = (i32) system_v
852    fn0 = colocated u0:0 sig0
853
854block0(v0: i32, v1: i32):
855    stack_store v0, ss0
856    stack_store v1, ss1
857    v4 = stack_load.i32 ss0
858    call fn0(v4), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
859    v2 = stack_load.i32 ss0
860    v3 = stack_load.i32 ss1
861    jump block0(v2, v3)
862}
863            "#
864        );
865    }
866
867    #[test]
868    fn needs_stack_map_simple() {
869        let _ = env_logger::try_init();
870
871        let sig = Signature::new(CallConv::SystemV);
872
873        let mut fn_ctx = FunctionBuilderContext::new();
874        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
875        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
876
877        let name = builder
878            .func
879            .declare_imported_user_function(ir::UserExternalName {
880                namespace: 0,
881                index: 0,
882            });
883        let mut sig = Signature::new(CallConv::SystemV);
884        sig.params.push(AbiParam::new(ir::types::I32));
885        let signature = builder.func.import_signature(sig);
886        let func_ref = builder.import_function(ir::ExtFuncData {
887            name: ir::ExternalName::user(name),
888            signature,
889            colocated: true,
890            patchable: false,
891        });
892
893        // At each `call` we are losing one more value as no longer live, so
894        // each stack map should be one smaller than the last. `v3` is never
895        // live across a safepoint, so should never appear in a stack map. Note
896        // that a value that is an argument to the call, but is not live after
897        // the call, should not appear in the stack map. This is why `v0`
898        // appears in the first call's stack map, but not the second call's
899        // stack map.
900        //
901        //     block0:
902        //       v0 = needs stack map
903        //       v1 = needs stack map
904        //       v2 = needs stack map
905        //       v3 = needs stack map
906        //       call $foo(v3)
907        //       call $foo(v0)
908        //       call $foo(v1)
909        //       call $foo(v2)
910        //       return
911        let block0 = builder.create_block();
912        builder.append_block_params_for_function_params(block0);
913        builder.switch_to_block(block0);
914        let v0 = builder.ins().iconst(ir::types::I32, 0);
915        builder.declare_value_needs_stack_map(v0);
916        let v1 = builder.ins().iconst(ir::types::I32, 1);
917        builder.declare_value_needs_stack_map(v1);
918        let v2 = builder.ins().iconst(ir::types::I32, 2);
919        builder.declare_value_needs_stack_map(v2);
920        let v3 = builder.ins().iconst(ir::types::I32, 3);
921        builder.declare_value_needs_stack_map(v3);
922        builder.ins().call(func_ref, &[v3]);
923        builder.ins().call(func_ref, &[v0]);
924        builder.ins().call(func_ref, &[v1]);
925        builder.ins().call(func_ref, &[v2]);
926        builder.ins().return_(&[]);
927        builder.seal_all_blocks();
928        builder.finalize();
929
930        assert_eq_output!(
931            func.display().to_string(),
932            r#"
933function %sample() system_v {
934    ss0 = explicit_slot 4, align = 4
935    ss1 = explicit_slot 4, align = 4
936    ss2 = explicit_slot 4, align = 4
937    sig0 = (i32) system_v
938    fn0 = colocated u0:0 sig0
939
940block0:
941    v0 = iconst.i32 0
942    stack_store v0, ss2  ; v0 = 0
943    v1 = iconst.i32 1
944    stack_store v1, ss1  ; v1 = 1
945    v2 = iconst.i32 2
946    stack_store v2, ss0  ; v2 = 2
947    v3 = iconst.i32 3
948    call fn0(v3), stack_map=[i32 @ ss2+0, i32 @ ss1+0, i32 @ ss0+0]  ; v3 = 3
949    v6 = stack_load.i32 ss2
950    call fn0(v6), stack_map=[i32 @ ss1+0, i32 @ ss0+0]
951    v5 = stack_load.i32 ss1
952    call fn0(v5), stack_map=[i32 @ ss0+0]
953    v4 = stack_load.i32 ss0
954    call fn0(v4)
955    return
956}
957            "#
958        );
959    }
960
961    #[test]
962    fn needs_stack_map_and_post_order_early_return() {
963        let mut sig = Signature::new(CallConv::SystemV);
964        sig.params.push(AbiParam::new(ir::types::I32));
965
966        let mut fn_ctx = FunctionBuilderContext::new();
967        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
968        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
969
970        let name = builder
971            .func
972            .declare_imported_user_function(ir::UserExternalName {
973                namespace: 0,
974                index: 0,
975            });
976        let signature = builder
977            .func
978            .import_signature(Signature::new(CallConv::SystemV));
979        let func_ref = builder.import_function(ir::ExtFuncData {
980            name: ir::ExternalName::user(name),
981            signature,
982            colocated: true,
983            patchable: false,
984        });
985
986        // Here we rely on the post-order to make sure that we never visit block
987        // 4 and add `v1` to our live set, then visit block 2 and add `v1` to
988        // its stack map even though `v1` is not in scope. Thanksfully, that
989        // sequence is impossible because it would be an invalid post-order
990        // traversal. The only valid post-order traversals are [3, 1, 2, 0] and
991        // [2, 3, 1, 0].
992        //
993        //     block0(v0):
994        //       brif v0, block1, block2
995        //
996        //     block1:
997        //       <stuff>
998        //       v1 = get some gc ref
999        //       jump block3
1000        //
1001        //     block2:
1002        //       call $needs_safepoint_accidentally
1003        //       return
1004        //
1005        //     block3:
1006        //       stuff keeping v1 live
1007        //       return
1008        let block0 = builder.create_block();
1009        let block1 = builder.create_block();
1010        let block2 = builder.create_block();
1011        let block3 = builder.create_block();
1012        builder.append_block_params_for_function_params(block0);
1013
1014        builder.switch_to_block(block0);
1015        let v0 = builder.func.dfg.block_params(block0)[0];
1016        builder.ins().brif(v0, block1, &[], block2, &[]);
1017
1018        builder.switch_to_block(block1);
1019        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1020        builder.declare_value_needs_stack_map(v1);
1021        builder.ins().jump(block3, &[]);
1022
1023        builder.switch_to_block(block2);
1024        builder.ins().call(func_ref, &[]);
1025        builder.ins().return_(&[]);
1026
1027        builder.switch_to_block(block3);
1028        // NB: Our simplistic liveness analysis conservatively treats any use of
1029        // a value as keeping it live, regardless if the use has side effects or
1030        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1031        // here.
1032        builder.ins().iadd_imm(v1, 0);
1033        builder.ins().return_(&[]);
1034
1035        builder.seal_all_blocks();
1036        builder.finalize();
1037
1038        assert_eq_output!(
1039            func.display().to_string(),
1040            r#"
1041function %sample(i32) system_v {
1042    sig0 = () system_v
1043    fn0 = colocated u0:0 sig0
1044
1045block0(v0: i32):
1046    brif v0, block1, block2
1047
1048block1:
1049    v1 = iconst.i64 0x1234_5678
1050    jump block3
1051
1052block2:
1053    call fn0()
1054    return
1055
1056block3:
1057    v2 = iadd_imm.i64 v1, 0  ; v1 = 0x1234_5678
1058    return
1059}
1060            "#
1061        );
1062    }
1063
1064    #[test]
1065    fn needs_stack_map_conditional_branches_and_liveness() {
1066        let mut sig = Signature::new(CallConv::SystemV);
1067        sig.params.push(AbiParam::new(ir::types::I32));
1068
1069        let mut fn_ctx = FunctionBuilderContext::new();
1070        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1071        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1072
1073        let name = builder
1074            .func
1075            .declare_imported_user_function(ir::UserExternalName {
1076                namespace: 0,
1077                index: 0,
1078            });
1079        let signature = builder
1080            .func
1081            .import_signature(Signature::new(CallConv::SystemV));
1082        let func_ref = builder.import_function(ir::ExtFuncData {
1083            name: ir::ExternalName::user(name),
1084            signature,
1085            colocated: true,
1086            patchable: false,
1087        });
1088
1089        // We should not have a stack map entry for `v1` in block 1 because it
1090        // is not live across the call.
1091        //
1092        //     block0(v0):
1093        //       v1 = needs stack map
1094        //       brif v0, block1, block2
1095        //
1096        //     block1:
1097        //       call $foo()
1098        //       return
1099        //
1100        //     block2:
1101        //       keep v1 alive
1102        //       return
1103        let block0 = builder.create_block();
1104        let block1 = builder.create_block();
1105        let block2 = builder.create_block();
1106        builder.append_block_params_for_function_params(block0);
1107
1108        builder.switch_to_block(block0);
1109        let v0 = builder.func.dfg.block_params(block0)[0];
1110        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1111        builder.declare_value_needs_stack_map(v1);
1112        builder.ins().brif(v0, block1, &[], block2, &[]);
1113
1114        builder.switch_to_block(block1);
1115        builder.ins().call(func_ref, &[]);
1116        builder.ins().return_(&[]);
1117
1118        builder.switch_to_block(block2);
1119        // NB: Our simplistic liveness analysis conservatively treats any use of
1120        // a value as keeping it live, regardless if the use has side effects or
1121        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1122        // here.
1123        builder.ins().iadd_imm(v1, 0);
1124        builder.ins().return_(&[]);
1125
1126        builder.seal_all_blocks();
1127        builder.finalize();
1128
1129        assert_eq_output!(
1130            func.display().to_string(),
1131            r#"
1132function %sample(i32) system_v {
1133    sig0 = () system_v
1134    fn0 = colocated u0:0 sig0
1135
1136block0(v0: i32):
1137    v1 = iconst.i64 0x1234_5678
1138    brif v0, block1, block2
1139
1140block1:
1141    call fn0()
1142    return
1143
1144block2:
1145    v2 = iadd_imm.i64 v1, 0  ; v1 = 0x1234_5678
1146    return
1147}
1148            "#
1149        );
1150
1151        // Now do the same test but with block 1 and 2 swapped so that we
1152        // exercise what we are trying to exercise, regardless of which
1153        // post-order traversal we happen to take.
1154        func.clear();
1155        fn_ctx.clear();
1156
1157        let mut sig = Signature::new(CallConv::SystemV);
1158        sig.params.push(AbiParam::new(ir::types::I32));
1159
1160        func.signature = sig;
1161        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1162
1163        let name = builder
1164            .func
1165            .declare_imported_user_function(ir::UserExternalName {
1166                namespace: 0,
1167                index: 0,
1168            });
1169        let signature = builder
1170            .func
1171            .import_signature(Signature::new(CallConv::SystemV));
1172        let func_ref = builder.import_function(ir::ExtFuncData {
1173            name: ir::ExternalName::user(name),
1174            signature,
1175            colocated: true,
1176            patchable: false,
1177        });
1178
1179        let block0 = builder.create_block();
1180        let block1 = builder.create_block();
1181        let block2 = builder.create_block();
1182        builder.append_block_params_for_function_params(block0);
1183
1184        builder.switch_to_block(block0);
1185        let v0 = builder.func.dfg.block_params(block0)[0];
1186        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1187        builder.declare_value_needs_stack_map(v1);
1188        builder.ins().brif(v0, block1, &[], block2, &[]);
1189
1190        builder.switch_to_block(block1);
1191        builder.ins().iadd_imm(v1, 0);
1192        builder.ins().return_(&[]);
1193
1194        builder.switch_to_block(block2);
1195        builder.ins().call(func_ref, &[]);
1196        builder.ins().return_(&[]);
1197
1198        builder.seal_all_blocks();
1199        builder.finalize();
1200
1201        assert_eq_output!(
1202            func.display().to_string(),
1203            r#"
1204function u0:0(i32) system_v {
1205    sig0 = () system_v
1206    fn0 = colocated u0:0 sig0
1207
1208block0(v0: i32):
1209    v1 = iconst.i64 0x1234_5678
1210    brif v0, block1, block2
1211
1212block1:
1213    v2 = iadd_imm.i64 v1, 0  ; v1 = 0x1234_5678
1214    return
1215
1216block2:
1217    call fn0()
1218    return
1219}
1220            "#
1221        );
1222    }
1223
1224    #[test]
1225    fn needs_stack_map_and_tail_calls() {
1226        let mut sig = Signature::new(CallConv::SystemV);
1227        sig.params.push(AbiParam::new(ir::types::I32));
1228
1229        let mut fn_ctx = FunctionBuilderContext::new();
1230        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1231        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1232
1233        let name = builder
1234            .func
1235            .declare_imported_user_function(ir::UserExternalName {
1236                namespace: 0,
1237                index: 0,
1238            });
1239        let signature = builder
1240            .func
1241            .import_signature(Signature::new(CallConv::SystemV));
1242        let func_ref = builder.import_function(ir::ExtFuncData {
1243            name: ir::ExternalName::user(name),
1244            signature,
1245            colocated: true,
1246            patchable: false,
1247        });
1248
1249        // Depending on which post-order traversal we take, we might consider
1250        // `v1` live inside `block1`. But nothing is live after a tail call so
1251        // we shouldn't spill `v1` either way here.
1252        //
1253        //     block0(v0):
1254        //       v1 = needs stack map
1255        //       brif v0, block1, block2
1256        //
1257        //     block1:
1258        //       return_call $foo()
1259        //
1260        //     block2:
1261        //       keep v1 alive
1262        //       return
1263        let block0 = builder.create_block();
1264        let block1 = builder.create_block();
1265        let block2 = builder.create_block();
1266        builder.append_block_params_for_function_params(block0);
1267
1268        builder.switch_to_block(block0);
1269        let v0 = builder.func.dfg.block_params(block0)[0];
1270        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1271        builder.declare_value_needs_stack_map(v1);
1272        builder.ins().brif(v0, block1, &[], block2, &[]);
1273
1274        builder.switch_to_block(block1);
1275        builder.ins().return_call(func_ref, &[]);
1276
1277        builder.switch_to_block(block2);
1278        // NB: Our simplistic liveness analysis conservatively treats any use of
1279        // a value as keeping it live, regardless if the use has side effects or
1280        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1281        // here.
1282        builder.ins().iadd_imm(v1, 0);
1283        builder.ins().return_(&[]);
1284
1285        builder.seal_all_blocks();
1286        builder.finalize();
1287
1288        assert_eq_output!(
1289            func.display().to_string(),
1290            r#"
1291function %sample(i32) system_v {
1292    sig0 = () system_v
1293    fn0 = colocated u0:0 sig0
1294
1295block0(v0: i32):
1296    v1 = iconst.i64 0x1234_5678
1297    brif v0, block1, block2
1298
1299block1:
1300    return_call fn0()
1301
1302block2:
1303    v2 = iadd_imm.i64 v1, 0  ; v1 = 0x1234_5678
1304    return
1305}
1306            "#
1307        );
1308
1309        // Do the same test but with block 1 and 2 swapped so that we exercise
1310        // what we are trying to exercise, regardless of which post-order
1311        // traversal we happen to take.
1312        func.clear();
1313        fn_ctx.clear();
1314
1315        let mut sig = Signature::new(CallConv::SystemV);
1316        sig.params.push(AbiParam::new(ir::types::I32));
1317        func.signature = sig;
1318
1319        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1320
1321        let name = builder
1322            .func
1323            .declare_imported_user_function(ir::UserExternalName {
1324                namespace: 0,
1325                index: 0,
1326            });
1327        let signature = builder
1328            .func
1329            .import_signature(Signature::new(CallConv::SystemV));
1330        let func_ref = builder.import_function(ir::ExtFuncData {
1331            name: ir::ExternalName::user(name),
1332            signature,
1333            colocated: true,
1334            patchable: false,
1335        });
1336
1337        let block0 = builder.create_block();
1338        let block1 = builder.create_block();
1339        let block2 = builder.create_block();
1340        builder.append_block_params_for_function_params(block0);
1341
1342        builder.switch_to_block(block0);
1343        let v0 = builder.func.dfg.block_params(block0)[0];
1344        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1345        builder.declare_value_needs_stack_map(v1);
1346        builder.ins().brif(v0, block1, &[], block2, &[]);
1347
1348        builder.switch_to_block(block1);
1349        builder.ins().iadd_imm(v1, 0);
1350        builder.ins().return_(&[]);
1351
1352        builder.switch_to_block(block2);
1353        builder.ins().return_call(func_ref, &[]);
1354
1355        builder.seal_all_blocks();
1356        builder.finalize();
1357
1358        assert_eq_output!(
1359            func.display().to_string(),
1360            r#"
1361function u0:0(i32) system_v {
1362    sig0 = () system_v
1363    fn0 = colocated u0:0 sig0
1364
1365block0(v0: i32):
1366    v1 = iconst.i64 0x1234_5678
1367    brif v0, block1, block2
1368
1369block1:
1370    v2 = iadd_imm.i64 v1, 0  ; v1 = 0x1234_5678
1371    return
1372
1373block2:
1374    return_call fn0()
1375}
1376            "#
1377        );
1378    }
1379
1380    #[test]
1381    fn needs_stack_map_and_cfg_diamond() {
1382        let mut sig = Signature::new(CallConv::SystemV);
1383        sig.params.push(AbiParam::new(ir::types::I32));
1384
1385        let mut fn_ctx = FunctionBuilderContext::new();
1386        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1387        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1388
1389        let name = builder
1390            .func
1391            .declare_imported_user_function(ir::UserExternalName {
1392                namespace: 0,
1393                index: 0,
1394            });
1395        let signature = builder
1396            .func
1397            .import_signature(Signature::new(CallConv::SystemV));
1398        let func_ref = builder.import_function(ir::ExtFuncData {
1399            name: ir::ExternalName::user(name),
1400            signature,
1401            colocated: true,
1402            patchable: false,
1403        });
1404
1405        // Create an if/else CFG diamond that and check that various things get
1406        // spilled as needed.
1407        //
1408        //     block0(v0):
1409        //       brif v0, block1, block2
1410        //
1411        //     block1:
1412        //       v1 = needs stack map
1413        //       v2 = needs stack map
1414        //       call $foo()
1415        //       jump block3(v1, v2)
1416        //
1417        //     block2:
1418        //       v3 = needs stack map
1419        //       v4 = needs stack map
1420        //       call $foo()
1421        //       jump block3(v3, v3)  ;; Note: v4 is not live
1422        //
1423        //     block3(v5, v6):
1424        //       call $foo()
1425        //       keep v5 alive, but not v6
1426        let block0 = builder.create_block();
1427        let block1 = builder.create_block();
1428        let block2 = builder.create_block();
1429        let block3 = builder.create_block();
1430        builder.append_block_params_for_function_params(block0);
1431
1432        builder.switch_to_block(block0);
1433        let v0 = builder.func.dfg.block_params(block0)[0];
1434        builder.ins().brif(v0, block1, &[], block2, &[]);
1435
1436        builder.switch_to_block(block1);
1437        let v1 = builder.ins().iconst(ir::types::I64, 1);
1438        builder.declare_value_needs_stack_map(v1);
1439        let v2 = builder.ins().iconst(ir::types::I64, 2);
1440        builder.declare_value_needs_stack_map(v2);
1441        builder.ins().call(func_ref, &[]);
1442        builder.ins().jump(block3, &[v1.into(), v2.into()]);
1443
1444        builder.switch_to_block(block2);
1445        let v3 = builder.ins().iconst(ir::types::I64, 3);
1446        builder.declare_value_needs_stack_map(v3);
1447        let v4 = builder.ins().iconst(ir::types::I64, 4);
1448        builder.declare_value_needs_stack_map(v4);
1449        builder.ins().call(func_ref, &[]);
1450        builder.ins().jump(block3, &[v3.into(), v3.into()]);
1451
1452        builder.switch_to_block(block3);
1453        builder.append_block_param(block3, ir::types::I64);
1454        builder.append_block_param(block3, ir::types::I64);
1455        builder.ins().call(func_ref, &[]);
1456        // NB: Our simplistic liveness analysis conservatively treats any use of
1457        // a value as keeping it live, regardless if the use has side effects or
1458        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1459        // here.
1460        builder.ins().iadd_imm(v1, 0);
1461        builder.ins().return_(&[]);
1462
1463        builder.seal_all_blocks();
1464        builder.finalize();
1465
1466        assert_eq_output!(
1467            func.display().to_string(),
1468            r#"
1469function %sample(i32) system_v {
1470    ss0 = explicit_slot 8, align = 8
1471    ss1 = explicit_slot 8, align = 8
1472    sig0 = () system_v
1473    fn0 = colocated u0:0 sig0
1474
1475block0(v0: i32):
1476    brif v0, block1, block2
1477
1478block1:
1479    v1 = iconst.i64 1
1480    stack_store v1, ss0  ; v1 = 1
1481    v2 = iconst.i64 2
1482    stack_store v2, ss1  ; v2 = 2
1483    call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss1+0]
1484    v9 = stack_load.i64 ss0
1485    v10 = stack_load.i64 ss1
1486    jump block3(v9, v10)
1487
1488block2:
1489    v3 = iconst.i64 3
1490    stack_store v3, ss0  ; v3 = 3
1491    v4 = iconst.i64 4
1492    call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss0+0]
1493    v11 = stack_load.i64 ss0
1494    v12 = stack_load.i64 ss0
1495    jump block3(v11, v12)
1496
1497block3(v5: i64, v6: i64):
1498    call fn0(), stack_map=[i64 @ ss0+0]
1499    v8 = stack_load.i64 ss0
1500    v7 = iadd_imm v8, 0
1501    return
1502}
1503            "#
1504        );
1505    }
1506
1507    #[test]
1508    fn needs_stack_map_and_heterogeneous_types() {
1509        let _ = env_logger::try_init();
1510
1511        let mut sig = Signature::new(CallConv::SystemV);
1512        for ty in [
1513            ir::types::I8,
1514            ir::types::I16,
1515            ir::types::I32,
1516            ir::types::I64,
1517            ir::types::I128,
1518            ir::types::F32,
1519            ir::types::F64,
1520            ir::types::I8X16,
1521            ir::types::I16X8,
1522        ] {
1523            sig.params.push(AbiParam::new(ty));
1524            sig.returns.push(AbiParam::new(ty));
1525        }
1526
1527        let mut fn_ctx = FunctionBuilderContext::new();
1528        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1529        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1530
1531        let name = builder
1532            .func
1533            .declare_imported_user_function(ir::UserExternalName {
1534                namespace: 0,
1535                index: 0,
1536            });
1537        let signature = builder
1538            .func
1539            .import_signature(Signature::new(CallConv::SystemV));
1540        let func_ref = builder.import_function(ir::ExtFuncData {
1541            name: ir::ExternalName::user(name),
1542            signature,
1543            colocated: true,
1544            patchable: false,
1545        });
1546
1547        // Test that we support stack maps of heterogeneous types and properly
1548        // coalesce types into stack slots based on their size.
1549        //
1550        //     block0(v0, v1, v2, v3, v4, v5, v6, v7, v8):
1551        //       call $foo()
1552        //       return v0, v1, v2, v3, v4, v5, v6, v7, v8
1553        let block0 = builder.create_block();
1554        builder.append_block_params_for_function_params(block0);
1555
1556        builder.switch_to_block(block0);
1557        let params = builder.func.dfg.block_params(block0).to_vec();
1558        for val in &params {
1559            builder.declare_value_needs_stack_map(*val);
1560        }
1561        builder.ins().call(func_ref, &[]);
1562        builder.ins().return_(&params);
1563
1564        builder.seal_all_blocks();
1565        builder.finalize();
1566
1567        assert_eq_output!(
1568            func.display().to_string(),
1569            r#"
1570function %sample(i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8) -> i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8 system_v {
1571    ss0 = explicit_slot 1
1572    ss1 = explicit_slot 2, align = 2
1573    ss2 = explicit_slot 4, align = 4
1574    ss3 = explicit_slot 8, align = 8
1575    ss4 = explicit_slot 16, align = 16
1576    ss5 = explicit_slot 4, align = 4
1577    ss6 = explicit_slot 8, align = 8
1578    ss7 = explicit_slot 16, align = 16
1579    ss8 = explicit_slot 16, align = 16
1580    sig0 = () system_v
1581    fn0 = colocated u0:0 sig0
1582
1583block0(v0: i8, v1: i16, v2: i32, v3: i64, v4: i128, v5: f32, v6: f64, v7: i8x16, v8: i16x8):
1584    stack_store v0, ss0
1585    stack_store v1, ss1
1586    stack_store v2, ss2
1587    stack_store v3, ss3
1588    stack_store v4, ss4
1589    stack_store v5, ss5
1590    stack_store v6, ss6
1591    stack_store v7, ss7
1592    stack_store v8, ss8
1593    call fn0(), stack_map=[i8 @ ss0+0, i16 @ ss1+0, i32 @ ss2+0, i64 @ ss3+0, i128 @ ss4+0, f32 @ ss5+0, f64 @ ss6+0, i8x16 @ ss7+0, i16x8 @ ss8+0]
1594    v9 = stack_load.i8 ss0
1595    v10 = stack_load.i16 ss1
1596    v11 = stack_load.i32 ss2
1597    v12 = stack_load.i64 ss3
1598    v13 = stack_load.i128 ss4
1599    v14 = stack_load.f32 ss5
1600    v15 = stack_load.f64 ss6
1601    v16 = stack_load.i8x16 ss7
1602    v17 = stack_load.i16x8 ss8
1603    return v9, v10, v11, v12, v13, v14, v15, v16, v17
1604}
1605            "#
1606        );
1607    }
1608
1609    #[test]
1610    fn series_of_non_overlapping_live_ranges_needs_stack_map() {
1611        let sig = Signature::new(CallConv::SystemV);
1612
1613        let mut fn_ctx = FunctionBuilderContext::new();
1614        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1615        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1616
1617        let name = builder
1618            .func
1619            .declare_imported_user_function(ir::UserExternalName {
1620                namespace: 0,
1621                index: 0,
1622            });
1623        let signature = builder
1624            .func
1625            .import_signature(Signature::new(CallConv::SystemV));
1626        let foo_func_ref = builder.import_function(ir::ExtFuncData {
1627            name: ir::ExternalName::user(name),
1628            signature,
1629            colocated: true,
1630            patchable: false,
1631        });
1632
1633        let name = builder
1634            .func
1635            .declare_imported_user_function(ir::UserExternalName {
1636                namespace: 0,
1637                index: 1,
1638            });
1639        let mut sig = Signature::new(CallConv::SystemV);
1640        sig.params.push(AbiParam::new(ir::types::I32));
1641        let signature = builder.func.import_signature(sig);
1642        let consume_func_ref = builder.import_function(ir::ExtFuncData {
1643            name: ir::ExternalName::user(name),
1644            signature,
1645            colocated: true,
1646            patchable: false,
1647        });
1648
1649        // Create a series of needs-stack-map values that do not have
1650        // overlapping live ranges, but which do appear in stack maps for calls
1651        // to `$foo`:
1652        //
1653        //     block0:
1654        //       v0 = needs stack map
1655        //       call $foo()
1656        //       call consume(v0)
1657        //       v1 = needs stack map
1658        //       call $foo()
1659        //       call consume(v1)
1660        //       v2 = needs stack map
1661        //       call $foo()
1662        //       call consume(v2)
1663        //       v3 = needs stack map
1664        //       call $foo()
1665        //       call consume(v3)
1666        //       return
1667        let block0 = builder.create_block();
1668        builder.append_block_params_for_function_params(block0);
1669        builder.switch_to_block(block0);
1670        let v0 = builder.ins().iconst(ir::types::I32, 0);
1671        builder.declare_value_needs_stack_map(v0);
1672        builder.ins().call(foo_func_ref, &[]);
1673        builder.ins().call(consume_func_ref, &[v0]);
1674        let v1 = builder.ins().iconst(ir::types::I32, 1);
1675        builder.declare_value_needs_stack_map(v1);
1676        builder.ins().call(foo_func_ref, &[]);
1677        builder.ins().call(consume_func_ref, &[v1]);
1678        let v2 = builder.ins().iconst(ir::types::I32, 2);
1679        builder.declare_value_needs_stack_map(v2);
1680        builder.ins().call(foo_func_ref, &[]);
1681        builder.ins().call(consume_func_ref, &[v2]);
1682        let v3 = builder.ins().iconst(ir::types::I32, 3);
1683        builder.declare_value_needs_stack_map(v3);
1684        builder.ins().call(foo_func_ref, &[]);
1685        builder.ins().call(consume_func_ref, &[v3]);
1686        builder.ins().return_(&[]);
1687        builder.seal_all_blocks();
1688        builder.finalize();
1689
1690        assert_eq_output!(
1691            func.display().to_string(),
1692            r#"
1693function %sample() system_v {
1694    ss0 = explicit_slot 4, align = 4
1695    sig0 = () system_v
1696    sig1 = (i32) system_v
1697    fn0 = colocated u0:0 sig0
1698    fn1 = colocated u0:1 sig1
1699
1700block0:
1701    v0 = iconst.i32 0
1702    stack_store v0, ss0  ; v0 = 0
1703    call fn0(), stack_map=[i32 @ ss0+0]
1704    v7 = stack_load.i32 ss0
1705    call fn1(v7)
1706    v1 = iconst.i32 1
1707    stack_store v1, ss0  ; v1 = 1
1708    call fn0(), stack_map=[i32 @ ss0+0]
1709    v6 = stack_load.i32 ss0
1710    call fn1(v6)
1711    v2 = iconst.i32 2
1712    stack_store v2, ss0  ; v2 = 2
1713    call fn0(), stack_map=[i32 @ ss0+0]
1714    v5 = stack_load.i32 ss0
1715    call fn1(v5)
1716    v3 = iconst.i32 3
1717    stack_store v3, ss0  ; v3 = 3
1718    call fn0(), stack_map=[i32 @ ss0+0]
1719    v4 = stack_load.i32 ss0
1720    call fn1(v4)
1721    return
1722}
1723            "#
1724        );
1725    }
1726
1727    #[test]
1728    fn vars_block_params_and_needs_stack_map() {
1729        let _ = env_logger::try_init();
1730
1731        let mut sig = Signature::new(CallConv::SystemV);
1732        sig.params.push(AbiParam::new(ir::types::I32));
1733        sig.returns.push(AbiParam::new(ir::types::I32));
1734
1735        let mut fn_ctx = FunctionBuilderContext::new();
1736        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1737        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1738
1739        let name = builder
1740            .func
1741            .declare_imported_user_function(ir::UserExternalName {
1742                namespace: 0,
1743                index: 0,
1744            });
1745        let mut sig = Signature::new(CallConv::SystemV);
1746        sig.params.push(AbiParam::new(ir::types::I32));
1747        let signature = builder.func.import_signature(sig);
1748        let func_ref = builder.import_function(ir::ExtFuncData {
1749            name: ir::ExternalName::user(name),
1750            signature,
1751            colocated: true,
1752            patchable: false,
1753        });
1754
1755        // Use a variable, create a control flow diamond so that the variable
1756        // forces a block parameter on the control join point, and make sure
1757        // that we get stack maps for all the appropriate uses of the variable
1758        // in all blocks, as well as that we are reusing stack slots for each of
1759        // the values.
1760        //
1761        //                        block0:
1762        //                          x := needs stack map
1763        //                          call $foo(x)
1764        //                          br_if v0, block1, block2
1765        //
1766        //
1767        //     block1:                                     block2:
1768        //       call $foo(x)                                call $foo(x)
1769        //       call $foo(x)                                call $foo(x)
1770        //       x := new needs stack map                    x := new needs stack map
1771        //       call $foo(x)                                call $foo(x)
1772        //       jump block3                                 jump block3
1773        //
1774        //
1775        //                        block3:
1776        //                          call $foo(x)
1777        //                          return x
1778
1779        let x = builder.declare_var(ir::types::I32);
1780        builder.declare_var_needs_stack_map(x);
1781
1782        let block0 = builder.create_block();
1783        let block1 = builder.create_block();
1784        let block2 = builder.create_block();
1785        let block3 = builder.create_block();
1786
1787        builder.append_block_params_for_function_params(block0);
1788        builder.switch_to_block(block0);
1789        let v0 = builder.func.dfg.block_params(block0)[0];
1790        let val = builder.ins().iconst(ir::types::I32, 42);
1791        builder.def_var(x, val);
1792        {
1793            let x = builder.use_var(x);
1794            builder.ins().call(func_ref, &[x]);
1795        }
1796        builder.ins().brif(v0, block1, &[], block2, &[]);
1797
1798        builder.switch_to_block(block1);
1799        {
1800            let x = builder.use_var(x);
1801            builder.ins().call(func_ref, &[x]);
1802            builder.ins().call(func_ref, &[x]);
1803        }
1804        let val = builder.ins().iconst(ir::types::I32, 36);
1805        builder.def_var(x, val);
1806        {
1807            let x = builder.use_var(x);
1808            builder.ins().call(func_ref, &[x]);
1809        }
1810        builder.ins().jump(block3, &[]);
1811
1812        builder.switch_to_block(block2);
1813        {
1814            let x = builder.use_var(x);
1815            builder.ins().call(func_ref, &[x]);
1816            builder.ins().call(func_ref, &[x]);
1817        }
1818        let val = builder.ins().iconst(ir::types::I32, 36);
1819        builder.def_var(x, val);
1820        {
1821            let x = builder.use_var(x);
1822            builder.ins().call(func_ref, &[x]);
1823        }
1824        builder.ins().jump(block3, &[]);
1825
1826        builder.switch_to_block(block3);
1827        let x = builder.use_var(x);
1828        builder.ins().call(func_ref, &[x]);
1829        builder.ins().return_(&[x]);
1830
1831        builder.seal_all_blocks();
1832        builder.finalize();
1833
1834        assert_eq_output!(
1835            func.display().to_string(),
1836            r#"
1837function %sample(i32) -> i32 system_v {
1838    ss0 = explicit_slot 4, align = 4
1839    ss1 = explicit_slot 4, align = 4
1840    sig0 = (i32) system_v
1841    fn0 = colocated u0:0 sig0
1842
1843block0(v0: i32):
1844    v1 = iconst.i32 42
1845    v2 -> v1
1846    v4 -> v1
1847    stack_store v1, ss0  ; v1 = 42
1848    v13 = stack_load.i32 ss0
1849    call fn0(v13), stack_map=[i32 @ ss0+0]
1850    brif v0, block1, block2
1851
1852block1:
1853    call fn0(v2), stack_map=[i32 @ ss0+0]  ; v2 = 42
1854    call fn0(v2)  ; v2 = 42
1855    v3 = iconst.i32 36
1856    stack_store v3, ss0  ; v3 = 36
1857    v10 = stack_load.i32 ss0
1858    call fn0(v10), stack_map=[i32 @ ss0+0]
1859    v9 = stack_load.i32 ss0
1860    jump block3(v9)
1861
1862block2:
1863    call fn0(v4), stack_map=[i32 @ ss0+0]  ; v4 = 42
1864    call fn0(v4)  ; v4 = 42
1865    v5 = iconst.i32 36
1866    stack_store v5, ss1  ; v5 = 36
1867    v12 = stack_load.i32 ss1
1868    call fn0(v12), stack_map=[i32 @ ss1+0]
1869    v11 = stack_load.i32 ss1
1870    jump block3(v11)
1871
1872block3(v6: i32):
1873    stack_store v6, ss0
1874    v8 = stack_load.i32 ss0
1875    call fn0(v8), stack_map=[i32 @ ss0+0]
1876    v7 = stack_load.i32 ss0
1877    return v7
1878}
1879            "#
1880        );
1881    }
1882
1883    #[test]
1884    fn var_needs_stack_map() {
1885        let mut sig = Signature::new(CallConv::SystemV);
1886        sig.params
1887            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
1888        sig.returns
1889            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
1890
1891        let mut fn_ctx = FunctionBuilderContext::new();
1892        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1893        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1894
1895        let var = builder.declare_var(cranelift_codegen::ir::types::I32);
1896        builder.declare_var_needs_stack_map(var);
1897
1898        let name = builder
1899            .func
1900            .declare_imported_user_function(ir::UserExternalName {
1901                namespace: 0,
1902                index: 0,
1903            });
1904        let signature = builder
1905            .func
1906            .import_signature(Signature::new(CallConv::SystemV));
1907        let func_ref = builder.import_function(ir::ExtFuncData {
1908            name: ir::ExternalName::user(name),
1909            signature,
1910            colocated: true,
1911            patchable: false,
1912        });
1913
1914        let block0 = builder.create_block();
1915        builder.append_block_params_for_function_params(block0);
1916        builder.switch_to_block(block0);
1917
1918        let arg = builder.func.dfg.block_params(block0)[0];
1919        builder.def_var(var, arg);
1920
1921        builder.ins().call(func_ref, &[]);
1922
1923        let val = builder.use_var(var);
1924        builder.ins().return_(&[val]);
1925
1926        builder.seal_all_blocks();
1927        builder.finalize();
1928
1929        assert_eq_output!(
1930            func.display().to_string(),
1931            r#"
1932function %sample(i32) -> i32 system_v {
1933    ss0 = explicit_slot 4, align = 4
1934    sig0 = () system_v
1935    fn0 = colocated u0:0 sig0
1936
1937block0(v0: i32):
1938    stack_store v0, ss0
1939    call fn0(), stack_map=[i32 @ ss0+0]
1940    v1 = stack_load.i32 ss0
1941    return v1
1942}
1943            "#
1944        );
1945    }
1946
1947    #[test]
1948    fn first_inst_defines_needs_stack_map() {
1949        let mut sig = Signature::new(CallConv::SystemV);
1950        sig.params
1951            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
1952        sig.returns
1953            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
1954        sig.returns
1955            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
1956
1957        let mut fn_ctx = FunctionBuilderContext::new();
1958        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1959        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1960
1961        let name = builder
1962            .func
1963            .declare_imported_user_function(ir::UserExternalName {
1964                namespace: 0,
1965                index: 0,
1966            });
1967        let signature = builder
1968            .func
1969            .import_signature(Signature::new(CallConv::SystemV));
1970        let func_ref = builder.import_function(ir::ExtFuncData {
1971            name: ir::ExternalName::user(name),
1972            signature,
1973            colocated: true,
1974            patchable: false,
1975        });
1976
1977        // Regression test found via fuzzing in
1978        // https://github.com/bytecodealliance/wasmtime/pull/8941 involving the
1979        // combination of cursor positions after we have block parameters that
1980        // need inclusion in stack maps and when the first instruction in a
1981        // block defines a value that needs inclusion in stack maps.
1982        //
1983        // block0(v0: i32):
1984        //   v1 = iconst.i32 42
1985        //   call $foo()
1986        //   return v0, v1
1987
1988        let block0 = builder.create_block();
1989        builder.append_block_params_for_function_params(block0);
1990        builder.switch_to_block(block0);
1991
1992        let arg = builder.func.dfg.block_params(block0)[0];
1993        builder.declare_value_needs_stack_map(arg);
1994
1995        let val = builder.ins().iconst(ir::types::I32, 42);
1996        builder.declare_value_needs_stack_map(val);
1997
1998        builder.ins().call(func_ref, &[]);
1999
2000        builder.ins().return_(&[arg, val]);
2001
2002        builder.seal_all_blocks();
2003        builder.finalize();
2004
2005        assert_eq_output!(
2006            func.display().to_string(),
2007            r#"
2008function %sample(i32) -> i32, i32 system_v {
2009    ss0 = explicit_slot 4, align = 4
2010    ss1 = explicit_slot 4, align = 4
2011    sig0 = () system_v
2012    fn0 = colocated u0:0 sig0
2013
2014block0(v0: i32):
2015    stack_store v0, ss0
2016    v1 = iconst.i32 42
2017    stack_store v1, ss1  ; v1 = 42
2018    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
2019    v2 = stack_load.i32 ss0
2020    v3 = stack_load.i32 ss1
2021    return v2, v3
2022}
2023            "#
2024        );
2025    }
2026
2027    #[test]
2028    fn needs_stack_map_and_loops_and_partially_live_values() {
2029        let _ = env_logger::try_init();
2030
2031        let mut sig = Signature::new(CallConv::SystemV);
2032        sig.params.push(AbiParam::new(ir::types::I32));
2033
2034        let mut fn_ctx = FunctionBuilderContext::new();
2035        let mut func =
2036            Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig.clone());
2037        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2038
2039        let name = builder
2040            .func
2041            .declare_imported_user_function(ir::UserExternalName {
2042                namespace: 0,
2043                index: 0,
2044            });
2045        let signature = builder
2046            .func
2047            .import_signature(Signature::new(CallConv::SystemV));
2048        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2049            name: ir::ExternalName::user(name),
2050            signature,
2051            colocated: true,
2052            patchable: false,
2053        });
2054
2055        let name = builder
2056            .func
2057            .declare_imported_user_function(ir::UserExternalName {
2058                namespace: 1,
2059                index: 1,
2060            });
2061        let signature = builder.func.import_signature(sig);
2062        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2063            name: ir::ExternalName::user(name),
2064            signature,
2065            colocated: true,
2066            patchable: false,
2067        });
2068
2069        // Test that we support stack maps in loops and that we properly handle
2070        // value that are only live for part of the loop body on each iteration,
2071        // but are live across the whole loop because they will be used again
2072        // the next iteration. Note that `v0` below, which is a GC value, is not
2073        // live *within a single iteration of the loop* after the call to `bar`,
2074        // but is actually live across the whole loop because it will be used
2075        // again in the *next iteration of the loop*:
2076        //
2077        //     block0(v0: i32):
2078        //       jump block1
2079        //
2080        //     block1:
2081        //       call $foo()
2082        //       call $bar(v0)
2083        //       call $foo()
2084        //       jump block1
2085        let block0 = builder.create_block();
2086        let block1 = builder.create_block();
2087        builder.append_block_params_for_function_params(block0);
2088
2089        builder.switch_to_block(block0);
2090        builder.ins().jump(block1, &[]);
2091
2092        builder.switch_to_block(block1);
2093        let v0 = builder.func.dfg.block_params(block0)[0];
2094        builder.declare_value_needs_stack_map(v0);
2095        builder.ins().call(foo_func_ref, &[]);
2096        builder.ins().call(bar_func_ref, &[v0]);
2097        builder.ins().call(foo_func_ref, &[]);
2098        builder.ins().jump(block1, &[]);
2099
2100        builder.seal_all_blocks();
2101        builder.finalize();
2102
2103        assert_eq_output!(
2104            func.display().to_string(),
2105            r#"
2106function %sample(i32) system_v {
2107    ss0 = explicit_slot 4, align = 4
2108    sig0 = () system_v
2109    sig1 = (i32) system_v
2110    fn0 = colocated u0:0 sig0
2111    fn1 = colocated u1:1 sig1
2112
2113block0(v0: i32):
2114    stack_store v0, ss0
2115    jump block1
2116
2117block1:
2118    call fn0(), stack_map=[i32 @ ss0+0]
2119    v1 = stack_load.i32 ss0
2120    call fn1(v1), stack_map=[i32 @ ss0+0]
2121    call fn0(), stack_map=[i32 @ ss0+0]
2122    jump block1
2123}
2124            "#,
2125        );
2126    }
2127
2128    #[test]
2129    fn needs_stack_map_and_irreducible_loops() {
2130        let _ = env_logger::try_init();
2131
2132        let mut sig = Signature::new(CallConv::SystemV);
2133        sig.params.push(AbiParam::new(ir::types::I32));
2134        sig.params.push(AbiParam::new(ir::types::I32));
2135
2136        let mut fn_ctx = FunctionBuilderContext::new();
2137        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2138        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2139
2140        let name = builder
2141            .func
2142            .declare_imported_user_function(ir::UserExternalName {
2143                namespace: 0,
2144                index: 0,
2145            });
2146        let signature = builder
2147            .func
2148            .import_signature(Signature::new(CallConv::SystemV));
2149        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2150            name: ir::ExternalName::user(name),
2151            signature,
2152            colocated: true,
2153            patchable: false,
2154        });
2155
2156        let name = builder
2157            .func
2158            .declare_imported_user_function(ir::UserExternalName {
2159                namespace: 1,
2160                index: 1,
2161            });
2162        let mut sig = Signature::new(CallConv::SystemV);
2163        sig.params.push(AbiParam::new(ir::types::I32));
2164        let signature = builder.func.import_signature(sig);
2165        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2166            name: ir::ExternalName::user(name),
2167            signature,
2168            colocated: true,
2169            patchable: false,
2170        });
2171
2172        // Test an irreducible loop with multiple entry points, both block1 and
2173        // block2, in this case:
2174        //
2175        //     block0(v0: i32, v1: i32):
2176        //       brif v0, block1, block2
2177        //
2178        //     block1:
2179        //       jump block3
2180        //
2181        //     block2:
2182        //       jump block4
2183        //
2184        //     block3:
2185        //       call $foo()
2186        //       call $bar(v1)
2187        //       call $foo()
2188        //       jump block2
2189        //
2190        //     block4:
2191        //       call $foo()
2192        //       call $bar(v1)
2193        //       call $foo()
2194        //       jump block1
2195        let block0 = builder.create_block();
2196        let block1 = builder.create_block();
2197        let block2 = builder.create_block();
2198        let block3 = builder.create_block();
2199        let block4 = builder.create_block();
2200        builder.append_block_params_for_function_params(block0);
2201
2202        builder.switch_to_block(block0);
2203        let v0 = builder.func.dfg.block_params(block0)[0];
2204        let v1 = builder.func.dfg.block_params(block0)[1];
2205        builder.declare_value_needs_stack_map(v1);
2206        builder.ins().brif(v0, block1, &[], block2, &[]);
2207
2208        builder.switch_to_block(block1);
2209        builder.ins().jump(block3, &[]);
2210
2211        builder.switch_to_block(block2);
2212        builder.ins().jump(block4, &[]);
2213
2214        builder.switch_to_block(block3);
2215        builder.ins().call(foo_func_ref, &[]);
2216        builder.ins().call(bar_func_ref, &[v1]);
2217        builder.ins().call(foo_func_ref, &[]);
2218        builder.ins().jump(block2, &[]);
2219
2220        builder.switch_to_block(block4);
2221        builder.ins().call(foo_func_ref, &[]);
2222        builder.ins().call(bar_func_ref, &[v1]);
2223        builder.ins().call(foo_func_ref, &[]);
2224        builder.ins().jump(block1, &[]);
2225
2226        builder.seal_all_blocks();
2227        builder.finalize();
2228
2229        assert_eq_output!(
2230            func.display().to_string(),
2231            r#"
2232function %sample(i32, i32) system_v {
2233    ss0 = explicit_slot 4, align = 4
2234    sig0 = () system_v
2235    sig1 = (i32) system_v
2236    fn0 = colocated u0:0 sig0
2237    fn1 = colocated u1:1 sig1
2238
2239block0(v0: i32, v1: i32):
2240    stack_store v1, ss0
2241    brif v0, block1, block2
2242
2243block1:
2244    jump block3
2245
2246block2:
2247    jump block4
2248
2249block3:
2250    call fn0(), stack_map=[i32 @ ss0+0]
2251    v3 = stack_load.i32 ss0
2252    call fn1(v3), stack_map=[i32 @ ss0+0]
2253    call fn0(), stack_map=[i32 @ ss0+0]
2254    jump block2
2255
2256block4:
2257    call fn0(), stack_map=[i32 @ ss0+0]
2258    v2 = stack_load.i32 ss0
2259    call fn1(v2), stack_map=[i32 @ ss0+0]
2260    call fn0(), stack_map=[i32 @ ss0+0]
2261    jump block1
2262}
2263            "#,
2264        );
2265    }
2266
2267    #[test]
2268    fn needs_stack_map_and_back_edge_to_back_edge() {
2269        let _ = env_logger::try_init();
2270
2271        let mut sig = Signature::new(CallConv::SystemV);
2272        sig.params.push(AbiParam::new(ir::types::I32));
2273        sig.params.push(AbiParam::new(ir::types::I32));
2274        sig.params.push(AbiParam::new(ir::types::I32));
2275        sig.params.push(AbiParam::new(ir::types::I32));
2276
2277        let mut fn_ctx = FunctionBuilderContext::new();
2278        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2279        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2280
2281        let name = builder
2282            .func
2283            .declare_imported_user_function(ir::UserExternalName {
2284                namespace: 0,
2285                index: 0,
2286            });
2287        let signature = builder
2288            .func
2289            .import_signature(Signature::new(CallConv::SystemV));
2290        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2291            name: ir::ExternalName::user(name),
2292            signature,
2293            colocated: true,
2294            patchable: false,
2295        });
2296
2297        let name = builder
2298            .func
2299            .declare_imported_user_function(ir::UserExternalName {
2300                namespace: 1,
2301                index: 1,
2302            });
2303        let mut sig = Signature::new(CallConv::SystemV);
2304        sig.params.push(AbiParam::new(ir::types::I32));
2305        let signature = builder.func.import_signature(sig);
2306        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2307            name: ir::ExternalName::user(name),
2308            signature,
2309            colocated: true,
2310            patchable: false,
2311        });
2312
2313        // Test that we detect the `block1 -> block2 -> block3 -> block2 ->
2314        // block1` loop in our liveness analysis and keep `v{0,1,2}` live across
2315        // the whole loop body.
2316        //
2317        //     block0(v0, v1, v2, v3):
2318        //       jump block1(v3)
2319        //
2320        //     block1(v4):
2321        //       call foo_func_ref()
2322        //       call bar_func_ref(v0)
2323        //       call foo_func_ref()
2324        //       jump block2
2325        //
2326        //     block2:
2327        //       call foo_func_ref()
2328        //       call bar_func_ref(v1)
2329        //       call foo_func_ref()
2330        //       v5 = iadd_imm v4, -1
2331        //       brif v4, block1(v5), block3
2332        //
2333        //     block3:
2334        //       call foo_func_ref()
2335        //       call bar_func_ref(v2)
2336        //       call foo_func_ref()
2337        //       jump block2
2338
2339        let block0 = builder.create_block();
2340        let block1 = builder.create_block();
2341        let block2 = builder.create_block();
2342        let block3 = builder.create_block();
2343
2344        builder.append_block_params_for_function_params(block0);
2345
2346        builder.switch_to_block(block0);
2347
2348        let v0 = builder.func.dfg.block_params(block0)[0];
2349        builder.declare_value_needs_stack_map(v0);
2350        let v1 = builder.func.dfg.block_params(block0)[1];
2351        builder.declare_value_needs_stack_map(v1);
2352        let v2 = builder.func.dfg.block_params(block0)[2];
2353        builder.declare_value_needs_stack_map(v2);
2354        let v3 = builder.func.dfg.block_params(block0)[3];
2355
2356        builder.ins().jump(block1, &[v3.into()]);
2357
2358        builder.switch_to_block(block1);
2359        let v4 = builder.append_block_param(block1, ir::types::I32);
2360        builder.ins().call(foo_func_ref, &[]);
2361        builder.ins().call(bar_func_ref, &[v0]);
2362        builder.ins().call(foo_func_ref, &[]);
2363        builder.ins().jump(block2, &[]);
2364
2365        builder.switch_to_block(block2);
2366        builder.ins().call(foo_func_ref, &[]);
2367        builder.ins().call(bar_func_ref, &[v1]);
2368        builder.ins().call(foo_func_ref, &[]);
2369        let v5 = builder.ins().iadd_imm(v4, -1);
2370        builder.ins().brif(v4, block1, &[v5.into()], block3, &[]);
2371
2372        builder.switch_to_block(block3);
2373        builder.ins().call(foo_func_ref, &[]);
2374        builder.ins().call(bar_func_ref, &[v2]);
2375        builder.ins().call(foo_func_ref, &[]);
2376        builder.ins().jump(block2, &[]);
2377
2378        builder.seal_all_blocks();
2379        builder.finalize();
2380
2381        assert_eq_output!(
2382            func.display().to_string(),
2383            r#"
2384function %sample(i32, i32, i32, i32) system_v {
2385    ss0 = explicit_slot 4, align = 4
2386    ss1 = explicit_slot 4, align = 4
2387    ss2 = explicit_slot 4, align = 4
2388    sig0 = () system_v
2389    sig1 = (i32) system_v
2390    fn0 = colocated u0:0 sig0
2391    fn1 = colocated u1:1 sig1
2392
2393block0(v0: i32, v1: i32, v2: i32, v3: i32):
2394    stack_store v0, ss0
2395    stack_store v1, ss1
2396    stack_store v2, ss2
2397    jump block1(v3)
2398
2399block1(v4: i32):
2400    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2401    v8 = stack_load.i32 ss0
2402    call fn1(v8), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2403    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2404    jump block2
2405
2406block2:
2407    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2408    v7 = stack_load.i32 ss1
2409    call fn1(v7), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2410    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2411    v5 = iadd_imm.i32 v4, -1
2412    brif.i32 v4, block1(v5), block3
2413
2414block3:
2415    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2416    v6 = stack_load.i32 ss2
2417    call fn1(v6), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2418    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2419    jump block2
2420}
2421            "#,
2422        );
2423    }
2424
2425    fn import_func(
2426        builder: &mut FunctionBuilder,
2427        params: impl IntoIterator<Item = ir::Type>,
2428        results: impl IntoIterator<Item = ir::Type>,
2429    ) -> ir::FuncRef {
2430        let index = u32::try_from(builder.func.dfg.ext_funcs.len()).unwrap();
2431
2432        let name = builder
2433            .func
2434            .declare_imported_user_function(ir::UserExternalName {
2435                namespace: 0,
2436                index,
2437            });
2438        let name = ir::ExternalName::user(name);
2439
2440        let mut signature = Signature::new(CallConv::SystemV);
2441        signature
2442            .params
2443            .extend(params.into_iter().map(|ty| AbiParam::new(ty)));
2444        signature
2445            .returns
2446            .extend(results.into_iter().map(|ty| AbiParam::new(ty)));
2447        let signature = builder.func.import_signature(signature);
2448
2449        builder.import_function(ir::ExtFuncData {
2450            name,
2451            signature,
2452            colocated: true,
2453            patchable: false,
2454        })
2455    }
2456
2457    #[test]
2458    fn issue_10397_stack_map_vars_and_indirect_block_params() {
2459        let _ = env_logger::try_init();
2460
2461        let mut sig = Signature::new(CallConv::SystemV);
2462        if false {
2463            sig.params.push(AbiParam::new(ir::types::I32));
2464        }
2465
2466        let mut fn_ctx = FunctionBuilderContext::new();
2467        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("f"), sig);
2468        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2469
2470        let alloc_struct = import_func(&mut builder, None, Some(ir::types::I32));
2471        let alloc_array = import_func(&mut builder, None, Some(ir::types::I32));
2472        let array_init_elem = import_func(&mut builder, Some(ir::types::I32), None);
2473        let type_of = import_func(&mut builder, Some(ir::types::I32), Some(ir::types::I32));
2474        let ref_test = import_func(
2475            &mut builder,
2476            vec![ir::types::I32, ir::types::I32],
2477            Some(ir::types::I32),
2478        );
2479        let access_array = import_func(&mut builder, Some(ir::types::I32), None);
2480        let should_continue_inner_loop = import_func(&mut builder, None, Some(ir::types::I32));
2481        let access_struct = import_func(&mut builder, Some(ir::types::I32), None);
2482        let should_return = import_func(&mut builder, None, Some(ir::types::I32));
2483
2484        // This test exercises the combination of declaring stack maps and when
2485        // we need to insert block params during SSA construction. The following
2486        // CLIF uses vars in such a way that it will ultimately require that we
2487        // insert block params. However, some of the inserted block params are
2488        // only ever used in such a way that they are passed as arguments to
2489        // *other* blocks. That is, there are regions where we never use them
2490        // directly, and therefore, if we are only declaring that the variable's
2491        // values need inclusion in stack maps on direct uses, we will
2492        // completely miss these inserted block params. That leads to incomplete
2493        // stack maps, which leads to collecting objects too early, which leads
2494        // to use-after-free bugs.
2495        //
2496        // After inserting safepoint spills and stack map annotations to the
2497        // following pseudo-CLIF, we should have stack map entries for the
2498        // `live: {...}` annotations (or an over-approximation of that
2499        // annotation).
2500        //
2501        //     block_entry:
2502        //       v0 = call alloc_struct()                          ;; live: {}
2503        //       define var_struct = v0
2504        //       jump block_outer_loop_head
2505        //
2506        //     block_outer_loop_head:
2507        //       v1 = call alloc_array()                           ;; live: {struct}
2508        //       define var_array = v1
2509        //       v2 = iconst.i32 0
2510        //       jump block_array_init_loop_head(v2)
2511        //
2512        //     block_array_init_loop_head(v3):
2513        //       v4 = iconst.i32 1
2514        //       v5 = icmp ult v3, v4
2515        //       br_if v5, block_array_init_loop_body, block_array_init_loop_done
2516        //
2517        //     block_array_init_loop_body:
2518        //       call array_init_elem(v1, v4)                      ;; live: {struct, array}
2519        //       v6 = iconst.i32 1
2520        //       v7 = iadd v4, v6
2521        //       jump block_array_init_loop_head(v7)
2522        //
2523        //     block_array_init_loop_done:
2524        //       jump block_inner_loop_head
2525        //
2526        //     block_inner_loop_head:
2527        //       v8 = use var_array
2528        //       v9 = iconst.i32 0
2529        //       v10 = icmp eq v8, v9
2530        //       br_if v10, block_ref_test_done(v9), block_ref_test_non_null
2531        //
2532        //     block_ref_test_non_null:
2533        //       v11 = call type_of(v8)                            ;; live: {struct, array}
2534        //       v12 = iconst.i32 0xbeefbeef
2535        //       v13 = icmp eq v11, v12
2536        //       v14 = iconst.i31 1
2537        //       br_if v13, block_ref_test_done(v14), block_ref_test_slow
2538        //
2539        //     block_ref_test_slow:
2540        //       v15 = call ref_test(v8, v12)                      ;; live: {struct, array}
2541        //       jump block_ref_test_done(v15)
2542        //
2543        //     block_ref_test_done(v16):
2544        //       trapz v16, user1
2545        //       define var_array = v8
2546        //       call access_array(v8)                             ;; live: {struct}
2547        //       v17 = call should_continue_inner_loop()           ;; live: {struct}
2548        //       br_if v17, block_inner_loop_head, block_after_inner_loop
2549        //
2550        //     block_after_inner_loop:
2551        //       v18 = use var_struct
2552        //       call access_struct(v18)                           ;; live: {struct}
2553        //       v19 = call should_return()                        ;; live: {struct}
2554        //       br_if v19, block_return, block_outer_loop_head
2555        //
2556        //     block_return:
2557        //       return
2558
2559        let var_struct = builder.declare_var(cranelift_codegen::ir::types::I32);
2560        builder.declare_var_needs_stack_map(var_struct);
2561
2562        let var_array = builder.declare_var(cranelift_codegen::ir::types::I32);
2563        builder.declare_var_needs_stack_map(var_array);
2564
2565        let block_entry = builder.create_block();
2566        let block_outer_loop_head = builder.create_block();
2567        let block_array_init_loop_head = builder.create_block();
2568        let block_array_init_loop_body = builder.create_block();
2569        let block_array_init_loop_done = builder.create_block();
2570        let block_inner_loop_head = builder.create_block();
2571        let block_ref_test_non_null = builder.create_block();
2572        let block_ref_test_slow = builder.create_block();
2573        let block_ref_test_done = builder.create_block();
2574        let block_after_inner_loop = builder.create_block();
2575        let block_return = builder.create_block();
2576
2577        builder.append_block_params_for_function_params(block_entry);
2578        builder.switch_to_block(block_entry);
2579        builder.seal_block(block_entry);
2580        let call_inst = builder.ins().call(alloc_struct, &[]);
2581        let v0 = builder.func.dfg.first_result(call_inst);
2582        builder.def_var(var_struct, v0);
2583        builder.ins().jump(block_outer_loop_head, &[]);
2584
2585        builder.switch_to_block(block_outer_loop_head);
2586        let call_inst = builder.ins().call(alloc_array, &[]);
2587        let v1 = builder.func.dfg.first_result(call_inst);
2588        builder.def_var(var_array, v1);
2589        let v2 = builder.ins().iconst(ir::types::I32, 0);
2590        builder.ins().jump(block_array_init_loop_head, &[v2.into()]);
2591
2592        builder.switch_to_block(block_array_init_loop_head);
2593        let v3 = builder.append_block_param(block_array_init_loop_head, ir::types::I32);
2594        let v4 = builder.ins().iconst(ir::types::I32, 1);
2595        let v5 = builder
2596            .ins()
2597            .icmp(ir::condcodes::IntCC::UnsignedLessThan, v3, v4);
2598        builder.ins().brif(
2599            v5,
2600            block_array_init_loop_body,
2601            &[],
2602            block_array_init_loop_done,
2603            &[],
2604        );
2605
2606        builder.switch_to_block(block_array_init_loop_body);
2607        builder.seal_block(block_array_init_loop_body);
2608        builder.ins().call(array_init_elem, &[v1, v4]);
2609        let v6 = builder.ins().iconst(ir::types::I32, 1);
2610        let v7 = builder.ins().iadd(v4, v6);
2611        builder.ins().jump(block_array_init_loop_head, &[v7.into()]);
2612        builder.seal_block(block_array_init_loop_head);
2613
2614        builder.switch_to_block(block_array_init_loop_done);
2615        builder.seal_block(block_array_init_loop_done);
2616        builder.ins().jump(block_inner_loop_head, &[]);
2617
2618        builder.switch_to_block(block_inner_loop_head);
2619        let v8 = builder.use_var(var_array);
2620        let v9 = builder.ins().iconst(ir::types::I32, 0);
2621        let v10 = builder.ins().icmp(ir::condcodes::IntCC::Equal, v8, v9);
2622        builder.ins().brif(
2623            v10,
2624            block_ref_test_done,
2625            &[v9.into()],
2626            block_ref_test_non_null,
2627            &[],
2628        );
2629
2630        builder.switch_to_block(block_ref_test_non_null);
2631        builder.seal_block(block_ref_test_non_null);
2632        let call_inst = builder.ins().call(type_of, &[v8]);
2633        let v11 = builder.func.dfg.first_result(call_inst);
2634        let v12 = builder.ins().iconst(ir::types::I32, 0xbeefbeef);
2635        let v13 = builder.ins().icmp(ir::condcodes::IntCC::Equal, v11, v12);
2636        let v14 = builder.ins().iconst(ir::types::I32, 1);
2637        builder.ins().brif(
2638            v13,
2639            block_ref_test_done,
2640            &[v14.into()],
2641            block_ref_test_slow,
2642            &[],
2643        );
2644
2645        builder.switch_to_block(block_ref_test_slow);
2646        builder.seal_block(block_ref_test_slow);
2647        let call_inst = builder.ins().call(ref_test, &[v8, v12]);
2648        let v15 = builder.func.dfg.first_result(call_inst);
2649        builder.ins().jump(block_ref_test_done, &[v15.into()]);
2650
2651        builder.switch_to_block(block_ref_test_done);
2652        let v16 = builder.append_block_param(block_ref_test_done, ir::types::I32);
2653        builder.seal_block(block_ref_test_done);
2654        builder.ins().trapz(v16, ir::TrapCode::user(1).unwrap());
2655        builder.def_var(var_array, v8);
2656        builder.ins().call(access_array, &[v8]);
2657        let call_inst = builder.ins().call(should_continue_inner_loop, &[]);
2658        let v17 = builder.func.dfg.first_result(call_inst);
2659        builder
2660            .ins()
2661            .brif(v17, block_inner_loop_head, &[], block_after_inner_loop, &[]);
2662        builder.seal_block(block_inner_loop_head);
2663
2664        builder.switch_to_block(block_after_inner_loop);
2665        builder.seal_block(block_after_inner_loop);
2666        let v18 = builder.use_var(var_struct);
2667        builder.ins().call(access_struct, &[v18]);
2668        let call_inst = builder.ins().call(should_return, &[]);
2669        let v19 = builder.func.dfg.first_result(call_inst);
2670        builder
2671            .ins()
2672            .brif(v19, block_return, &[], block_outer_loop_head, &[]);
2673        builder.seal_block(block_outer_loop_head);
2674
2675        builder.switch_to_block(block_return);
2676        builder.seal_block(block_return);
2677        builder.ins().return_(&[]);
2678
2679        builder.finalize();
2680        assert_eq_output!(
2681            func.display().to_string(),
2682            r#"
2683function %f() system_v {
2684    ss0 = explicit_slot 4, align = 4
2685    ss1 = explicit_slot 4, align = 4
2686    ss2 = explicit_slot 4, align = 4
2687    sig0 = () -> i32 system_v
2688    sig1 = () -> i32 system_v
2689    sig2 = (i32) system_v
2690    sig3 = (i32) -> i32 system_v
2691    sig4 = (i32, i32) -> i32 system_v
2692    sig5 = (i32) system_v
2693    sig6 = () -> i32 system_v
2694    sig7 = (i32) system_v
2695    sig8 = () -> i32 system_v
2696    fn0 = colocated u0:0 sig0
2697    fn1 = colocated u0:1 sig1
2698    fn2 = colocated u0:2 sig2
2699    fn3 = colocated u0:3 sig3
2700    fn4 = colocated u0:4 sig4
2701    fn5 = colocated u0:5 sig5
2702    fn6 = colocated u0:6 sig6
2703    fn7 = colocated u0:7 sig7
2704    fn8 = colocated u0:8 sig8
2705
2706block0:
2707    v0 = call fn0()
2708    jump block1(v0)
2709
2710block1(v22: i32):
2711    v21 -> v22
2712    stack_store v22, ss1
2713    v1 = call fn1(), stack_map=[i32 @ ss1+0]
2714    v8 -> v1
2715    v18 -> v1
2716    stack_store v1, ss0
2717    v2 = iconst.i32 0
2718    jump block2(v2)  ; v2 = 0
2719
2720block2(v3: i32):
2721    v4 = iconst.i32 1
2722    v5 = icmp ult v3, v4  ; v4 = 1
2723    brif v5, block3, block4
2724
2725block3:
2726    v24 = stack_load.i32 ss0
2727    call fn2(v24, v4), stack_map=[i32 @ ss0+0, i32 @ ss1+0]  ; v4 = 1
2728    v6 = iconst.i32 1
2729    v7 = iadd.i32 v4, v6  ; v4 = 1, v6 = 1
2730    jump block2(v7)
2731
2732block4:
2733    v26 = stack_load.i32 ss1
2734    jump block5(v26)
2735
2736block5(v20: i32):
2737    v19 -> v20
2738    stack_store v20, ss2
2739    v9 = iconst.i32 0
2740    v10 = icmp.i32 eq v8, v9  ; v9 = 0
2741    brif v10, block8(v9), block6  ; v9 = 0
2742
2743block6:
2744    v11 = call fn3(v8), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2745    v12 = iconst.i32 -1091584273
2746    v13 = icmp eq v11, v12  ; v12 = -1091584273
2747    v14 = iconst.i32 1
2748    brif v13, block8(v14), block7  ; v14 = 1
2749
2750block7:
2751    v15 = call fn4(v8, v12), stack_map=[i32 @ ss0+0, i32 @ ss2+0]  ; v12 = -1091584273
2752    jump block8(v15)
2753
2754block8(v16: i32):
2755    trapz v16, user1
2756    call fn5(v8), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2757    v17 = call fn6(), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2758    brif v17, block5(v19), block9
2759
2760block9:
2761    v25 = stack_load.i32 ss2
2762    call fn7(v25), stack_map=[i32 @ ss2+0]
2763    v23 = call fn8(), stack_map=[i32 @ ss2+0]
2764    brif v23, block10, block1(v19)
2765
2766block10:
2767    return
2768}
2769            "#,
2770        );
2771    }
2772}