Skip to main content

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, func: &Function, val: ir::Value) {
371        debug_assert!(!func.dfg.value_is_alias(val));
372        if self.currently_live.remove(&val) {
373            log::trace!("liveness:   defining {val:?}, removing it from the live set");
374        }
375    }
376
377    /// Record the live set of needs-stack-map values at the given safepoint.
378    fn record_safepoint(&mut self, func: &Function, inst: Inst) {
379        log::trace!(
380            "liveness:   found safepoint: {inst:?}: {}",
381            func.dfg.display_inst(inst)
382        );
383        log::trace!("liveness:     live set = {:?}", self.currently_live);
384
385        let mut live = self.currently_live.iter().copied().collect::<SmallVec<_>>();
386        // Keep order deterministic since we add stack map entries in this
387        // order.
388        live.sort();
389        debug_assert!(live.iter().all(|v| !func.dfg.value_is_alias(*v)));
390
391        self.live_across_any_safepoint.extend(live.iter().copied());
392        self.safepoints.insert(inst, live);
393    }
394
395    /// Process a use of a needs-stack-map value, inserting it into the
396    /// currently-live set.
397    fn process_use(&mut self, func: &Function, inst: Inst, val: Value) {
398        debug_assert!(!func.dfg.value_is_alias(val));
399        if self.currently_live.insert(val) {
400            log::trace!(
401                "liveness:   found use of {val:?}, marking it live: {inst:?}: {}",
402                func.dfg.display_inst(inst)
403            );
404        }
405    }
406
407    /// Process all the instructions in a block in reverse order.
408    fn process_block(
409        &mut self,
410        func: &mut Function,
411        stack_map_values: &EntitySet<ir::Value>,
412        block_index: usize,
413        record_safepoints: RecordSafepoints,
414    ) {
415        let block = self.post_order[block_index];
416        log::trace!("liveness: traversing {block:?}");
417
418        // Reset the currently-live set to this block's live-out set.
419        self.currently_live.clear();
420        self.currently_live
421            .extend(self.live_outs[block_index].iter().copied());
422
423        // Now process this block's instructions, incrementally building its
424        // live-in set inside the currently-live set.
425        let mut option_inst = func.layout.last_inst(block);
426        while let Some(inst) = option_inst {
427            // Process any needs-stack-map values defined by this instruction.
428            for val in func.dfg.inst_results(inst) {
429                self.process_def(func, *val);
430            }
431
432            // If this instruction is a safepoint and we've been asked to record
433            // safepoints, then do so.
434            let opcode = func.dfg.insts[inst].opcode();
435            if record_safepoints == RecordSafepoints::Yes && opcode.is_safepoint() {
436                self.record_safepoint(func, inst);
437            }
438
439            // Process any needs-stack-map values used by this instruction.
440            for val in func.dfg.inst_values(inst) {
441                let val = func.dfg.resolve_aliases(val);
442                if stack_map_values.contains(val) {
443                    self.process_use(func, inst, val);
444                }
445            }
446
447            option_inst = func.layout.prev_inst(inst);
448        }
449
450        // After we've processed this block's instructions, remove its
451        // parameters from the live set. This is part of step (1).
452        for val in func.dfg.block_params(block) {
453            self.process_def(func, *val);
454        }
455    }
456
457    /// Run the liveness analysis on the given function.
458    pub fn run(&mut self, func: &mut Function, stack_map_values: &EntitySet<ir::Value>) {
459        self.clear();
460        self.post_order.extend(self.dfs.post_order_iter(func));
461        self.reserve_capacity(func);
462        self.initialize_block_to_index_map();
463        self.initialize_predecessors_map(func);
464
465        // Initially enqueue all blocks for processing. We push them in reverse
466        // post-order (which yields them in post-order when popped) because if
467        // there are no back-edges in the control-flow graph, post-order will
468        // result in only a single pass over the blocks.
469        self.worklist
470            .extend((0..u32::try_from(self.post_order.len()).unwrap()).rev());
471
472        // Pump the worklist until we reach a fixed-point.
473        while let Some(block_index) = self.worklist.pop() {
474            let block_index = usize::try_from(block_index).unwrap();
475
476            // Because our live sets grow monotonically, we just need to see if
477            // the size changed to determine whether the whole set changed.
478            let initial_live_in_len = self.live_ins[block_index].len();
479
480            // The live-out set for a block is the union of the live-in sets of
481            // its successors.
482            for successor in func.block_successors(self.post_order[block_index]) {
483                let successor_index = self.block_to_index[successor];
484                debug_assert_ne!(successor_index, u32::MAX);
485                let successor_index = usize::try_from(successor_index).unwrap();
486                self.live_outs[block_index].extend(self.live_ins[successor_index].iter().copied());
487            }
488            debug_assert!(
489                self.live_outs[block_index]
490                    .iter()
491                    .all(|v| !func.dfg.value_is_alias(*v))
492            );
493
494            // Process the block to compute its live-in set, but do not record
495            // safepoints yet, as we haven't yet processed loop back edges (see
496            // below).
497            self.process_block(func, stack_map_values, block_index, RecordSafepoints::No);
498
499            // The live-in set for a block is the set of values that are still
500            // live after the block's instructions have been processed.
501            self.live_ins[block_index].extend(self.currently_live.iter().copied());
502            debug_assert!(
503                self.live_ins[block_index]
504                    .iter()
505                    .all(|v| !func.dfg.value_is_alias(*v))
506            );
507
508            // If the live-in set changed, then we need to revisit all this
509            // block's predecessors.
510            if self.live_ins[block_index].len() != initial_live_in_len {
511                self.worklist
512                    .extend(self.predecessors[block_index].iter().copied());
513            }
514        }
515
516        // Once we've reached a fixed-point, compute the actual live set for
517        // each safepoint instruction in each block, backwards from the block's
518        // live-out set.
519        for block_index in 0..self.post_order.len() {
520            self.process_block(func, stack_map_values, block_index, RecordSafepoints::Yes);
521
522            debug_assert_eq!(
523                self.currently_live, self.live_ins[block_index],
524                "when we recompute the live-in set for a block as part of \
525                 computing live sets at each safepoint, we should get the same \
526                 result we computed in the fixed-point"
527            );
528        }
529    }
530}
531
532/// A mapping from each needs-stack-map value to its associated stack slot.
533///
534/// Internally maintains free lists for stack slots that won't be used again, so
535/// that we can reuse them and minimize the number of stack slots we need to
536/// allocate.
537#[derive(Default)]
538struct StackSlots {
539    /// A mapping from each needs-stack-map value that is live across some
540    /// safepoint to the stack slot that it resides within. Note that if a
541    /// needs-stack-map value is never live across a safepoint, then we won't
542    /// ever add it to this map, it can remain in a virtual register for the
543    /// duration of its lifetime, and we won't replace all its uses with reloads
544    /// and all that stuff.
545    stack_slots: HashMap<ir::Value, ir::StackSlot>,
546
547    /// A map from slot size to free stack slots that are not being used
548    /// anymore. This allows us to reuse stack slots across multiple values
549    /// helps cut down on the ultimate size of our stack frames.
550    free_stack_slots: SlotSizeMap<SmallVec<[ir::StackSlot; 4]>>,
551}
552
553impl StackSlots {
554    fn clear(&mut self) {
555        let StackSlots {
556            stack_slots,
557            free_stack_slots,
558        } = self;
559        stack_slots.clear();
560        free_stack_slots.clear();
561    }
562
563    fn get(&self, val: ir::Value) -> Option<ir::StackSlot> {
564        self.stack_slots.get(&val).copied()
565    }
566
567    fn get_or_create_stack_slot(&mut self, func: &mut Function, val: ir::Value) -> ir::StackSlot {
568        debug_assert!(!func.dfg.value_is_alias(val));
569        *self.stack_slots.entry(val).or_insert_with(|| {
570            log::trace!("rewriting:     {val:?} needs a stack slot");
571            let ty = func.dfg.value_type(val);
572            let size = ty.bytes();
573            match self.free_stack_slots[SlotSize::unwrap_new(size)].pop() {
574                Some(slot) => {
575                    log::trace!("rewriting:       reusing free stack slot {slot:?} for {val:?}");
576                    slot
577                }
578                None => {
579                    debug_assert!(size.is_power_of_two());
580                    let log2_size = size.ilog2();
581                    let slot = func.create_sized_stack_slot(ir::StackSlotData::new(
582                        ir::StackSlotKind::ExplicitSlot,
583                        size,
584                        log2_size.try_into().unwrap(),
585                    ));
586                    log::trace!("rewriting:       created new stack slot {slot:?} for {val:?}");
587                    slot
588                }
589            }
590        })
591    }
592
593    fn free_stack_slot(&mut self, size: SlotSize, slot: ir::StackSlot) {
594        log::trace!("rewriting:     returning {slot:?} to the free list");
595        self.free_stack_slots[size].push(slot);
596    }
597}
598
599/// A pass to rewrite a function's instructions to spill and reload values that
600/// are live across safepoints.
601///
602/// A single `SafepointSpiller` instance may be reused to rewrite many
603/// functions, amortizing the cost of its internal allocations and avoiding
604/// repeated `malloc` and `free` calls.
605#[derive(Default)]
606pub(super) struct SafepointSpiller {
607    liveness: LivenessAnalysis,
608    stack_slots: StackSlots,
609
610    /// Optional embedder callback used to assign an alias region to the loads
611    /// and stores emitted when spilling and reloading values that are live
612    /// across safepoints.
613    pub(super) make_alias_region: Option<
614        Box<
615            dyn Fn(&mut ir::AliasRegionSet, ir::Type, ir::StackSlot, u32) -> Option<ir::AliasRegion>
616                + Send
617                + Sync,
618        >,
619    >,
620}
621
622impl SafepointSpiller {
623    /// Clear and reset all internal state such that this pass is ready to run
624    /// on a new function.
625    pub fn clear(&mut self) {
626        let SafepointSpiller {
627            liveness,
628            stack_slots,
629            make_alias_region: _,
630        } = self;
631        liveness.clear();
632        stack_slots.clear();
633    }
634
635    /// Identify needs-stack-map values that are live across safepoints, and
636    /// rewrite the function's instructions to spill and reload them as
637    /// necessary.
638    pub fn run(
639        &mut self,
640        func: &mut Function,
641        stack_map_values: &EntitySet<ir::Value>,
642        pointer_type: ir::Type,
643    ) {
644        log::trace!("values needing inclusion in stack maps: {stack_map_values:?}");
645        log::trace!(
646            "before inserting safepoint spills and reloads:\n{}",
647            func.display()
648        );
649
650        self.clear();
651        self.liveness.run(func, stack_map_values);
652        self.rewrite(func, pointer_type);
653
654        log::trace!(
655            "after inserting safepoint spills and reloads:\n{}",
656            func.display()
657        );
658    }
659
660    /// Spill this value to a stack slot if it has been declared that it must be
661    /// included in stack maps and is live across any safepoints.
662    ///
663    /// The given cursor must point just after this value's definition.
664    fn rewrite_def(&mut self, pos: &mut FuncCursor<'_>, val: ir::Value, pointer_type: ir::Type) {
665        debug_assert!(!pos.func.dfg.value_is_alias(val));
666        if let Some(slot) = self.stack_slots.get(val) {
667            let ty = pos.func.dfg.value_type(val);
668
669            let mut flags = ir::MemFlagsData::trusted();
670            flags.set_notrap();
671            if let Some(make_alias_region) = &self.make_alias_region {
672                if let Some(region) =
673                    make_alias_region(&mut pos.func.dfg.alias_regions, ty, slot, 0)
674                {
675                    flags.set_alias_region(Some(region));
676                }
677            }
678
679            let addr = pos.ins().stack_addr(pointer_type, slot, 0);
680            let inst = pos.ins().store(flags, val, addr, 0);
681            log::trace!(
682                "rewriting:   spilling {val:?} to {slot:?}: {}",
683                pos.func.dfg.display_inst(inst)
684            );
685
686            // Now that we've defined this value, there cannot be any more uses
687            // of it, and therefore this stack slot is now available for reuse.
688            let size = SlotSize::try_from(ty).unwrap();
689            self.stack_slots.free_stack_slot(size, slot);
690        }
691    }
692
693    /// Add a stack map entry for each needs-stack-map value that is live across
694    /// the given safepoint instruction.
695    ///
696    /// This will additionally assign stack slots to needs-stack-map values, if
697    /// no such assignment has already been made.
698    fn rewrite_safepoint(&mut self, func: &mut Function, inst: ir::Inst) {
699        log::trace!(
700            "rewriting:   found safepoint: {inst:?}: {}",
701            func.dfg.display_inst(inst)
702        );
703
704        let live = self
705            .liveness
706            .safepoints
707            .get(&inst)
708            .expect("should only call `rewrite_safepoint` on safepoint instructions");
709
710        for val in live {
711            debug_assert!(!func.dfg.value_is_alias(*val));
712
713            // Get or create the stack slot for this live needs-stack-map value.
714            let slot = self.stack_slots.get_or_create_stack_slot(func, *val);
715
716            log::trace!(
717                "rewriting:     adding stack map entry for {val:?} at {slot:?}: {}",
718                func.dfg.display_inst(inst)
719            );
720            let ty = func.dfg.value_type(*val);
721            func.dfg.append_user_stack_map_entry(
722                inst,
723                ir::UserStackMapEntry {
724                    ty,
725                    slot,
726                    offset: 0,
727                },
728            );
729        }
730    }
731
732    /// If `val` is a needs-stack-map value that has been spilled to a stack
733    /// slot, then rewrite `val` to be a load from its associated stack
734    /// slot.
735    ///
736    /// Returns `true` if `val` was rewritten, `false` if not.
737    ///
738    /// The given cursor must point just before the use of the value that we are
739    /// replacing.
740    fn rewrite_use(
741        &mut self,
742        pos: &mut FuncCursor<'_>,
743        val: &mut ir::Value,
744        pointer_type: ir::Type,
745    ) -> bool {
746        debug_assert!(!pos.func.dfg.value_is_alias(*val));
747        if !self.liveness.live_across_any_safepoint.contains(*val) {
748            return false;
749        }
750
751        let old_val = *val;
752        log::trace!("rewriting:     found use of {old_val:?}");
753
754        let ty = pos.func.dfg.value_type(*val);
755        let slot = self.stack_slots.get_or_create_stack_slot(pos.func, *val);
756
757        let mut flags = ir::MemFlagsData::trusted();
758        flags.set_notrap();
759        if let Some(make_alias_region) = &self.make_alias_region {
760            if let Some(region) = make_alias_region(&mut pos.func.dfg.alias_regions, ty, slot, 0) {
761                flags.set_alias_region(Some(region));
762            }
763        }
764
765        let addr = pos.ins().stack_addr(pointer_type, slot, 0);
766        *val = pos.ins().load(ty, flags, addr, 0);
767
768        log::trace!(
769            "rewriting:     reloading {old_val:?}: {}",
770            pos.func
771                .dfg
772                .display_inst(pos.func.dfg.value_def(*val).unwrap_inst())
773        );
774
775        true
776    }
777
778    /// Rewrite the function's instructions to spill and reload values that are
779    /// live across safepoints:
780    ///
781    /// 1. Definitions of needs-stack-map values that are live across some
782    ///    safepoint need to be spilled to their assigned stack slot.
783    ///
784    /// 2. Instructions that are themselves safepoints must have stack map
785    ///    entries added for the needs-stack-map values that are live across
786    ///    them.
787    ///
788    /// 3. Uses of needs-stack-map values that have been spilled to a stack slot
789    ///    need to be replaced with reloads from the slot.
790    fn rewrite(&mut self, func: &mut Function, pointer_type: ir::Type) {
791        // Shared temporary storage for operand and result lists.
792        let mut vals: SmallVec<[_; 8]> = Default::default();
793
794        // Rewrite the function's instructions in post-order. This ensures that
795        // we rewrite uses before defs, and therefore once we see a def we know
796        // its stack slot will never be used for that value again. Therefore,
797        // the slot can be reappropriated for a new needs-stack-map value with a
798        // non-overlapping live range. See `rewrite_def` and `free_stack_slots`
799        // for more details.
800        for block_index in 0..self.liveness.post_order.len() {
801            let block = self.liveness.post_order[block_index];
802            log::trace!("rewriting: processing {block:?}");
803
804            // Eagerly allocate all stack slots for values that are live-out in
805            // this block. This reserves a loop-invariant value's stack slot
806            // across the whole loop, rather than just at the first use site we
807            // see within the loop, which could otherwise lead to incorrect
808            // stack slot reuse.
809            vals.extend(
810                self.liveness.live_outs[block_index]
811                    .iter()
812                    .copied()
813                    .filter(|val| self.liveness.live_across_any_safepoint.contains(*val)),
814            );
815            vals.sort_unstable();
816            for val in vals.drain(..) {
817                self.stack_slots.get_or_create_stack_slot(func, val);
818            }
819
820            let mut option_inst = func.layout.last_inst(block);
821            while let Some(inst) = option_inst {
822                // If this instruction defines a needs-stack-map value that is
823                // live across a safepoint, then spill the value to its stack
824                // slot.
825                let mut pos = FuncCursor::new(func).after_inst(inst);
826                vals.extend_from_slice(pos.func.dfg.inst_results(inst));
827                for val in vals.drain(..) {
828                    self.rewrite_def(&mut pos, val, pointer_type);
829                }
830
831                // If this instruction is a safepoint, then we must add stack
832                // map entries for the needs-stack-map values that are live
833                // across it.
834                if self.liveness.safepoints.contains_key(&inst) {
835                    self.rewrite_safepoint(func, inst);
836                }
837
838                // Replace all uses of needs-stack-map values with loads from
839                // the value's associated stack slot.
840                let mut pos = FuncCursor::new(func).at_inst(inst);
841                vals.extend(pos.func.dfg.inst_values(inst));
842                let mut replaced_any = false;
843                for val in &mut vals {
844                    *val = pos.func.dfg.resolve_aliases(*val);
845                    replaced_any |= self.rewrite_use(&mut pos, val, pointer_type);
846                }
847                if replaced_any {
848                    pos.func.dfg.overwrite_inst_values(inst, vals.drain(..));
849                    log::trace!(
850                        "rewriting:     updated {inst:?} operands with reloaded values: {}",
851                        pos.func.dfg.display_inst(inst)
852                    );
853                } else {
854                    vals.clear();
855                }
856
857                option_inst = func.layout.prev_inst(inst);
858            }
859
860            // Spill needs-stack-map values defined by block parameters to their
861            // associated stack slots.
862            let mut pos = FuncCursor::new(func).at_position(CursorPosition::Before(block));
863            pos.next_inst(); // Advance to the first instruction in the block.
864            vals.clear();
865            vals.extend_from_slice(pos.func.dfg.block_params(block));
866            for val in vals.drain(..) {
867                self.rewrite_def(&mut pos, val, pointer_type);
868            }
869        }
870    }
871}
872
873#[cfg(test)]
874mod tests {
875    use super::*;
876    use alloc::string::ToString;
877    use cranelift_codegen::ir::{BlockCall, ExceptionTableData};
878    use cranelift_codegen::isa::{CallConv, TargetFrontendConfig};
879
880    fn systemv_frontend_config() -> TargetFrontendConfig {
881        TargetFrontendConfig {
882            default_call_conv: CallConv::SystemV,
883            pointer_width: target_lexicon::PointerWidth::U64,
884            page_size_align_log2: 12,
885        }
886    }
887
888    #[test]
889    fn needs_stack_map_and_loop() {
890        let mut sig = Signature::new(CallConv::SystemV);
891        sig.params.push(AbiParam::new(ir::types::I32));
892        sig.params.push(AbiParam::new(ir::types::I32));
893
894        let mut fn_ctx = FunctionBuilderContext::new();
895        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
896        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
897
898        let name = builder
899            .func
900            .declare_imported_user_function(ir::UserExternalName {
901                namespace: 0,
902                index: 0,
903            });
904        let mut sig = Signature::new(CallConv::SystemV);
905        sig.params.push(AbiParam::new(ir::types::I32));
906        let signature = builder.func.import_signature(sig);
907        let func_ref = builder.import_function(ir::ExtFuncData {
908            name: ir::ExternalName::user(name),
909            signature,
910            colocated: true,
911            patchable: false,
912        });
913
914        // Here the value `v1` is technically not live but our single-pass liveness
915        // analysis treats every branch argument to a block as live to avoid
916        // needing to do a fixed-point loop.
917        //
918        //     block0(v0, v1):
919        //       call $foo(v0)
920        //       jump block0(v0, v1)
921        let block0 = builder.create_block();
922        builder.append_block_params_for_function_params(block0);
923        let a = builder.func.dfg.block_params(block0)[0];
924        let b = builder.func.dfg.block_params(block0)[1];
925        builder.declare_value_needs_stack_map(a);
926        builder.declare_value_needs_stack_map(b);
927        builder.switch_to_block(block0);
928        builder.ins().call(func_ref, &[a]);
929        builder.ins().jump(block0, &[a.into(), b.into()]);
930        builder.seal_all_blocks();
931        builder.finalize(systemv_frontend_config());
932
933        assert_eq_output!(
934            func.display().to_string(),
935            r#"
936function %sample(i32, i32) system_v {
937    ss0 = explicit_slot 4, align = 4
938    ss1 = explicit_slot 4, align = 4
939    sig0 = (i32) system_v
940    fn0 = colocated u0:0 sig0
941
942block0(v0: i32, v1: i32):
943    v8 = stack_addr.i64 ss0
944    store notrap aligned v0, v8
945    v9 = stack_addr.i64 ss1
946    store notrap aligned v1, v9
947    v6 = stack_addr.i64 ss0
948    v7 = load.i32 notrap aligned v6
949    call fn0(v7), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
950    v2 = stack_addr.i64 ss0
951    v3 = load.i32 notrap aligned v2
952    v4 = stack_addr.i64 ss1
953    v5 = load.i32 notrap aligned v4
954    jump block0(v3, v5)
955}
956            "#
957        );
958    }
959
960    #[test]
961    fn needs_stack_map_simple() {
962        let _ = env_logger::try_init();
963
964        let sig = Signature::new(CallConv::SystemV);
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 mut sig = Signature::new(CallConv::SystemV);
977        sig.params.push(AbiParam::new(ir::types::I32));
978        let signature = builder.func.import_signature(sig);
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        // At each `call` we are losing one more value as no longer live, so
987        // each stack map should be one smaller than the last. `v3` is never
988        // live across a safepoint, so should never appear in a stack map. Note
989        // that a value that is an argument to the call, but is not live after
990        // the call, should not appear in the stack map. This is why `v0`
991        // appears in the first call's stack map, but not the second call's
992        // stack map.
993        //
994        //     block0:
995        //       v0 = needs stack map
996        //       v1 = needs stack map
997        //       v2 = needs stack map
998        //       v3 = needs stack map
999        //       call $foo(v3)
1000        //       call $foo(v0)
1001        //       call $foo(v1)
1002        //       call $foo(v2)
1003        //       return
1004        let block0 = builder.create_block();
1005        builder.append_block_params_for_function_params(block0);
1006        builder.switch_to_block(block0);
1007        let v0 = builder.ins().iconst(ir::types::I32, 0);
1008        builder.declare_value_needs_stack_map(v0);
1009        let v1 = builder.ins().iconst(ir::types::I32, 1);
1010        builder.declare_value_needs_stack_map(v1);
1011        let v2 = builder.ins().iconst(ir::types::I32, 2);
1012        builder.declare_value_needs_stack_map(v2);
1013        let v3 = builder.ins().iconst(ir::types::I32, 3);
1014        builder.declare_value_needs_stack_map(v3);
1015        builder.ins().call(func_ref, &[v3]);
1016        builder.ins().call(func_ref, &[v0]);
1017        builder.ins().call(func_ref, &[v1]);
1018        builder.ins().call(func_ref, &[v2]);
1019        builder.ins().return_(&[]);
1020        builder.seal_all_blocks();
1021        builder.finalize(systemv_frontend_config());
1022
1023        assert_eq_output!(
1024            func.display().to_string(),
1025            r#"
1026function %sample() system_v {
1027    ss0 = explicit_slot 4, align = 4
1028    ss1 = explicit_slot 4, align = 4
1029    ss2 = explicit_slot 4, align = 4
1030    sig0 = (i32) system_v
1031    fn0 = colocated u0:0 sig0
1032
1033block0:
1034    v0 = iconst.i32 0
1035    v12 = stack_addr.i64 ss2
1036    store notrap aligned v0, v12  ; v0 = 0
1037    v1 = iconst.i32 1
1038    v11 = stack_addr.i64 ss1
1039    store notrap aligned v1, v11  ; v1 = 1
1040    v2 = iconst.i32 2
1041    v10 = stack_addr.i64 ss0
1042    store notrap aligned v2, v10  ; v2 = 2
1043    v3 = iconst.i32 3
1044    call fn0(v3), stack_map=[i32 @ ss2+0, i32 @ ss1+0, i32 @ ss0+0]  ; v3 = 3
1045    v8 = stack_addr.i64 ss2
1046    v9 = load.i32 notrap aligned v8
1047    call fn0(v9), stack_map=[i32 @ ss1+0, i32 @ ss0+0]
1048    v6 = stack_addr.i64 ss1
1049    v7 = load.i32 notrap aligned v6
1050    call fn0(v7), stack_map=[i32 @ ss0+0]
1051    v4 = stack_addr.i64 ss0
1052    v5 = load.i32 notrap aligned v4
1053    call fn0(v5)
1054    return
1055}
1056            "#
1057        );
1058    }
1059
1060    #[test]
1061    fn needs_stack_map_and_post_order_early_return() {
1062        let mut sig = Signature::new(CallConv::SystemV);
1063        sig.params.push(AbiParam::new(ir::types::I32));
1064
1065        let mut fn_ctx = FunctionBuilderContext::new();
1066        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1067        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1068
1069        let name = builder
1070            .func
1071            .declare_imported_user_function(ir::UserExternalName {
1072                namespace: 0,
1073                index: 0,
1074            });
1075        let signature = builder
1076            .func
1077            .import_signature(Signature::new(CallConv::SystemV));
1078        let func_ref = builder.import_function(ir::ExtFuncData {
1079            name: ir::ExternalName::user(name),
1080            signature,
1081            colocated: true,
1082            patchable: false,
1083        });
1084
1085        // Here we rely on the post-order to make sure that we never visit block
1086        // 4 and add `v1` to our live set, then visit block 2 and add `v1` to
1087        // its stack map even though `v1` is not in scope. Thanksfully, that
1088        // sequence is impossible because it would be an invalid post-order
1089        // traversal. The only valid post-order traversals are [3, 1, 2, 0] and
1090        // [2, 3, 1, 0].
1091        //
1092        //     block0(v0):
1093        //       brif v0, block1, block2
1094        //
1095        //     block1:
1096        //       <stuff>
1097        //       v1 = get some gc ref
1098        //       jump block3
1099        //
1100        //     block2:
1101        //       call $needs_safepoint_accidentally
1102        //       return
1103        //
1104        //     block3:
1105        //       stuff keeping v1 live
1106        //       return
1107        let block0 = builder.create_block();
1108        let block1 = builder.create_block();
1109        let block2 = builder.create_block();
1110        let block3 = builder.create_block();
1111        builder.append_block_params_for_function_params(block0);
1112
1113        builder.switch_to_block(block0);
1114        let v0 = builder.func.dfg.block_params(block0)[0];
1115        builder.ins().brif(v0, block1, &[], block2, &[]);
1116
1117        builder.switch_to_block(block1);
1118        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1119        builder.declare_value_needs_stack_map(v1);
1120        builder.ins().jump(block3, &[]);
1121
1122        builder.switch_to_block(block2);
1123        builder.ins().call(func_ref, &[]);
1124        builder.ins().return_(&[]);
1125
1126        builder.switch_to_block(block3);
1127        // NB: Our simplistic liveness analysis conservatively treats any use of
1128        // a value as keeping it live, regardless if the use has side effects or
1129        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1130        // here.
1131        builder.ins().iadd_imm_s(v1, 0);
1132        builder.ins().return_(&[]);
1133
1134        builder.seal_all_blocks();
1135        builder.finalize(systemv_frontend_config());
1136
1137        assert_eq_output!(
1138            func.display().to_string(),
1139            r#"
1140function %sample(i32) system_v {
1141    sig0 = () system_v
1142    fn0 = colocated u0:0 sig0
1143
1144block0(v0: i32):
1145    brif v0, block1, block2
1146
1147block1:
1148    v1 = iconst.i64 0x1234_5678
1149    jump block3
1150
1151block2:
1152    call fn0()
1153    return
1154
1155block3:
1156    v2 = iconst.i64 0
1157    v3 = iadd.i64 v1, v2  ; v1 = 0x1234_5678, v2 = 0
1158    return
1159}
1160            "#
1161        );
1162    }
1163
1164    #[test]
1165    fn needs_stack_map_conditional_branches_and_liveness() {
1166        let mut sig = Signature::new(CallConv::SystemV);
1167        sig.params.push(AbiParam::new(ir::types::I32));
1168
1169        let mut fn_ctx = FunctionBuilderContext::new();
1170        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1171        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1172
1173        let name = builder
1174            .func
1175            .declare_imported_user_function(ir::UserExternalName {
1176                namespace: 0,
1177                index: 0,
1178            });
1179        let signature = builder
1180            .func
1181            .import_signature(Signature::new(CallConv::SystemV));
1182        let func_ref = builder.import_function(ir::ExtFuncData {
1183            name: ir::ExternalName::user(name),
1184            signature,
1185            colocated: true,
1186            patchable: false,
1187        });
1188
1189        // We should not have a stack map entry for `v1` in block 1 because it
1190        // is not live across the call.
1191        //
1192        //     block0(v0):
1193        //       v1 = needs stack map
1194        //       brif v0, block1, block2
1195        //
1196        //     block1:
1197        //       call $foo()
1198        //       return
1199        //
1200        //     block2:
1201        //       keep v1 alive
1202        //       return
1203        let block0 = builder.create_block();
1204        let block1 = builder.create_block();
1205        let block2 = builder.create_block();
1206        builder.append_block_params_for_function_params(block0);
1207
1208        builder.switch_to_block(block0);
1209        let v0 = builder.func.dfg.block_params(block0)[0];
1210        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1211        builder.declare_value_needs_stack_map(v1);
1212        builder.ins().brif(v0, block1, &[], block2, &[]);
1213
1214        builder.switch_to_block(block1);
1215        builder.ins().call(func_ref, &[]);
1216        builder.ins().return_(&[]);
1217
1218        builder.switch_to_block(block2);
1219        // NB: Our simplistic liveness analysis conservatively treats any use of
1220        // a value as keeping it live, regardless if the use has side effects or
1221        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1222        // here.
1223        builder.ins().iadd_imm_s(v1, 0);
1224        builder.ins().return_(&[]);
1225
1226        builder.seal_all_blocks();
1227        builder.finalize(systemv_frontend_config());
1228
1229        assert_eq_output!(
1230            func.display().to_string(),
1231            r#"
1232function %sample(i32) system_v {
1233    sig0 = () system_v
1234    fn0 = colocated u0:0 sig0
1235
1236block0(v0: i32):
1237    v1 = iconst.i64 0x1234_5678
1238    brif v0, block1, block2
1239
1240block1:
1241    call fn0()
1242    return
1243
1244block2:
1245    v2 = iconst.i64 0
1246    v3 = iadd.i64 v1, v2  ; v1 = 0x1234_5678, v2 = 0
1247    return
1248}
1249            "#
1250        );
1251
1252        // Now do the same test but with block 1 and 2 swapped so that we
1253        // exercise what we are trying to exercise, regardless of which
1254        // post-order traversal we happen to take.
1255        func.clear();
1256        fn_ctx.clear();
1257
1258        let mut sig = Signature::new(CallConv::SystemV);
1259        sig.params.push(AbiParam::new(ir::types::I32));
1260
1261        func.signature = sig;
1262        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1263
1264        let name = builder
1265            .func
1266            .declare_imported_user_function(ir::UserExternalName {
1267                namespace: 0,
1268                index: 0,
1269            });
1270        let signature = builder
1271            .func
1272            .import_signature(Signature::new(CallConv::SystemV));
1273        let func_ref = builder.import_function(ir::ExtFuncData {
1274            name: ir::ExternalName::user(name),
1275            signature,
1276            colocated: true,
1277            patchable: false,
1278        });
1279
1280        let block0 = builder.create_block();
1281        let block1 = builder.create_block();
1282        let block2 = builder.create_block();
1283        builder.append_block_params_for_function_params(block0);
1284
1285        builder.switch_to_block(block0);
1286        let v0 = builder.func.dfg.block_params(block0)[0];
1287        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1288        builder.declare_value_needs_stack_map(v1);
1289        builder.ins().brif(v0, block1, &[], block2, &[]);
1290
1291        builder.switch_to_block(block1);
1292        builder.ins().iadd_imm_s(v1, 0);
1293        builder.ins().return_(&[]);
1294
1295        builder.switch_to_block(block2);
1296        builder.ins().call(func_ref, &[]);
1297        builder.ins().return_(&[]);
1298
1299        builder.seal_all_blocks();
1300        builder.finalize(systemv_frontend_config());
1301
1302        assert_eq_output!(
1303            func.display().to_string(),
1304            r#"
1305function u0:0(i32) system_v {
1306    sig0 = () system_v
1307    fn0 = colocated u0:0 sig0
1308
1309block0(v0: i32):
1310    v1 = iconst.i64 0x1234_5678
1311    brif v0, block1, block2
1312
1313block1:
1314    v2 = iconst.i64 0
1315    v3 = iadd.i64 v1, v2  ; v1 = 0x1234_5678, v2 = 0
1316    return
1317
1318block2:
1319    call fn0()
1320    return
1321}
1322            "#
1323        );
1324    }
1325
1326    #[test]
1327    fn needs_stack_map_and_tail_calls() {
1328        let mut sig = Signature::new(CallConv::SystemV);
1329        sig.params.push(AbiParam::new(ir::types::I32));
1330
1331        let mut fn_ctx = FunctionBuilderContext::new();
1332        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1333        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1334
1335        let name = builder
1336            .func
1337            .declare_imported_user_function(ir::UserExternalName {
1338                namespace: 0,
1339                index: 0,
1340            });
1341        let signature = builder
1342            .func
1343            .import_signature(Signature::new(CallConv::SystemV));
1344        let func_ref = builder.import_function(ir::ExtFuncData {
1345            name: ir::ExternalName::user(name),
1346            signature,
1347            colocated: true,
1348            patchable: false,
1349        });
1350
1351        // Depending on which post-order traversal we take, we might consider
1352        // `v1` live inside `block1`. But nothing is live after a tail call so
1353        // we shouldn't spill `v1` either way here.
1354        //
1355        //     block0(v0):
1356        //       v1 = needs stack map
1357        //       brif v0, block1, block2
1358        //
1359        //     block1:
1360        //       return_call $foo()
1361        //
1362        //     block2:
1363        //       keep v1 alive
1364        //       return
1365        let block0 = builder.create_block();
1366        let block1 = builder.create_block();
1367        let block2 = builder.create_block();
1368        builder.append_block_params_for_function_params(block0);
1369
1370        builder.switch_to_block(block0);
1371        let v0 = builder.func.dfg.block_params(block0)[0];
1372        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1373        builder.declare_value_needs_stack_map(v1);
1374        builder.ins().brif(v0, block1, &[], block2, &[]);
1375
1376        builder.switch_to_block(block1);
1377        builder.ins().return_call(func_ref, &[]);
1378
1379        builder.switch_to_block(block2);
1380        // NB: Our simplistic liveness analysis conservatively treats any use of
1381        // a value as keeping it live, regardless if the use has side effects or
1382        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1383        // here.
1384        builder.ins().iadd_imm_s(v1, 0);
1385        builder.ins().return_(&[]);
1386
1387        builder.seal_all_blocks();
1388        builder.finalize(systemv_frontend_config());
1389
1390        assert_eq_output!(
1391            func.display().to_string(),
1392            r#"
1393function %sample(i32) system_v {
1394    sig0 = () system_v
1395    fn0 = colocated u0:0 sig0
1396
1397block0(v0: i32):
1398    v1 = iconst.i64 0x1234_5678
1399    brif v0, block1, block2
1400
1401block1:
1402    return_call fn0()
1403
1404block2:
1405    v2 = iconst.i64 0
1406    v3 = iadd.i64 v1, v2  ; v1 = 0x1234_5678, v2 = 0
1407    return
1408}
1409            "#
1410        );
1411
1412        // Do the same test but with block 1 and 2 swapped so that we exercise
1413        // what we are trying to exercise, regardless of which post-order
1414        // traversal we happen to take.
1415        func.clear();
1416        fn_ctx.clear();
1417
1418        let mut sig = Signature::new(CallConv::SystemV);
1419        sig.params.push(AbiParam::new(ir::types::I32));
1420        func.signature = sig;
1421
1422        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1423
1424        let name = builder
1425            .func
1426            .declare_imported_user_function(ir::UserExternalName {
1427                namespace: 0,
1428                index: 0,
1429            });
1430        let signature = builder
1431            .func
1432            .import_signature(Signature::new(CallConv::SystemV));
1433        let func_ref = builder.import_function(ir::ExtFuncData {
1434            name: ir::ExternalName::user(name),
1435            signature,
1436            colocated: true,
1437            patchable: false,
1438        });
1439
1440        let block0 = builder.create_block();
1441        let block1 = builder.create_block();
1442        let block2 = builder.create_block();
1443        builder.append_block_params_for_function_params(block0);
1444
1445        builder.switch_to_block(block0);
1446        let v0 = builder.func.dfg.block_params(block0)[0];
1447        let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
1448        builder.declare_value_needs_stack_map(v1);
1449        builder.ins().brif(v0, block1, &[], block2, &[]);
1450
1451        builder.switch_to_block(block1);
1452        builder.ins().iadd_imm_s(v1, 0);
1453        builder.ins().return_(&[]);
1454
1455        builder.switch_to_block(block2);
1456        builder.ins().return_call(func_ref, &[]);
1457
1458        builder.seal_all_blocks();
1459        builder.finalize(systemv_frontend_config());
1460
1461        assert_eq_output!(
1462            func.display().to_string(),
1463            r#"
1464function u0:0(i32) system_v {
1465    sig0 = () system_v
1466    fn0 = colocated u0:0 sig0
1467
1468block0(v0: i32):
1469    v1 = iconst.i64 0x1234_5678
1470    brif v0, block1, block2
1471
1472block1:
1473    v2 = iconst.i64 0
1474    v3 = iadd.i64 v1, v2  ; v1 = 0x1234_5678, v2 = 0
1475    return
1476
1477block2:
1478    return_call fn0()
1479}
1480            "#
1481        );
1482    }
1483
1484    #[test]
1485    fn needs_stack_map_and_cfg_diamond() {
1486        let mut sig = Signature::new(CallConv::SystemV);
1487        sig.params.push(AbiParam::new(ir::types::I32));
1488
1489        let mut fn_ctx = FunctionBuilderContext::new();
1490        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1491        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1492
1493        let name = builder
1494            .func
1495            .declare_imported_user_function(ir::UserExternalName {
1496                namespace: 0,
1497                index: 0,
1498            });
1499        let signature = builder
1500            .func
1501            .import_signature(Signature::new(CallConv::SystemV));
1502        let func_ref = builder.import_function(ir::ExtFuncData {
1503            name: ir::ExternalName::user(name),
1504            signature,
1505            colocated: true,
1506            patchable: false,
1507        });
1508
1509        // Create an if/else CFG diamond that and check that various things get
1510        // spilled as needed.
1511        //
1512        //     block0(v0):
1513        //       brif v0, block1, block2
1514        //
1515        //     block1:
1516        //       v1 = needs stack map
1517        //       v2 = needs stack map
1518        //       call $foo()
1519        //       jump block3(v1, v2)
1520        //
1521        //     block2:
1522        //       v3 = needs stack map
1523        //       v4 = needs stack map
1524        //       call $foo()
1525        //       jump block3(v3, v3)  ;; Note: v4 is not live
1526        //
1527        //     block3(v5, v6):
1528        //       call $foo()
1529        //       keep v5 alive, but not v6
1530        let block0 = builder.create_block();
1531        let block1 = builder.create_block();
1532        let block2 = builder.create_block();
1533        let block3 = builder.create_block();
1534        builder.append_block_params_for_function_params(block0);
1535
1536        builder.switch_to_block(block0);
1537        let v0 = builder.func.dfg.block_params(block0)[0];
1538        builder.ins().brif(v0, block1, &[], block2, &[]);
1539
1540        builder.switch_to_block(block1);
1541        let v1 = builder.ins().iconst(ir::types::I64, 1);
1542        builder.declare_value_needs_stack_map(v1);
1543        let v2 = builder.ins().iconst(ir::types::I64, 2);
1544        builder.declare_value_needs_stack_map(v2);
1545        builder.ins().call(func_ref, &[]);
1546        builder.ins().jump(block3, &[v1.into(), v2.into()]);
1547
1548        builder.switch_to_block(block2);
1549        let v3 = builder.ins().iconst(ir::types::I64, 3);
1550        builder.declare_value_needs_stack_map(v3);
1551        let v4 = builder.ins().iconst(ir::types::I64, 4);
1552        builder.declare_value_needs_stack_map(v4);
1553        builder.ins().call(func_ref, &[]);
1554        builder.ins().jump(block3, &[v3.into(), v3.into()]);
1555
1556        builder.switch_to_block(block3);
1557        builder.append_block_param(block3, ir::types::I64);
1558        builder.append_block_param(block3, ir::types::I64);
1559        builder.ins().call(func_ref, &[]);
1560        // NB: Our simplistic liveness analysis conservatively treats any use of
1561        // a value as keeping it live, regardless if the use has side effects or
1562        // is otherwise itself live, so an `iadd_imm` suffices to keep `v1` live
1563        // here.
1564        builder.ins().iadd_imm_s(v1, 0);
1565        builder.ins().return_(&[]);
1566
1567        builder.seal_all_blocks();
1568        builder.finalize(systemv_frontend_config());
1569
1570        assert_eq_output!(
1571            func.display().to_string(),
1572            r#"
1573function %sample(i32) system_v {
1574    ss0 = explicit_slot 8, align = 8
1575    ss1 = explicit_slot 8, align = 8
1576    sig0 = () system_v
1577    fn0 = colocated u0:0 sig0
1578
1579block0(v0: i32):
1580    brif v0, block1, block2
1581
1582block1:
1583    v1 = iconst.i64 1
1584    v16 = stack_addr.i64 ss0
1585    store notrap aligned v1, v16  ; v1 = 1
1586    v2 = iconst.i64 2
1587    v15 = stack_addr.i64 ss1
1588    store notrap aligned v2, v15  ; v2 = 2
1589    call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss1+0]
1590    v11 = stack_addr.i64 ss0
1591    v12 = load.i64 notrap aligned v11
1592    v13 = stack_addr.i64 ss1
1593    v14 = load.i64 notrap aligned v13
1594    jump block3(v12, v14)
1595
1596block2:
1597    v3 = iconst.i64 3
1598    v21 = stack_addr.i64 ss0
1599    store notrap aligned v3, v21  ; v3 = 3
1600    v4 = iconst.i64 4
1601    call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss0+0]
1602    v17 = stack_addr.i64 ss0
1603    v18 = load.i64 notrap aligned v17
1604    v19 = stack_addr.i64 ss0
1605    v20 = load.i64 notrap aligned v19
1606    jump block3(v18, v20)
1607
1608block3(v5: i64, v6: i64):
1609    call fn0(), stack_map=[i64 @ ss0+0]
1610    v7 = iconst.i64 0
1611    v9 = stack_addr.i64 ss0
1612    v10 = load.i64 notrap aligned v9
1613    v8 = iadd v10, v7  ; v7 = 0
1614    return
1615}
1616            "#
1617        );
1618    }
1619
1620    #[test]
1621    fn needs_stack_map_and_heterogeneous_types() {
1622        let _ = env_logger::try_init();
1623
1624        let mut sig = Signature::new(CallConv::SystemV);
1625        for ty in [
1626            ir::types::I8,
1627            ir::types::I16,
1628            ir::types::I32,
1629            ir::types::I64,
1630            ir::types::I128,
1631            ir::types::F32,
1632            ir::types::F64,
1633            ir::types::I8X16,
1634            ir::types::I16X8,
1635        ] {
1636            sig.params.push(AbiParam::new(ty));
1637            sig.returns.push(AbiParam::new(ty));
1638        }
1639
1640        let mut fn_ctx = FunctionBuilderContext::new();
1641        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1642        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1643
1644        let name = builder
1645            .func
1646            .declare_imported_user_function(ir::UserExternalName {
1647                namespace: 0,
1648                index: 0,
1649            });
1650        let signature = builder
1651            .func
1652            .import_signature(Signature::new(CallConv::SystemV));
1653        let func_ref = builder.import_function(ir::ExtFuncData {
1654            name: ir::ExternalName::user(name),
1655            signature,
1656            colocated: true,
1657            patchable: false,
1658        });
1659
1660        // Test that we support stack maps of heterogeneous types and properly
1661        // coalesce types into stack slots based on their size.
1662        //
1663        //     block0(v0, v1, v2, v3, v4, v5, v6, v7, v8):
1664        //       call $foo()
1665        //       return v0, v1, v2, v3, v4, v5, v6, v7, v8
1666        let block0 = builder.create_block();
1667        builder.append_block_params_for_function_params(block0);
1668
1669        builder.switch_to_block(block0);
1670        let params = builder.func.dfg.block_params(block0).to_vec();
1671        for val in &params {
1672            builder.declare_value_needs_stack_map(*val);
1673        }
1674        builder.ins().call(func_ref, &[]);
1675        builder.ins().return_(&params);
1676
1677        builder.seal_all_blocks();
1678        builder.finalize(systemv_frontend_config());
1679
1680        assert_eq_output!(
1681            func.display().to_string(),
1682            r#"
1683function %sample(i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8) -> i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8 system_v {
1684    ss0 = explicit_slot 1
1685    ss1 = explicit_slot 2, align = 2
1686    ss2 = explicit_slot 4, align = 4
1687    ss3 = explicit_slot 8, align = 8
1688    ss4 = explicit_slot 16, align = 16
1689    ss5 = explicit_slot 4, align = 4
1690    ss6 = explicit_slot 8, align = 8
1691    ss7 = explicit_slot 16, align = 16
1692    ss8 = explicit_slot 16, align = 16
1693    sig0 = () system_v
1694    fn0 = colocated u0:0 sig0
1695
1696block0(v0: i8, v1: i16, v2: i32, v3: i64, v4: i128, v5: f32, v6: f64, v7: i8x16, v8: i16x8):
1697    v27 = stack_addr.i64 ss0
1698    store notrap aligned v0, v27
1699    v28 = stack_addr.i64 ss1
1700    store notrap aligned v1, v28
1701    v29 = stack_addr.i64 ss2
1702    store notrap aligned v2, v29
1703    v30 = stack_addr.i64 ss3
1704    store notrap aligned v3, v30
1705    v31 = stack_addr.i64 ss4
1706    store notrap aligned v4, v31
1707    v32 = stack_addr.i64 ss5
1708    store notrap aligned v5, v32
1709    v33 = stack_addr.i64 ss6
1710    store notrap aligned v6, v33
1711    v34 = stack_addr.i64 ss7
1712    store notrap aligned v7, v34
1713    v35 = stack_addr.i64 ss8
1714    store notrap aligned v8, v35
1715    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]
1716    v9 = stack_addr.i64 ss0
1717    v10 = load.i8 notrap aligned v9
1718    v11 = stack_addr.i64 ss1
1719    v12 = load.i16 notrap aligned v11
1720    v13 = stack_addr.i64 ss2
1721    v14 = load.i32 notrap aligned v13
1722    v15 = stack_addr.i64 ss3
1723    v16 = load.i64 notrap aligned v15
1724    v17 = stack_addr.i64 ss4
1725    v18 = load.i128 notrap aligned v17
1726    v19 = stack_addr.i64 ss5
1727    v20 = load.f32 notrap aligned v19
1728    v21 = stack_addr.i64 ss6
1729    v22 = load.f64 notrap aligned v21
1730    v23 = stack_addr.i64 ss7
1731    v24 = load.i8x16 notrap aligned v23
1732    v25 = stack_addr.i64 ss8
1733    v26 = load.i16x8 notrap aligned v25
1734    return v10, v12, v14, v16, v18, v20, v22, v24, v26
1735}
1736            "#
1737        );
1738    }
1739
1740    #[test]
1741    fn series_of_non_overlapping_live_ranges_needs_stack_map() {
1742        let sig = Signature::new(CallConv::SystemV);
1743
1744        let mut fn_ctx = FunctionBuilderContext::new();
1745        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1746        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1747
1748        let name = builder
1749            .func
1750            .declare_imported_user_function(ir::UserExternalName {
1751                namespace: 0,
1752                index: 0,
1753            });
1754        let signature = builder
1755            .func
1756            .import_signature(Signature::new(CallConv::SystemV));
1757        let foo_func_ref = builder.import_function(ir::ExtFuncData {
1758            name: ir::ExternalName::user(name),
1759            signature,
1760            colocated: true,
1761            patchable: false,
1762        });
1763
1764        let name = builder
1765            .func
1766            .declare_imported_user_function(ir::UserExternalName {
1767                namespace: 0,
1768                index: 1,
1769            });
1770        let mut sig = Signature::new(CallConv::SystemV);
1771        sig.params.push(AbiParam::new(ir::types::I32));
1772        let signature = builder.func.import_signature(sig);
1773        let consume_func_ref = builder.import_function(ir::ExtFuncData {
1774            name: ir::ExternalName::user(name),
1775            signature,
1776            colocated: true,
1777            patchable: false,
1778        });
1779
1780        // Create a series of needs-stack-map values that do not have
1781        // overlapping live ranges, but which do appear in stack maps for calls
1782        // to `$foo`:
1783        //
1784        //     block0:
1785        //       v0 = needs stack map
1786        //       call $foo()
1787        //       call consume(v0)
1788        //       v1 = needs stack map
1789        //       call $foo()
1790        //       call consume(v1)
1791        //       v2 = needs stack map
1792        //       call $foo()
1793        //       call consume(v2)
1794        //       v3 = needs stack map
1795        //       call $foo()
1796        //       call consume(v3)
1797        //       return
1798        let block0 = builder.create_block();
1799        builder.append_block_params_for_function_params(block0);
1800        builder.switch_to_block(block0);
1801        let v0 = builder.ins().iconst(ir::types::I32, 0);
1802        builder.declare_value_needs_stack_map(v0);
1803        builder.ins().call(foo_func_ref, &[]);
1804        builder.ins().call(consume_func_ref, &[v0]);
1805        let v1 = builder.ins().iconst(ir::types::I32, 1);
1806        builder.declare_value_needs_stack_map(v1);
1807        builder.ins().call(foo_func_ref, &[]);
1808        builder.ins().call(consume_func_ref, &[v1]);
1809        let v2 = builder.ins().iconst(ir::types::I32, 2);
1810        builder.declare_value_needs_stack_map(v2);
1811        builder.ins().call(foo_func_ref, &[]);
1812        builder.ins().call(consume_func_ref, &[v2]);
1813        let v3 = builder.ins().iconst(ir::types::I32, 3);
1814        builder.declare_value_needs_stack_map(v3);
1815        builder.ins().call(foo_func_ref, &[]);
1816        builder.ins().call(consume_func_ref, &[v3]);
1817        builder.ins().return_(&[]);
1818        builder.seal_all_blocks();
1819        builder.finalize(systemv_frontend_config());
1820
1821        assert_eq_output!(
1822            func.display().to_string(),
1823            r#"
1824function %sample() system_v {
1825    ss0 = explicit_slot 4, align = 4
1826    sig0 = () system_v
1827    sig1 = (i32) system_v
1828    fn0 = colocated u0:0 sig0
1829    fn1 = colocated u0:1 sig1
1830
1831block0:
1832    v0 = iconst.i32 0
1833    v15 = stack_addr.i64 ss0
1834    store notrap aligned v0, v15  ; v0 = 0
1835    call fn0(), stack_map=[i32 @ ss0+0]
1836    v13 = stack_addr.i64 ss0
1837    v14 = load.i32 notrap aligned v13
1838    call fn1(v14)
1839    v1 = iconst.i32 1
1840    v12 = stack_addr.i64 ss0
1841    store notrap aligned v1, v12  ; v1 = 1
1842    call fn0(), stack_map=[i32 @ ss0+0]
1843    v10 = stack_addr.i64 ss0
1844    v11 = load.i32 notrap aligned v10
1845    call fn1(v11)
1846    v2 = iconst.i32 2
1847    v9 = stack_addr.i64 ss0
1848    store notrap aligned v2, v9  ; v2 = 2
1849    call fn0(), stack_map=[i32 @ ss0+0]
1850    v7 = stack_addr.i64 ss0
1851    v8 = load.i32 notrap aligned v7
1852    call fn1(v8)
1853    v3 = iconst.i32 3
1854    v6 = stack_addr.i64 ss0
1855    store notrap aligned v3, v6  ; v3 = 3
1856    call fn0(), stack_map=[i32 @ ss0+0]
1857    v4 = stack_addr.i64 ss0
1858    v5 = load.i32 notrap aligned v4
1859    call fn1(v5)
1860    return
1861}
1862            "#
1863        );
1864    }
1865
1866    #[test]
1867    fn vars_block_params_and_needs_stack_map() {
1868        let _ = env_logger::try_init();
1869
1870        let mut sig = Signature::new(CallConv::SystemV);
1871        sig.params.push(AbiParam::new(ir::types::I32));
1872        sig.returns.push(AbiParam::new(ir::types::I32));
1873
1874        let mut fn_ctx = FunctionBuilderContext::new();
1875        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
1876        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
1877
1878        let name = builder
1879            .func
1880            .declare_imported_user_function(ir::UserExternalName {
1881                namespace: 0,
1882                index: 0,
1883            });
1884        let mut sig = Signature::new(CallConv::SystemV);
1885        sig.params.push(AbiParam::new(ir::types::I32));
1886        let signature = builder.func.import_signature(sig);
1887        let func_ref = builder.import_function(ir::ExtFuncData {
1888            name: ir::ExternalName::user(name),
1889            signature,
1890            colocated: true,
1891            patchable: false,
1892        });
1893
1894        // Use a variable, create a control flow diamond so that the variable
1895        // forces a block parameter on the control join point, and make sure
1896        // that we get stack maps for all the appropriate uses of the variable
1897        // in all blocks, as well as that we are reusing stack slots for each of
1898        // the values.
1899        //
1900        //                        block0:
1901        //                          x := needs stack map
1902        //                          call $foo(x)
1903        //                          br_if v0, block1, block2
1904        //
1905        //
1906        //     block1:                                     block2:
1907        //       call $foo(x)                                call $foo(x)
1908        //       call $foo(x)                                call $foo(x)
1909        //       x := new needs stack map                    x := new needs stack map
1910        //       call $foo(x)                                call $foo(x)
1911        //       jump block3                                 jump block3
1912        //
1913        //
1914        //                        block3:
1915        //                          call $foo(x)
1916        //                          return x
1917
1918        let x = builder.declare_var(ir::types::I32);
1919        builder.declare_var_needs_stack_map(x);
1920
1921        let block0 = builder.create_block();
1922        let block1 = builder.create_block();
1923        let block2 = builder.create_block();
1924        let block3 = builder.create_block();
1925
1926        builder.append_block_params_for_function_params(block0);
1927        builder.switch_to_block(block0);
1928        let v0 = builder.func.dfg.block_params(block0)[0];
1929        let val = builder.ins().iconst(ir::types::I32, 42);
1930        builder.def_var(x, val);
1931        {
1932            let x = builder.use_var(x);
1933            builder.ins().call(func_ref, &[x]);
1934        }
1935        builder.ins().brif(v0, block1, &[], block2, &[]);
1936
1937        builder.switch_to_block(block1);
1938        {
1939            let x = builder.use_var(x);
1940            builder.ins().call(func_ref, &[x]);
1941            builder.ins().call(func_ref, &[x]);
1942        }
1943        let val = builder.ins().iconst(ir::types::I32, 36);
1944        builder.def_var(x, val);
1945        {
1946            let x = builder.use_var(x);
1947            builder.ins().call(func_ref, &[x]);
1948        }
1949        builder.ins().jump(block3, &[]);
1950
1951        builder.switch_to_block(block2);
1952        {
1953            let x = builder.use_var(x);
1954            builder.ins().call(func_ref, &[x]);
1955            builder.ins().call(func_ref, &[x]);
1956        }
1957        let val = builder.ins().iconst(ir::types::I32, 36);
1958        builder.def_var(x, val);
1959        {
1960            let x = builder.use_var(x);
1961            builder.ins().call(func_ref, &[x]);
1962        }
1963        builder.ins().jump(block3, &[]);
1964
1965        builder.switch_to_block(block3);
1966        let x = builder.use_var(x);
1967        builder.ins().call(func_ref, &[x]);
1968        builder.ins().return_(&[x]);
1969
1970        builder.seal_all_blocks();
1971        builder.finalize(systemv_frontend_config());
1972
1973        assert_eq_output!(
1974            func.display().to_string(),
1975            r#"
1976function %sample(i32) -> i32 system_v {
1977    ss0 = explicit_slot 4, align = 4
1978    ss1 = explicit_slot 4, align = 4
1979    sig0 = (i32) system_v
1980    fn0 = colocated u0:0 sig0
1981
1982block0(v0: i32):
1983    v1 = iconst.i32 42
1984    v2 -> v1
1985    v4 -> v1
1986    v32 = stack_addr.i64 ss0
1987    store notrap aligned v1, v32  ; v1 = 42
1988    v30 = stack_addr.i64 ss0
1989    v31 = load.i32 notrap aligned v30
1990    call fn0(v31), stack_map=[i32 @ ss0+0]
1991    brif v0, block1, block2
1992
1993block1:
1994    v19 = stack_addr.i64 ss0
1995    v20 = load.i32 notrap aligned v19
1996    call fn0(v20), stack_map=[i32 @ ss0+0]
1997    v17 = stack_addr.i64 ss0
1998    v18 = load.i32 notrap aligned v17
1999    call fn0(v18)
2000    v3 = iconst.i32 36
2001    v16 = stack_addr.i64 ss0
2002    store notrap aligned v3, v16  ; v3 = 36
2003    v14 = stack_addr.i64 ss0
2004    v15 = load.i32 notrap aligned v14
2005    call fn0(v15), stack_map=[i32 @ ss0+0]
2006    v12 = stack_addr.i64 ss0
2007    v13 = load.i32 notrap aligned v12
2008    jump block3(v13)
2009
2010block2:
2011    v28 = stack_addr.i64 ss0
2012    v29 = load.i32 notrap aligned v28
2013    call fn0(v29), stack_map=[i32 @ ss0+0]
2014    v26 = stack_addr.i64 ss0
2015    v27 = load.i32 notrap aligned v26
2016    call fn0(v27)
2017    v5 = iconst.i32 36
2018    v25 = stack_addr.i64 ss1
2019    store notrap aligned v5, v25  ; v5 = 36
2020    v23 = stack_addr.i64 ss1
2021    v24 = load.i32 notrap aligned v23
2022    call fn0(v24), stack_map=[i32 @ ss1+0]
2023    v21 = stack_addr.i64 ss1
2024    v22 = load.i32 notrap aligned v21
2025    jump block3(v22)
2026
2027block3(v6: i32):
2028    v11 = stack_addr.i64 ss0
2029    store notrap aligned v6, v11
2030    v9 = stack_addr.i64 ss0
2031    v10 = load.i32 notrap aligned v9
2032    call fn0(v10), stack_map=[i32 @ ss0+0]
2033    v7 = stack_addr.i64 ss0
2034    v8 = load.i32 notrap aligned v7
2035    return v8
2036}
2037            "#
2038        );
2039    }
2040
2041    #[test]
2042    fn var_needs_stack_map() {
2043        let mut sig = Signature::new(CallConv::SystemV);
2044        sig.params
2045            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
2046        sig.returns
2047            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
2048
2049        let mut fn_ctx = FunctionBuilderContext::new();
2050        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2051        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2052
2053        let var = builder.declare_var(cranelift_codegen::ir::types::I32);
2054        builder.declare_var_needs_stack_map(var);
2055
2056        let name = builder
2057            .func
2058            .declare_imported_user_function(ir::UserExternalName {
2059                namespace: 0,
2060                index: 0,
2061            });
2062        let signature = builder
2063            .func
2064            .import_signature(Signature::new(CallConv::SystemV));
2065        let func_ref = builder.import_function(ir::ExtFuncData {
2066            name: ir::ExternalName::user(name),
2067            signature,
2068            colocated: true,
2069            patchable: false,
2070        });
2071
2072        let block0 = builder.create_block();
2073        builder.append_block_params_for_function_params(block0);
2074        builder.switch_to_block(block0);
2075
2076        let arg = builder.func.dfg.block_params(block0)[0];
2077        builder.def_var(var, arg);
2078
2079        builder.ins().call(func_ref, &[]);
2080
2081        let val = builder.use_var(var);
2082        builder.ins().return_(&[val]);
2083
2084        builder.seal_all_blocks();
2085        builder.finalize(systemv_frontend_config());
2086
2087        assert_eq_output!(
2088            func.display().to_string(),
2089            r#"
2090function %sample(i32) -> i32 system_v {
2091    ss0 = explicit_slot 4, align = 4
2092    sig0 = () system_v
2093    fn0 = colocated u0:0 sig0
2094
2095block0(v0: i32):
2096    v3 = stack_addr.i64 ss0
2097    store notrap aligned v0, v3
2098    call fn0(), stack_map=[i32 @ ss0+0]
2099    v1 = stack_addr.i64 ss0
2100    v2 = load.i32 notrap aligned v1
2101    return v2
2102}
2103            "#
2104        );
2105    }
2106
2107    #[test]
2108    fn first_inst_defines_needs_stack_map() {
2109        let mut sig = Signature::new(CallConv::SystemV);
2110        sig.params
2111            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
2112        sig.returns
2113            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
2114        sig.returns
2115            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
2116
2117        let mut fn_ctx = FunctionBuilderContext::new();
2118        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2119        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2120
2121        let name = builder
2122            .func
2123            .declare_imported_user_function(ir::UserExternalName {
2124                namespace: 0,
2125                index: 0,
2126            });
2127        let signature = builder
2128            .func
2129            .import_signature(Signature::new(CallConv::SystemV));
2130        let func_ref = builder.import_function(ir::ExtFuncData {
2131            name: ir::ExternalName::user(name),
2132            signature,
2133            colocated: true,
2134            patchable: false,
2135        });
2136
2137        // Regression test found via fuzzing in
2138        // https://github.com/bytecodealliance/wasmtime/pull/8941 involving the
2139        // combination of cursor positions after we have block parameters that
2140        // need inclusion in stack maps and when the first instruction in a
2141        // block defines a value that needs inclusion in stack maps.
2142        //
2143        // block0(v0: i32):
2144        //   v1 = iconst.i32 42
2145        //   call $foo()
2146        //   return v0, v1
2147
2148        let block0 = builder.create_block();
2149        builder.append_block_params_for_function_params(block0);
2150        builder.switch_to_block(block0);
2151
2152        let arg = builder.func.dfg.block_params(block0)[0];
2153        builder.declare_value_needs_stack_map(arg);
2154
2155        let val = builder.ins().iconst(ir::types::I32, 42);
2156        builder.declare_value_needs_stack_map(val);
2157
2158        builder.ins().call(func_ref, &[]);
2159
2160        builder.ins().return_(&[arg, val]);
2161
2162        builder.seal_all_blocks();
2163        builder.finalize(systemv_frontend_config());
2164
2165        assert_eq_output!(
2166            func.display().to_string(),
2167            r#"
2168function %sample(i32) -> i32, i32 system_v {
2169    ss0 = explicit_slot 4, align = 4
2170    ss1 = explicit_slot 4, align = 4
2171    sig0 = () system_v
2172    fn0 = colocated u0:0 sig0
2173
2174block0(v0: i32):
2175    v7 = stack_addr.i64 ss0
2176    store notrap aligned v0, v7
2177    v1 = iconst.i32 42
2178    v6 = stack_addr.i64 ss1
2179    store notrap aligned v1, v6  ; v1 = 42
2180    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
2181    v2 = stack_addr.i64 ss0
2182    v3 = load.i32 notrap aligned v2
2183    v4 = stack_addr.i64 ss1
2184    v5 = load.i32 notrap aligned v4
2185    return v3, v5
2186}
2187            "#
2188        );
2189    }
2190
2191    #[test]
2192    fn needs_stack_map_and_loops_and_partially_live_values() {
2193        let _ = env_logger::try_init();
2194
2195        let mut sig = Signature::new(CallConv::SystemV);
2196        sig.params.push(AbiParam::new(ir::types::I32));
2197
2198        let mut fn_ctx = FunctionBuilderContext::new();
2199        let mut func =
2200            Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig.clone());
2201        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2202
2203        let name = builder
2204            .func
2205            .declare_imported_user_function(ir::UserExternalName {
2206                namespace: 0,
2207                index: 0,
2208            });
2209        let signature = builder
2210            .func
2211            .import_signature(Signature::new(CallConv::SystemV));
2212        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2213            name: ir::ExternalName::user(name),
2214            signature,
2215            colocated: true,
2216            patchable: false,
2217        });
2218
2219        let name = builder
2220            .func
2221            .declare_imported_user_function(ir::UserExternalName {
2222                namespace: 1,
2223                index: 1,
2224            });
2225        let signature = builder.func.import_signature(sig);
2226        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2227            name: ir::ExternalName::user(name),
2228            signature,
2229            colocated: true,
2230            patchable: false,
2231        });
2232
2233        // Test that we support stack maps in loops and that we properly handle
2234        // value that are only live for part of the loop body on each iteration,
2235        // but are live across the whole loop because they will be used again
2236        // the next iteration. Note that `v0` below, which is a GC value, is not
2237        // live *within a single iteration of the loop* after the call to `bar`,
2238        // but is actually live across the whole loop because it will be used
2239        // again in the *next iteration of the loop*:
2240        //
2241        //     block0(v0: i32):
2242        //       jump block1
2243        //
2244        //     block1:
2245        //       call $foo()
2246        //       call $bar(v0)
2247        //       call $foo()
2248        //       jump block1
2249        let block0 = builder.create_block();
2250        let block1 = builder.create_block();
2251        builder.append_block_params_for_function_params(block0);
2252
2253        builder.switch_to_block(block0);
2254        builder.ins().jump(block1, &[]);
2255
2256        builder.switch_to_block(block1);
2257        let v0 = builder.func.dfg.block_params(block0)[0];
2258        builder.declare_value_needs_stack_map(v0);
2259        builder.ins().call(foo_func_ref, &[]);
2260        builder.ins().call(bar_func_ref, &[v0]);
2261        builder.ins().call(foo_func_ref, &[]);
2262        builder.ins().jump(block1, &[]);
2263
2264        builder.seal_all_blocks();
2265        builder.finalize(systemv_frontend_config());
2266
2267        assert_eq_output!(
2268            func.display().to_string(),
2269            r#"
2270function %sample(i32) system_v {
2271    ss0 = explicit_slot 4, align = 4
2272    sig0 = () system_v
2273    sig1 = (i32) system_v
2274    fn0 = colocated u0:0 sig0
2275    fn1 = colocated u1:1 sig1
2276
2277block0(v0: i32):
2278    v3 = stack_addr.i64 ss0
2279    store notrap aligned v0, v3
2280    jump block1
2281
2282block1:
2283    call fn0(), stack_map=[i32 @ ss0+0]
2284    v1 = stack_addr.i64 ss0
2285    v2 = load.i32 notrap aligned v1
2286    call fn1(v2), stack_map=[i32 @ ss0+0]
2287    call fn0(), stack_map=[i32 @ ss0+0]
2288    jump block1
2289}
2290            "#,
2291        );
2292    }
2293
2294    #[test]
2295    fn needs_stack_map_and_irreducible_loops() {
2296        let _ = env_logger::try_init();
2297
2298        let mut sig = Signature::new(CallConv::SystemV);
2299        sig.params.push(AbiParam::new(ir::types::I32));
2300        sig.params.push(AbiParam::new(ir::types::I32));
2301
2302        let mut fn_ctx = FunctionBuilderContext::new();
2303        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2304        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2305
2306        let name = builder
2307            .func
2308            .declare_imported_user_function(ir::UserExternalName {
2309                namespace: 0,
2310                index: 0,
2311            });
2312        let signature = builder
2313            .func
2314            .import_signature(Signature::new(CallConv::SystemV));
2315        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2316            name: ir::ExternalName::user(name),
2317            signature,
2318            colocated: true,
2319            patchable: false,
2320        });
2321
2322        let name = builder
2323            .func
2324            .declare_imported_user_function(ir::UserExternalName {
2325                namespace: 1,
2326                index: 1,
2327            });
2328        let mut sig = Signature::new(CallConv::SystemV);
2329        sig.params.push(AbiParam::new(ir::types::I32));
2330        let signature = builder.func.import_signature(sig);
2331        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2332            name: ir::ExternalName::user(name),
2333            signature,
2334            colocated: true,
2335            patchable: false,
2336        });
2337
2338        // Test an irreducible loop with multiple entry points, both block1 and
2339        // block2, in this case:
2340        //
2341        //     block0(v0: i32, v1: i32):
2342        //       brif v0, block1, block2
2343        //
2344        //     block1:
2345        //       jump block3
2346        //
2347        //     block2:
2348        //       jump block4
2349        //
2350        //     block3:
2351        //       call $foo()
2352        //       call $bar(v1)
2353        //       call $foo()
2354        //       jump block2
2355        //
2356        //     block4:
2357        //       call $foo()
2358        //       call $bar(v1)
2359        //       call $foo()
2360        //       jump block1
2361        let block0 = builder.create_block();
2362        let block1 = builder.create_block();
2363        let block2 = builder.create_block();
2364        let block3 = builder.create_block();
2365        let block4 = builder.create_block();
2366        builder.append_block_params_for_function_params(block0);
2367
2368        builder.switch_to_block(block0);
2369        let v0 = builder.func.dfg.block_params(block0)[0];
2370        let v1 = builder.func.dfg.block_params(block0)[1];
2371        builder.declare_value_needs_stack_map(v1);
2372        builder.ins().brif(v0, block1, &[], block2, &[]);
2373
2374        builder.switch_to_block(block1);
2375        builder.ins().jump(block3, &[]);
2376
2377        builder.switch_to_block(block2);
2378        builder.ins().jump(block4, &[]);
2379
2380        builder.switch_to_block(block3);
2381        builder.ins().call(foo_func_ref, &[]);
2382        builder.ins().call(bar_func_ref, &[v1]);
2383        builder.ins().call(foo_func_ref, &[]);
2384        builder.ins().jump(block2, &[]);
2385
2386        builder.switch_to_block(block4);
2387        builder.ins().call(foo_func_ref, &[]);
2388        builder.ins().call(bar_func_ref, &[v1]);
2389        builder.ins().call(foo_func_ref, &[]);
2390        builder.ins().jump(block1, &[]);
2391
2392        builder.seal_all_blocks();
2393        builder.finalize(systemv_frontend_config());
2394
2395        assert_eq_output!(
2396            func.display().to_string(),
2397            r#"
2398function %sample(i32, i32) system_v {
2399    ss0 = explicit_slot 4, align = 4
2400    sig0 = () system_v
2401    sig1 = (i32) system_v
2402    fn0 = colocated u0:0 sig0
2403    fn1 = colocated u1:1 sig1
2404
2405block0(v0: i32, v1: i32):
2406    v6 = stack_addr.i64 ss0
2407    store notrap aligned v1, v6
2408    brif v0, block1, block2
2409
2410block1:
2411    jump block3
2412
2413block2:
2414    jump block4
2415
2416block3:
2417    call fn0(), stack_map=[i32 @ ss0+0]
2418    v4 = stack_addr.i64 ss0
2419    v5 = load.i32 notrap aligned v4
2420    call fn1(v5), stack_map=[i32 @ ss0+0]
2421    call fn0(), stack_map=[i32 @ ss0+0]
2422    jump block2
2423
2424block4:
2425    call fn0(), stack_map=[i32 @ ss0+0]
2426    v2 = stack_addr.i64 ss0
2427    v3 = load.i32 notrap aligned v2
2428    call fn1(v3), stack_map=[i32 @ ss0+0]
2429    call fn0(), stack_map=[i32 @ ss0+0]
2430    jump block1
2431}
2432            "#,
2433        );
2434    }
2435
2436    #[test]
2437    fn needs_stack_map_and_back_edge_to_back_edge() {
2438        let _ = env_logger::try_init();
2439
2440        let mut sig = Signature::new(CallConv::SystemV);
2441        sig.params.push(AbiParam::new(ir::types::I32));
2442        sig.params.push(AbiParam::new(ir::types::I32));
2443        sig.params.push(AbiParam::new(ir::types::I32));
2444        sig.params.push(AbiParam::new(ir::types::I32));
2445
2446        let mut fn_ctx = FunctionBuilderContext::new();
2447        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2448        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2449
2450        let name = builder
2451            .func
2452            .declare_imported_user_function(ir::UserExternalName {
2453                namespace: 0,
2454                index: 0,
2455            });
2456        let signature = builder
2457            .func
2458            .import_signature(Signature::new(CallConv::SystemV));
2459        let foo_func_ref = builder.import_function(ir::ExtFuncData {
2460            name: ir::ExternalName::user(name),
2461            signature,
2462            colocated: true,
2463            patchable: false,
2464        });
2465
2466        let name = builder
2467            .func
2468            .declare_imported_user_function(ir::UserExternalName {
2469                namespace: 1,
2470                index: 1,
2471            });
2472        let mut sig = Signature::new(CallConv::SystemV);
2473        sig.params.push(AbiParam::new(ir::types::I32));
2474        let signature = builder.func.import_signature(sig);
2475        let bar_func_ref = builder.import_function(ir::ExtFuncData {
2476            name: ir::ExternalName::user(name),
2477            signature,
2478            colocated: true,
2479            patchable: false,
2480        });
2481
2482        // Test that we detect the `block1 -> block2 -> block3 -> block2 ->
2483        // block1` loop in our liveness analysis and keep `v{0,1,2}` live across
2484        // the whole loop body.
2485        //
2486        //     block0(v0, v1, v2, v3):
2487        //       jump block1(v3)
2488        //
2489        //     block1(v4):
2490        //       call foo_func_ref()
2491        //       call bar_func_ref(v0)
2492        //       call foo_func_ref()
2493        //       jump block2
2494        //
2495        //     block2:
2496        //       call foo_func_ref()
2497        //       call bar_func_ref(v1)
2498        //       call foo_func_ref()
2499        //       v5 = iadd_imm v4, -1
2500        //       brif v4, block1(v5), block3
2501        //
2502        //     block3:
2503        //       call foo_func_ref()
2504        //       call bar_func_ref(v2)
2505        //       call foo_func_ref()
2506        //       jump block2
2507
2508        let block0 = builder.create_block();
2509        let block1 = builder.create_block();
2510        let block2 = builder.create_block();
2511        let block3 = builder.create_block();
2512
2513        builder.append_block_params_for_function_params(block0);
2514
2515        builder.switch_to_block(block0);
2516
2517        let v0 = builder.func.dfg.block_params(block0)[0];
2518        builder.declare_value_needs_stack_map(v0);
2519        let v1 = builder.func.dfg.block_params(block0)[1];
2520        builder.declare_value_needs_stack_map(v1);
2521        let v2 = builder.func.dfg.block_params(block0)[2];
2522        builder.declare_value_needs_stack_map(v2);
2523        let v3 = builder.func.dfg.block_params(block0)[3];
2524
2525        builder.ins().jump(block1, &[v3.into()]);
2526
2527        builder.switch_to_block(block1);
2528        let v4 = builder.append_block_param(block1, ir::types::I32);
2529        builder.ins().call(foo_func_ref, &[]);
2530        builder.ins().call(bar_func_ref, &[v0]);
2531        builder.ins().call(foo_func_ref, &[]);
2532        builder.ins().jump(block2, &[]);
2533
2534        builder.switch_to_block(block2);
2535        builder.ins().call(foo_func_ref, &[]);
2536        builder.ins().call(bar_func_ref, &[v1]);
2537        builder.ins().call(foo_func_ref, &[]);
2538        let v5 = builder.ins().iadd_imm_s(v4, -1);
2539        builder.ins().brif(v4, block1, &[v5.into()], block3, &[]);
2540
2541        builder.switch_to_block(block3);
2542        builder.ins().call(foo_func_ref, &[]);
2543        builder.ins().call(bar_func_ref, &[v2]);
2544        builder.ins().call(foo_func_ref, &[]);
2545        builder.ins().jump(block2, &[]);
2546
2547        builder.seal_all_blocks();
2548        builder.finalize(systemv_frontend_config());
2549
2550        assert_eq_output!(
2551            func.display().to_string(),
2552            r#"
2553function %sample(i32, i32, i32, i32) system_v {
2554    ss0 = explicit_slot 4, align = 4
2555    ss1 = explicit_slot 4, align = 4
2556    ss2 = explicit_slot 4, align = 4
2557    sig0 = () system_v
2558    sig1 = (i32) system_v
2559    fn0 = colocated u0:0 sig0
2560    fn1 = colocated u1:1 sig1
2561
2562block0(v0: i32, v1: i32, v2: i32, v3: i32):
2563    v13 = stack_addr.i64 ss0
2564    store notrap aligned v0, v13
2565    v14 = stack_addr.i64 ss1
2566    store notrap aligned v1, v14
2567    v15 = stack_addr.i64 ss2
2568    store notrap aligned v2, v15
2569    jump block1(v3)
2570
2571block1(v4: i32):
2572    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2573    v11 = stack_addr.i64 ss0
2574    v12 = load.i32 notrap aligned v11
2575    call fn1(v12), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2576    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2577    jump block2
2578
2579block2:
2580    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2581    v9 = stack_addr.i64 ss1
2582    v10 = load.i32 notrap aligned v9
2583    call fn1(v10), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2584    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2585    v5 = iconst.i32 -1
2586    v6 = iadd.i32 v4, v5  ; v5 = -1
2587    brif.i32 v4, block1(v6), block3
2588
2589block3:
2590    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2591    v7 = stack_addr.i64 ss2
2592    v8 = load.i32 notrap aligned v7
2593    call fn1(v8), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2594    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
2595    jump block2
2596}
2597            "#,
2598        );
2599    }
2600
2601    fn import_func(
2602        builder: &mut FunctionBuilder,
2603        params: impl IntoIterator<Item = ir::Type>,
2604        results: impl IntoIterator<Item = ir::Type>,
2605    ) -> ir::FuncRef {
2606        let index = u32::try_from(builder.func.dfg.ext_funcs.len()).unwrap();
2607
2608        let name = builder
2609            .func
2610            .declare_imported_user_function(ir::UserExternalName {
2611                namespace: 0,
2612                index,
2613            });
2614        let name = ir::ExternalName::user(name);
2615
2616        let mut signature = Signature::new(CallConv::SystemV);
2617        signature
2618            .params
2619            .extend(params.into_iter().map(|ty| AbiParam::new(ty)));
2620        signature
2621            .returns
2622            .extend(results.into_iter().map(|ty| AbiParam::new(ty)));
2623        let signature = builder.func.import_signature(signature);
2624
2625        builder.import_function(ir::ExtFuncData {
2626            name,
2627            signature,
2628            colocated: true,
2629            patchable: false,
2630        })
2631    }
2632
2633    #[test]
2634    fn issue_10397_stack_map_vars_and_indirect_block_params() {
2635        let _ = env_logger::try_init();
2636
2637        let mut sig = Signature::new(CallConv::SystemV);
2638        if false {
2639            sig.params.push(AbiParam::new(ir::types::I32));
2640        }
2641
2642        let mut fn_ctx = FunctionBuilderContext::new();
2643        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("f"), sig);
2644        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2645
2646        let alloc_struct = import_func(&mut builder, None, Some(ir::types::I32));
2647        let alloc_array = import_func(&mut builder, None, Some(ir::types::I32));
2648        let array_init_elem = import_func(&mut builder, Some(ir::types::I32), None);
2649        let type_of = import_func(&mut builder, Some(ir::types::I32), Some(ir::types::I32));
2650        let ref_test = import_func(
2651            &mut builder,
2652            vec![ir::types::I32, ir::types::I32],
2653            Some(ir::types::I32),
2654        );
2655        let access_array = import_func(&mut builder, Some(ir::types::I32), None);
2656        let should_continue_inner_loop = import_func(&mut builder, None, Some(ir::types::I32));
2657        let access_struct = import_func(&mut builder, Some(ir::types::I32), None);
2658        let should_return = import_func(&mut builder, None, Some(ir::types::I32));
2659
2660        // This test exercises the combination of declaring stack maps and when
2661        // we need to insert block params during SSA construction. The following
2662        // CLIF uses vars in such a way that it will ultimately require that we
2663        // insert block params. However, some of the inserted block params are
2664        // only ever used in such a way that they are passed as arguments to
2665        // *other* blocks. That is, there are regions where we never use them
2666        // directly, and therefore, if we are only declaring that the variable's
2667        // values need inclusion in stack maps on direct uses, we will
2668        // completely miss these inserted block params. That leads to incomplete
2669        // stack maps, which leads to collecting objects too early, which leads
2670        // to use-after-free bugs.
2671        //
2672        // After inserting safepoint spills and stack map annotations to the
2673        // following pseudo-CLIF, we should have stack map entries for the
2674        // `live: {...}` annotations (or an over-approximation of that
2675        // annotation).
2676        //
2677        //     block_entry:
2678        //       v0 = call alloc_struct()                          ;; live: {}
2679        //       define var_struct = v0
2680        //       jump block_outer_loop_head
2681        //
2682        //     block_outer_loop_head:
2683        //       v1 = call alloc_array()                           ;; live: {struct}
2684        //       define var_array = v1
2685        //       v2 = iconst.i32 0
2686        //       jump block_array_init_loop_head(v2)
2687        //
2688        //     block_array_init_loop_head(v3):
2689        //       v4 = iconst.i32 1
2690        //       v5 = icmp ult v3, v4
2691        //       br_if v5, block_array_init_loop_body, block_array_init_loop_done
2692        //
2693        //     block_array_init_loop_body:
2694        //       call array_init_elem(v1, v4)                      ;; live: {struct, array}
2695        //       v6 = iconst.i32 1
2696        //       v7 = iadd v4, v6
2697        //       jump block_array_init_loop_head(v7)
2698        //
2699        //     block_array_init_loop_done:
2700        //       jump block_inner_loop_head
2701        //
2702        //     block_inner_loop_head:
2703        //       v8 = use var_array
2704        //       v9 = iconst.i32 0
2705        //       v10 = icmp eq v8, v9
2706        //       br_if v10, block_ref_test_done(v9), block_ref_test_non_null
2707        //
2708        //     block_ref_test_non_null:
2709        //       v11 = call type_of(v8)                            ;; live: {struct, array}
2710        //       v12 = iconst.i32 0xbeefbeef
2711        //       v13 = icmp eq v11, v12
2712        //       v14 = iconst.i31 1
2713        //       br_if v13, block_ref_test_done(v14), block_ref_test_slow
2714        //
2715        //     block_ref_test_slow:
2716        //       v15 = call ref_test(v8, v12)                      ;; live: {struct, array}
2717        //       jump block_ref_test_done(v15)
2718        //
2719        //     block_ref_test_done(v16):
2720        //       trapz v16, user1
2721        //       define var_array = v8
2722        //       call access_array(v8)                             ;; live: {struct}
2723        //       v17 = call should_continue_inner_loop()           ;; live: {struct}
2724        //       br_if v17, block_inner_loop_head, block_after_inner_loop
2725        //
2726        //     block_after_inner_loop:
2727        //       v18 = use var_struct
2728        //       call access_struct(v18)                           ;; live: {struct}
2729        //       v19 = call should_return()                        ;; live: {struct}
2730        //       br_if v19, block_return, block_outer_loop_head
2731        //
2732        //     block_return:
2733        //       return
2734
2735        let var_struct = builder.declare_var(cranelift_codegen::ir::types::I32);
2736        builder.declare_var_needs_stack_map(var_struct);
2737
2738        let var_array = builder.declare_var(cranelift_codegen::ir::types::I32);
2739        builder.declare_var_needs_stack_map(var_array);
2740
2741        let block_entry = builder.create_block();
2742        let block_outer_loop_head = builder.create_block();
2743        let block_array_init_loop_head = builder.create_block();
2744        let block_array_init_loop_body = builder.create_block();
2745        let block_array_init_loop_done = builder.create_block();
2746        let block_inner_loop_head = builder.create_block();
2747        let block_ref_test_non_null = builder.create_block();
2748        let block_ref_test_slow = builder.create_block();
2749        let block_ref_test_done = builder.create_block();
2750        let block_after_inner_loop = builder.create_block();
2751        let block_return = builder.create_block();
2752
2753        builder.append_block_params_for_function_params(block_entry);
2754        builder.switch_to_block(block_entry);
2755        builder.seal_block(block_entry);
2756        let call_inst = builder.ins().call(alloc_struct, &[]);
2757        let v0 = builder.func.dfg.first_result(call_inst);
2758        builder.def_var(var_struct, v0);
2759        builder.ins().jump(block_outer_loop_head, &[]);
2760
2761        builder.switch_to_block(block_outer_loop_head);
2762        let call_inst = builder.ins().call(alloc_array, &[]);
2763        let v1 = builder.func.dfg.first_result(call_inst);
2764        builder.def_var(var_array, v1);
2765        let v2 = builder.ins().iconst(ir::types::I32, 0);
2766        builder.ins().jump(block_array_init_loop_head, &[v2.into()]);
2767
2768        builder.switch_to_block(block_array_init_loop_head);
2769        let v3 = builder.append_block_param(block_array_init_loop_head, ir::types::I32);
2770        let v4 = builder.ins().iconst(ir::types::I32, 1);
2771        let v5 = builder
2772            .ins()
2773            .icmp(ir::condcodes::IntCC::UnsignedLessThan, v3, v4);
2774        builder.ins().brif(
2775            v5,
2776            block_array_init_loop_body,
2777            &[],
2778            block_array_init_loop_done,
2779            &[],
2780        );
2781
2782        builder.switch_to_block(block_array_init_loop_body);
2783        builder.seal_block(block_array_init_loop_body);
2784        builder.ins().call(array_init_elem, &[v1, v4]);
2785        let v6 = builder.ins().iconst(ir::types::I32, 1);
2786        let v7 = builder.ins().iadd(v4, v6);
2787        builder.ins().jump(block_array_init_loop_head, &[v7.into()]);
2788        builder.seal_block(block_array_init_loop_head);
2789
2790        builder.switch_to_block(block_array_init_loop_done);
2791        builder.seal_block(block_array_init_loop_done);
2792        builder.ins().jump(block_inner_loop_head, &[]);
2793
2794        builder.switch_to_block(block_inner_loop_head);
2795        let v8 = builder.use_var(var_array);
2796        let v9 = builder.ins().iconst(ir::types::I32, 0);
2797        let v10 = builder.ins().icmp(ir::condcodes::IntCC::Equal, v8, v9);
2798        builder.ins().brif(
2799            v10,
2800            block_ref_test_done,
2801            &[v9.into()],
2802            block_ref_test_non_null,
2803            &[],
2804        );
2805
2806        builder.switch_to_block(block_ref_test_non_null);
2807        builder.seal_block(block_ref_test_non_null);
2808        let call_inst = builder.ins().call(type_of, &[v8]);
2809        let v11 = builder.func.dfg.first_result(call_inst);
2810        let v12 = builder.ins().iconst(ir::types::I32, 0xbeefbeef);
2811        let v13 = builder.ins().icmp(ir::condcodes::IntCC::Equal, v11, v12);
2812        let v14 = builder.ins().iconst(ir::types::I32, 1);
2813        builder.ins().brif(
2814            v13,
2815            block_ref_test_done,
2816            &[v14.into()],
2817            block_ref_test_slow,
2818            &[],
2819        );
2820
2821        builder.switch_to_block(block_ref_test_slow);
2822        builder.seal_block(block_ref_test_slow);
2823        let call_inst = builder.ins().call(ref_test, &[v8, v12]);
2824        let v15 = builder.func.dfg.first_result(call_inst);
2825        builder.ins().jump(block_ref_test_done, &[v15.into()]);
2826
2827        builder.switch_to_block(block_ref_test_done);
2828        let v16 = builder.append_block_param(block_ref_test_done, ir::types::I32);
2829        builder.seal_block(block_ref_test_done);
2830        builder.ins().trapz(v16, ir::TrapCode::user(1).unwrap());
2831        builder.def_var(var_array, v8);
2832        builder.ins().call(access_array, &[v8]);
2833        let call_inst = builder.ins().call(should_continue_inner_loop, &[]);
2834        let v17 = builder.func.dfg.first_result(call_inst);
2835        builder
2836            .ins()
2837            .brif(v17, block_inner_loop_head, &[], block_after_inner_loop, &[]);
2838        builder.seal_block(block_inner_loop_head);
2839
2840        builder.switch_to_block(block_after_inner_loop);
2841        builder.seal_block(block_after_inner_loop);
2842        let v18 = builder.use_var(var_struct);
2843        builder.ins().call(access_struct, &[v18]);
2844        let call_inst = builder.ins().call(should_return, &[]);
2845        let v19 = builder.func.dfg.first_result(call_inst);
2846        builder
2847            .ins()
2848            .brif(v19, block_return, &[], block_outer_loop_head, &[]);
2849        builder.seal_block(block_outer_loop_head);
2850
2851        builder.switch_to_block(block_return);
2852        builder.seal_block(block_return);
2853        builder.ins().return_(&[]);
2854
2855        builder.finalize(systemv_frontend_config());
2856        assert_eq_output!(
2857            func.display().to_string(),
2858            r#"
2859function %f() system_v {
2860    ss0 = explicit_slot 4, align = 4
2861    ss1 = explicit_slot 4, align = 4
2862    ss2 = explicit_slot 4, align = 4
2863    sig0 = () -> i32 system_v
2864    sig1 = () -> i32 system_v
2865    sig2 = (i32) system_v
2866    sig3 = (i32) -> i32 system_v
2867    sig4 = (i32, i32) -> i32 system_v
2868    sig5 = (i32) system_v
2869    sig6 = () -> i32 system_v
2870    sig7 = (i32) system_v
2871    sig8 = () -> i32 system_v
2872    fn0 = colocated u0:0 sig0
2873    fn1 = colocated u0:1 sig1
2874    fn2 = colocated u0:2 sig2
2875    fn3 = colocated u0:3 sig3
2876    fn4 = colocated u0:4 sig4
2877    fn5 = colocated u0:5 sig5
2878    fn6 = colocated u0:6 sig6
2879    fn7 = colocated u0:7 sig7
2880    fn8 = colocated u0:8 sig8
2881
2882block0:
2883    v0 = call fn0()
2884    jump block1(v0)
2885
2886block1(v22: i32):
2887    v21 -> v22
2888    v44 = stack_addr.i64 ss1
2889    store notrap aligned v22, v44
2890    v1 = call fn1(), stack_map=[i32 @ ss1+0]
2891    v8 -> v1
2892    v18 -> v1
2893    v43 = stack_addr.i64 ss0
2894    store notrap aligned v1, v43
2895    v2 = iconst.i32 0
2896    jump block2(v2)  ; v2 = 0
2897
2898block2(v3: i32):
2899    v4 = iconst.i32 1
2900    v5 = icmp ult v3, v4  ; v4 = 1
2901    brif v5, block3, block4
2902
2903block3:
2904    v24 = stack_addr.i64 ss0
2905    v25 = load.i32 notrap aligned v24
2906    call fn2(v25, v4), stack_map=[i32 @ ss0+0, i32 @ ss1+0]  ; v4 = 1
2907    v6 = iconst.i32 1
2908    v7 = iadd.i32 v4, v6  ; v4 = 1, v6 = 1
2909    jump block2(v7)
2910
2911block4:
2912    v41 = stack_addr.i64 ss1
2913    v42 = load.i32 notrap aligned v41
2914    jump block5(v42)
2915
2916block5(v20: i32):
2917    v19 -> v20
2918    v40 = stack_addr.i64 ss2
2919    store notrap aligned v20, v40
2920    v9 = iconst.i32 0
2921    v38 = stack_addr.i64 ss0
2922    v39 = load.i32 notrap aligned v38
2923    v10 = icmp eq v39, v9  ; v9 = 0
2924    brif v10, block8(v9), block6  ; v9 = 0
2925
2926block6:
2927    v36 = stack_addr.i64 ss0
2928    v37 = load.i32 notrap aligned v36
2929    v11 = call fn3(v37), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2930    v12 = iconst.i32 -1091584273
2931    v13 = icmp eq v11, v12  ; v12 = -1091584273
2932    v14 = iconst.i32 1
2933    brif v13, block8(v14), block7  ; v14 = 1
2934
2935block7:
2936    v34 = stack_addr.i64 ss0
2937    v35 = load.i32 notrap aligned v34
2938    v15 = call fn4(v35, v12), stack_map=[i32 @ ss0+0, i32 @ ss2+0]  ; v12 = -1091584273
2939    jump block8(v15)
2940
2941block8(v16: i32):
2942    trapz v16, user1
2943    v32 = stack_addr.i64 ss0
2944    v33 = load.i32 notrap aligned v32
2945    call fn5(v33), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2946    v17 = call fn6(), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2947    v30 = stack_addr.i64 ss2
2948    v31 = load.i32 notrap aligned v30
2949    brif v17, block5(v31), block9
2950
2951block9:
2952    v28 = stack_addr.i64 ss2
2953    v29 = load.i32 notrap aligned v28
2954    call fn7(v29), stack_map=[i32 @ ss2+0]
2955    v23 = call fn8(), stack_map=[i32 @ ss2+0]
2956    v26 = stack_addr.i64 ss2
2957    v27 = load.i32 notrap aligned v26
2958    brif v23, block10, block1(v27)
2959
2960block10:
2961    return
2962}
2963            "#,
2964        );
2965    }
2966
2967    #[test]
2968    fn needs_stack_map_across_try_call() {
2969        let _ = env_logger::try_init();
2970
2971        // Test that values needing stack maps get stack_map entries on
2972        // try_call instructions, not just regular call instructions.
2973        //
2974        //     block0:
2975        //       v0 = call fn0()       ;; returns a gc ref
2976        //       v1 = call fn0()       ;; returns another gc ref
2977        //       try_call fn0(), sig0, block1(), [default: block2()]
2978        //                             ;; v0 and v1 should be in the stack map here
2979        //     block1:
2980        //       call fn1(v0)          ;; uses v0, v1 is dead
2981        //       return
2982        //     block2(v2: i64):
2983        //       return
2984
2985        let sig = Signature::new(CallConv::SystemV);
2986
2987        let mut fn_ctx = FunctionBuilderContext::new();
2988        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2989        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2990
2991        // fn0: () -> i32 (returns a gc ref)
2992        let name0 = builder
2993            .func
2994            .declare_imported_user_function(ir::UserExternalName {
2995                namespace: 0,
2996                index: 0,
2997            });
2998        let mut sig0 = Signature::new(CallConv::SystemV);
2999        sig0.returns.push(AbiParam::new(ir::types::I32));
3000        let signature0 = builder.func.import_signature(sig0);
3001        let func_ref0 = builder.import_function(ir::ExtFuncData {
3002            name: ir::ExternalName::user(name0),
3003            signature: signature0,
3004            colocated: true,
3005            patchable: false,
3006        });
3007
3008        // fn1: (i32) -> () (consumes a gc ref)
3009        let name1 = builder
3010            .func
3011            .declare_imported_user_function(ir::UserExternalName {
3012                namespace: 0,
3013                index: 1,
3014            });
3015        let mut sig1 = Signature::new(CallConv::SystemV);
3016        sig1.params.push(AbiParam::new(ir::types::I32));
3017        let signature1 = builder.func.import_signature(sig1);
3018        let func_ref1 = builder.import_function(ir::ExtFuncData {
3019            name: ir::ExternalName::user(name1),
3020            signature: signature1,
3021            colocated: true,
3022            patchable: false,
3023        });
3024
3025        let block0 = builder.create_block();
3026        let block1 = builder.create_block();
3027        let block2 = builder.create_block();
3028
3029        builder.switch_to_block(block0);
3030
3031        // v0 = call fn0()  -- a gc ref that's live across the try_call
3032        let call0 = builder.ins().call(func_ref0, &[]);
3033        let v0 = builder.func.dfg.inst_results(call0)[0];
3034        builder.declare_value_needs_stack_map(v0);
3035
3036        // v1 = call fn0()  -- another gc ref, also live across the try_call
3037        let call1 = builder.ins().call(func_ref0, &[]);
3038        let v1 = builder.func.dfg.inst_results(call1)[0];
3039        builder.declare_value_needs_stack_map(v1);
3040
3041        // try_call fn0() -> block1, exception -> block2
3042        let normal_return = BlockCall::new(block1, [], &mut builder.func.dfg.value_lists);
3043        let exception_table = builder
3044            .func
3045            .dfg
3046            .exception_tables
3047            .push(ExceptionTableData::new(signature0, normal_return, []));
3048        builder.ins().try_call(func_ref0, &[], exception_table);
3049
3050        // block1: use v0 (so it's live across the try_call)
3051        builder.switch_to_block(block1);
3052        builder.ins().call(func_ref1, &[v0]);
3053        builder.ins().return_(&[]);
3054
3055        // block2: exception handler
3056        builder.switch_to_block(block2);
3057        builder.ins().return_(&[]);
3058
3059        builder.seal_all_blocks();
3060        builder.finalize(systemv_frontend_config());
3061
3062        // The try_call should have a stack_map with v0 (and v1 should NOT
3063        // be in it since v1 is not used after the try_call).
3064        // The second call (which produces v1) should have v0 in its stack_map
3065        // since v0 is live across that call too.
3066        let output = func.display().to_string();
3067        assert!(
3068            output.contains("try_call fn0(), sig0, block1, [], stack_map=[i32 @ ss0+0]"),
3069            "try_call should have stack_map entry for v0 (spilled to ss0), got:\n{output}"
3070        );
3071    }
3072
3073    #[test]
3074    fn rewrite_uses_of_alias_values() {
3075        let _ = env_logger::try_init();
3076
3077        let mut sig = Signature::new(CallConv::SystemV);
3078        sig.params.push(AbiParam::new(ir::types::I32));
3079        let mut fn_ctx = FunctionBuilderContext::new();
3080        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
3081        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
3082
3083        // fn0: () -> i32 (returns a gc ref)
3084        let name0 = builder
3085            .func
3086            .declare_imported_user_function(ir::UserExternalName {
3087                namespace: 0,
3088                index: 0,
3089            });
3090        let mut sig = Signature::new(CallConv::SystemV);
3091        sig.returns.push(AbiParam::new(ir::types::I32));
3092        let signature = builder.func.import_signature(sig);
3093        let fn0 = builder.import_function(ir::ExtFuncData {
3094            name: ir::ExternalName::user(name0),
3095            signature,
3096            colocated: true,
3097            patchable: false,
3098        });
3099
3100        // fn1: (i32) -> () (consumes a gc ref)
3101        let name1 = builder
3102            .func
3103            .declare_imported_user_function(ir::UserExternalName {
3104                namespace: 0,
3105                index: 1,
3106            });
3107        let mut sig = Signature::new(CallConv::SystemV);
3108        sig.params.push(AbiParam::new(ir::types::I32));
3109        let signature = builder.func.import_signature(sig);
3110        let fn1 = builder.import_function(ir::ExtFuncData {
3111            name: ir::ExternalName::user(name1),
3112            signature,
3113            colocated: true,
3114            patchable: false,
3115        });
3116
3117        let var = builder.declare_var(ir::types::I32);
3118        builder.declare_var_needs_stack_map(var);
3119
3120        let block0 = builder.create_block();
3121        let block1 = builder.create_block();
3122        let block2 = builder.create_block();
3123
3124        builder.append_block_params_for_function_params(block0);
3125        builder.switch_to_block(block0);
3126        let v0 = builder.func.dfg.block_params(block0)[0];
3127        let inst = builder.ins().call(fn0, &[]);
3128        let v1 = builder.func.dfg.first_result(inst);
3129        builder.def_var(var, v1);
3130        builder.ins().brif(v0, block1, &[], block2, &[]);
3131
3132        builder.switch_to_block(block1);
3133        builder.def_var(var, v1);
3134        builder.ins().jump(block2, &[]);
3135
3136        builder.switch_to_block(block2);
3137        let v2 = builder.use_var(var);
3138        builder.ins().call(fn1, &[v2]);
3139        let v3 = builder.use_var(var);
3140        builder.ins().call(fn1, &[v3]);
3141        builder.ins().return_(&[]);
3142
3143        builder.seal_all_blocks();
3144
3145        builder.finalize(systemv_frontend_config());
3146
3147        // The SSA construction / `Variable` infrastructure makes this value
3148        // into an alias. But it is also an alias of a value that needs
3149        // inclusion in stack maps, so we should reload it from the stack before
3150        // it is passed into `fn1` in the disassembly below, rather than pass
3151        // the original or aliased value directly.
3152        assert!(func.dfg.value_is_alias(v3));
3153        assert_eq_output!(
3154            func.display().to_string(),
3155            r#"
3156function %sample(i32) system_v {
3157    ss0 = explicit_slot 4, align = 4
3158    sig0 = () -> i32 system_v
3159    sig1 = (i32) system_v
3160    fn0 = colocated u0:0 sig0
3161    fn1 = colocated u0:1 sig1
3162
3163block0(v0: i32):
3164    v1 = call fn0()
3165    v2 -> v1
3166    v7 = stack_addr.i64 ss0
3167    store notrap aligned v1, v7
3168    brif v0, block1, block2
3169
3170block1:
3171    jump block2
3172
3173block2:
3174    v5 = stack_addr.i64 ss0
3175    v6 = load.i32 notrap aligned v5
3176    call fn1(v6), stack_map=[i32 @ ss0+0]
3177    v3 = stack_addr.i64 ss0
3178    v4 = load.i32 notrap aligned v3
3179    call fn1(v4)
3180    return
3181}
3182            "#,
3183        );
3184    }
3185
3186    /// Regression test for a missed stack-map slot when a stack-map
3187    /// variable is re-defined in the same block while an earlier SSA
3188    /// value bound to it is still live across a safepoint.
3189    ///
3190    /// Previously, `SSABuilder::variables` stored only the *latest*
3191    /// `Value` per `(Variable, Block)`, and `values_for_var` iterated
3192    /// that map, so the earlier `Value` was dropped from the set the
3193    /// safepoint pass propagates stack-map flags onto. The expected
3194    /// output below requires `v1` to be spilled and listed in the
3195    /// `call` instruction's stack map alongside `v2`.
3196    #[test]
3197    fn var_redefined_in_same_block_keeps_earlier_value_in_stack_map() {
3198        let mut sig = Signature::new(CallConv::SystemV);
3199        sig.returns
3200            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
3201        sig.returns
3202            .push(AbiParam::new(cranelift_codegen::ir::types::I32));
3203
3204        let mut fn_ctx = FunctionBuilderContext::new();
3205        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
3206        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
3207
3208        let var = builder.declare_var(cranelift_codegen::ir::types::I32);
3209        builder.declare_var_needs_stack_map(var);
3210
3211        let name = builder
3212            .func
3213            .declare_imported_user_function(ir::UserExternalName {
3214                namespace: 0,
3215                index: 0,
3216            });
3217        let signature = builder
3218            .func
3219            .import_signature(Signature::new(CallConv::SystemV));
3220        let func_ref = builder.import_function(ir::ExtFuncData {
3221            name: ir::ExternalName::user(name),
3222            signature,
3223            colocated: true,
3224            patchable: false,
3225        });
3226
3227        let block0 = builder.create_block();
3228        builder.switch_to_block(block0);
3229
3230        // First definition of `var`. Read it out (giving us an SSA
3231        // value `v1` that we'll keep live across the safepoint below).
3232        let v1 = builder.ins().iconst(ir::types::I32, 11);
3233        builder.def_var(var, v1);
3234        let v1_use = builder.use_var(var);
3235
3236        // Re-define `var` to a different SSA value within the same
3237        // block. Without the fix, this overwrites the only place
3238        // `v1` was recorded, so `values_for_var(var)` no longer
3239        // returns it and the safepoint pass omits it from the
3240        // upcoming stack map.
3241        let v2 = builder.ins().iconst(ir::types::I32, 22);
3242        builder.def_var(var, v2);
3243        let v2_use = builder.use_var(var);
3244
3245        // Safepoint at which both `v1_use` (= v1) and `v2_use` (= v2)
3246        // must be live and listed in the stack map.
3247        builder.ins().call(func_ref, &[]);
3248
3249        builder.ins().return_(&[v1_use, v2_use]);
3250
3251        builder.seal_all_blocks();
3252        builder.finalize(systemv_frontend_config());
3253
3254        assert_eq_output!(
3255            func.display().to_string(),
3256            r#"
3257function %sample() -> i32, i32 system_v {
3258    ss0 = explicit_slot 4, align = 4
3259    ss1 = explicit_slot 4, align = 4
3260    sig0 = () system_v
3261    fn0 = colocated u0:0 sig0
3262
3263block0:
3264    v0 = iconst.i32 11
3265    v7 = stack_addr.i64 ss0
3266    store notrap aligned v0, v7  ; v0 = 11
3267    v1 = iconst.i32 22
3268    v6 = stack_addr.i64 ss1
3269    store notrap aligned v1, v6  ; v1 = 22
3270    call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
3271    v2 = stack_addr.i64 ss0
3272    v3 = load.i32 notrap aligned v2
3273    v4 = stack_addr.i64 ss1
3274    v5 = load.i32 notrap aligned v4
3275    return v3, v5
3276}
3277            "#
3278        );
3279    }
3280
3281    #[test]
3282    fn loop_invariant_value_needs_stack_map() {
3283        let sig = Signature::new(CallConv::SystemV);
3284        let mut fn_ctx = FunctionBuilderContext::new();
3285        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
3286        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
3287
3288        // alloc: () -> i32
3289        let alloc_name = builder
3290            .func
3291            .declare_imported_user_function(ir::UserExternalName {
3292                namespace: 0,
3293                index: 1,
3294            });
3295        let mut sig = Signature::new(CallConv::SystemV);
3296        sig.returns.push(AbiParam::new(ir::types::I32));
3297        let signature = builder.func.import_signature(sig);
3298        let alloc = builder.import_function(ir::ExtFuncData {
3299            name: ir::ExternalName::user(alloc_name),
3300            signature,
3301            colocated: true,
3302            patchable: false,
3303        });
3304
3305        // observe: (i32) -> ()
3306        let observe_name = builder
3307            .func
3308            .declare_imported_user_function(ir::UserExternalName {
3309                namespace: 0,
3310                index: 1,
3311            });
3312        let mut sig = Signature::new(CallConv::SystemV);
3313        sig.params.push(AbiParam::new(ir::types::I32));
3314        let signature = builder.func.import_signature(sig);
3315        let observe = builder.import_function(ir::ExtFuncData {
3316            name: ir::ExternalName::user(observe_name),
3317            signature,
3318            colocated: true,
3319            patchable: false,
3320        });
3321
3322        let block0 = builder.create_block();
3323        let block1 = builder.create_block();
3324        let block2 = builder.create_block();
3325
3326        // block0: Allocate a GC reference, then enter the loop.
3327        builder.switch_to_block(block0);
3328        let call_inst = builder.ins().call(alloc, &[]);
3329        let v0 = builder.func.dfg.first_result(call_inst);
3330        builder.declare_value_needs_stack_map(v0);
3331        builder.ins().jump(block1, &[]);
3332
3333        // block1: A loop that uses the loop-invariant GC ref and then allocates
3334        // another GC ref. The two GC refs' live ranges are overlapping (since
3335        // `v0` is live across the whole loop) even if they "don't" overlap
3336        // within a single loop iteration.
3337        builder.switch_to_block(block1);
3338        builder.ins().call(observe, &[v0]);
3339        let call_inst = builder.ins().call(alloc, &[]);
3340        let v1 = builder.func.dfg.first_result(call_inst);
3341        builder.declare_value_needs_stack_map(v1);
3342        // This call should have stack map entries for both `v0` and `v1`.
3343        builder.ins().call(observe, &[v1]);
3344        builder.ins().brif(v1, block2, &[], block1, &[]);
3345
3346        // block2: Keep `v1` alive across a safepoint and return.
3347        builder.switch_to_block(block2);
3348        builder.ins().call(observe, &[v1]);
3349        builder.ins().call(observe, &[v1]);
3350        builder.ins().return_(&[]);
3351
3352        builder.seal_all_blocks();
3353        builder.finalize(systemv_frontend_config());
3354
3355        // `v0` and `v1` should be spilled and reloaded from different stack
3356        // slots.
3357        assert_eq_output!(
3358            func.display().to_string(),
3359            r#"
3360function %sample() system_v {
3361    ss0 = explicit_slot 4, align = 4
3362    ss1 = explicit_slot 4, align = 4
3363    sig0 = () -> i32 system_v
3364    sig1 = (i32) system_v
3365    fn0 = colocated u0:1 sig0
3366    fn1 = colocated u0:1 sig1
3367
3368block0:
3369    v0 = call fn0()
3370    v13 = stack_addr.i64 ss1
3371    store notrap aligned v0, v13
3372    jump block1
3373
3374block1:
3375    v11 = stack_addr.i64 ss1
3376    v12 = load.i32 notrap aligned v11
3377    call fn1(v12), stack_map=[i32 @ ss1+0]
3378    v1 = call fn0(), stack_map=[i32 @ ss1+0]
3379    v10 = stack_addr.i64 ss0
3380    store notrap aligned v1, v10
3381    v8 = stack_addr.i64 ss0
3382    v9 = load.i32 notrap aligned v8
3383    call fn1(v9), stack_map=[i32 @ ss1+0, i32 @ ss0+0]
3384    v6 = stack_addr.i64 ss0
3385    v7 = load.i32 notrap aligned v6
3386    brif v7, block2, block1
3387
3388block2:
3389    v4 = stack_addr.i64 ss0
3390    v5 = load.i32 notrap aligned v4
3391    call fn1(v5), stack_map=[i32 @ ss0+0]
3392    v2 = stack_addr.i64 ss0
3393    v3 = load.i32 notrap aligned v2
3394    call fn1(v3)
3395    return
3396}
3397            "#
3398        );
3399    }
3400
3401    #[test]
3402    fn stack_map_alias_region() {
3403        let _ = env_logger::try_init();
3404
3405        let sig = Signature::new(CallConv::SystemV);
3406
3407        let mut fn_ctx = FunctionBuilderContext::new();
3408        let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
3409        let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
3410
3411        // Place every safepoint spill and reload into a single deduplicated
3412        // alias region.
3413        builder.make_stack_map_alias_region(Box::new(|regions, _ty, _slot, _offset| {
3414            Some(regions.insert(ir::AliasRegionData {
3415                user_id: 0,
3416                description: "stack map".into(),
3417            }))
3418        }));
3419
3420        let name = builder
3421            .func
3422            .declare_imported_user_function(ir::UserExternalName {
3423                namespace: 0,
3424                index: 0,
3425            });
3426        let mut sig = Signature::new(CallConv::SystemV);
3427        sig.params.push(AbiParam::new(ir::types::I32));
3428        let signature = builder.func.import_signature(sig);
3429        let func_ref = builder.import_function(ir::ExtFuncData {
3430            name: ir::ExternalName::user(name),
3431            signature,
3432            colocated: true,
3433            patchable: false,
3434        });
3435
3436        // `v0` is live across the first `call` safepoint (it is used again by
3437        // the second call), so it is spilled at its definition and reloaded at
3438        // each use. The spill `store` and reload `load`s should all be tagged
3439        // with the configured alias region.
3440        //
3441        //     block0:
3442        //       v0 = iconst.i32 42  ; needs stack map
3443        //       call $foo(v0)
3444        //       call $foo(v0)
3445        //       return
3446        let block0 = builder.create_block();
3447        builder.append_block_params_for_function_params(block0);
3448        builder.switch_to_block(block0);
3449        let v0 = builder.ins().iconst(ir::types::I32, 42);
3450        builder.declare_value_needs_stack_map(v0);
3451        builder.ins().call(func_ref, &[v0]);
3452        builder.ins().call(func_ref, &[v0]);
3453        builder.ins().return_(&[]);
3454        builder.seal_all_blocks();
3455        builder.finalize(systemv_frontend_config());
3456
3457        assert_eq_output!(
3458            func.display().to_string(),
3459            r#"
3460function %sample() system_v {
3461    ss0 = explicit_slot 4, align = 4
3462    region0 = 0 "stack map"
3463    sig0 = (i32) system_v
3464    fn0 = colocated u0:0 sig0
3465
3466block0:
3467    v0 = iconst.i32 42
3468    v5 = stack_addr.i64 ss0
3469    store notrap aligned region0 v0, v5  ; v0 = 42
3470    v3 = stack_addr.i64 ss0
3471    v4 = load.i32 notrap aligned region0 v3
3472    call fn0(v4), stack_map=[i32 @ ss0+0]
3473    v1 = stack_addr.i64 ss0
3474    v2 = load.i32 notrap aligned region0 v1
3475    call fn0(v2)
3476    return
3477}
3478            "#
3479        );
3480    }
3481}