Skip to main content

rucc_regalloc/
rewrite.rs

1//! Making an assignment true in the function it was worked out for.
2//!
3//! Design: `spec/10-backend.md` section 10.4.
4//!
5//! [`crate::assign`] says where every value goes and touches nothing. This is the other half: every
6//! operand is rewritten to the place its value was given, and the moves that the places do not
7//! already say are collected. After it the function names no virtual register and no block asks
8//! for anything, which is the point at which machine IR stops being in SSA form and starts being
9//! something an encoder could read.
10//!
11//! # Why the moves are handed back rather than written
12//!
13//! A move is an instruction, and an instruction has an opcode, and an opcode belongs to a target.
14//! `spec/10-backend.md` section 10.8 says no pipeline crate holds target specific code, so this
15//! crate is not the one that can write `x64.mov`. What it hands back is an [`Edit`]: a move
16//! between two places, the class it is in, and where in the function it goes. `rucc-codegen` turns
17//! each one into whatever its target moves a register with, which for a value on the stack is a
18//! load or a store rather than a move at all.
19//!
20//! The edits at any one place are in the order they have to be made in. That matters in two
21//! places: a spilled operand is read into a scratch register before the instruction that wants it,
22//! and a two address instruction's copy has to come after that read, because what it is copying
23//! may be the thing that was just read in.
24//!
25//! # What a fixed register turns into
26//!
27//! A move each way. The assignment deliberately gave the value some other register, so a division
28//! whose dividend has to be in `rax` gets a move into `rax` in front of it and a move out of `rax`
29//! behind it. That is the cost of the rule the assignment follows, and it is the rule that keeps
30//! the `-O0` allocator one pass.
31//!
32//! # What an edge turns into
33//!
34//! The moves that write the block's parameters, in an order they can be made in one at a time,
35//! which is what [`crate::moves`] is for. Where they go depends on the shape of the edge. A block
36//! with one successor puts them at its own end, in front of the branch it finishes with, and a
37//! block with several puts them at the start of the block the edge goes to, which is safe exactly
38//! because that block has no other predecessor. An edge that is critical has neither place to put
39//! them and has to have been split before allocation ran, which this checks rather than assumes.
40//!
41//! An edge is also the one place a value can be asked to go from one stack slot to another, which
42//! happens when a spilled value is passed to a parameter that was itself spilled. No machine here
43//! has that instruction, so the move goes through a register, and the register is a second scratch
44//! rather than the one the ordering may be holding a value in for the length of a cycle. Expanding
45//! it here rather than leaving it to the target is the same decision as everything else in this
46//! file: a move through a temporary is a fact about places, and which register is free to be the
47//! temporary is a fact only this crate has.
48
49use rucc_mir::{Block, Constraint, Func, Inst, Operand, Param, Reg};
50use rucc_target::{PhysReg, RegClass};
51
52use crate::assign::{Assignment, Env, Place};
53use crate::moves::{self, Move};
54
55/// One move the places did not already make true.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct Edit {
58    /// Where in the function it goes.
59    pub at: At,
60    /// What it moves, and where to.
61    pub mov: Move<Place>,
62    /// The class both places are in, which is what says how wide the move is.
63    pub class: RegClass,
64}
65
66/// Where an edit goes.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum At {
69    /// In front of an instruction, which is where a value it reads is put where it wants it.
70    Before(Inst),
71    /// Behind an instruction, which is where a value it wrote somewhere it insisted on is taken
72    /// away to where it lives.
73    After(Inst),
74    /// At the start of a block, in front of everything in it.
75    StartOf(Block),
76    /// At the end of a block, behind everything in it. Only ever a block with one edge out of
77    /// it, since a block with two puts an edge's moves at the start of the block it goes to.
78    EndOf(Block),
79}
80
81/// Rewrites a function to the places it was given, and says what moves are still wanted.
82///
83/// # Panics
84///
85/// Panics if the entry block has parameters, since there is no edge into it for their moves to go
86/// on and what arrives in a function is the ABI lowering's to say. Panics on a critical edge, on
87/// an edge carrying the wrong number of arguments, and if a class runs out of scratch registers
88/// for one instruction or has fewer than two on an edge that moves a spilled value into a spilled
89/// parameter, all of which are the caller handing it something it was told not to.
90#[must_use]
91pub fn rewrite(func: &mut Func, assignment: &Assignment, env: &Env) -> Vec<Edit> {
92    let blocks: Vec<Block> = func.blocks().collect();
93    assert!(
94        func.entry().is_none_or(|entry| func[entry].params.is_empty()),
95        "what arrives in a function is not a block parameter"
96    );
97
98    let mut edits = Vec::new();
99    for &block in &blocks {
100        let insts: Vec<Inst> = func.insts(block).collect();
101        for inst in insts {
102            instruction(func, assignment, env, inst, &mut edits);
103        }
104    }
105
106    let preds = preds(func, &blocks);
107    for &block in &blocks {
108        edges(func, assignment, env, block, &preds, &mut edits);
109    }
110    for &block in &blocks {
111        func.params_mut(block).clear();
112        for call in func.succs_mut(block) {
113            call.args.clear();
114        }
115    }
116    edits
117}
118
119/// Rewrites one instruction's operands, and says what has to happen either side of it.
120fn instruction(
121    func: &mut Func,
122    assignment: &Assignment,
123    env: &Env,
124    inst: Inst,
125    edits: &mut Vec<Edit>,
126) {
127    let list = func[inst].operands;
128    let mut operands: Vec<Operand> = func[list].to_vec();
129    let mut before: Vec<(Move<Place>, RegClass)> = Vec::new();
130    let mut after: Vec<(Move<Place>, RegClass)> = Vec::new();
131    let mut taken = 0;
132
133    for operand in &mut operands {
134        let fixed = match operand.constraint {
135            Constraint::Fixed(at) => Some(at),
136            _ => None,
137        };
138        let at = match (place(assignment, operand.reg), fixed) {
139            (Place::Reg(at), None) => at,
140            (Place::Reg(at), Some(fixed)) => {
141                if at != fixed {
142                    let (there, here) = (Place::Reg(fixed), Place::Reg(at));
143                    push(&mut before, &mut after, operand, Move::new(there, here));
144                }
145                fixed
146            }
147            (Place::Slot(slot), fixed) => {
148                let at = fixed.unwrap_or_else(|| {
149                    let scratch = *env
150                        .scratch(operand.class)
151                        .get(taken)
152                        .expect("an instruction wanting more scratch registers than the class has");
153                    taken += 1;
154                    scratch
155                });
156                push(
157                    &mut before,
158                    &mut after,
159                    operand,
160                    Move::new(Place::Reg(at), Place::Slot(slot)),
161                );
162                at
163            }
164        };
165        operand.reg = Reg::physical(at);
166    }
167
168    // A two address instruction writes one of the registers it reads, and the copy that makes that
169    // true goes after everything else in front of the instruction, since what it reads may be a
170    // value that was itself only just read in from the stack.
171    for index in 0..operands.len() {
172        let Constraint::Reuse(other) = operands[index].constraint else { continue };
173        let (to, from) = (operands[index], operands[usize::from(other)]);
174        if to.reg != from.reg {
175            let mov = Move::new(Place::Reg(phys(to.reg)), Place::Reg(phys(from.reg)));
176            before.push((mov, to.class));
177        }
178    }
179
180    func[list].copy_from_slice(&operands);
181    edits.extend(before.into_iter().map(|(mov, class)| Edit { at: At::Before(inst), mov, class }));
182    edits.extend(after.into_iter().map(|(mov, class)| Edit { at: At::After(inst), mov, class }));
183}
184
185/// Files a move in front of the instruction or behind it, and turns it round for a value the
186/// instruction writes, since that one travels the other way.
187fn push(
188    before: &mut Vec<(Move<Place>, RegClass)>,
189    after: &mut Vec<(Move<Place>, RegClass)>,
190    operand: &Operand,
191    mov: Move<Place>,
192) {
193    if operand.role.is_def() {
194        after.push((Move::new(mov.from, mov.to), operand.class));
195    } else {
196        before.push((mov, operand.class));
197    }
198}
199
200/// The moves the edges out of a block turn into.
201fn edges(
202    func: &mut Func,
203    assignment: &Assignment,
204    env: &Env,
205    block: Block,
206    preds: &[usize],
207    edits: &mut Vec<Edit>,
208) {
209    let succs = func[block].succs.clone();
210    let single = succs.len() == 1;
211    for call in &succs {
212        let params = func[call.block].params.clone();
213        assert_eq!(
214            params.len(),
215            call.args.len(),
216            "an edge carries what the block it goes to asks for"
217        );
218        if params.is_empty() {
219            continue;
220        }
221        assert!(
222            single || preds[call.block.index()] == 1,
223            "a critical edge has nowhere to put its moves and has to be split before allocation"
224        );
225        let at = if single { At::EndOf(block) } else { At::StartOf(call.block) };
226        edits.extend(edge(assignment, env, &params, &call.args, at));
227    }
228}
229
230/// The moves one edge turns into, in the order they can be made in.
231fn edge(assignment: &Assignment, env: &Env, params: &[Param], args: &[Reg], at: At) -> Vec<Edit> {
232    let mut classes: Vec<RegClass> = params.iter().map(|param| param.class).collect();
233    classes.sort_unstable();
234    classes.dedup();
235
236    let mut edits = Vec::new();
237    for class in classes {
238        // One class at a time, because a scratch register is per class and a value never crosses
239        // from one to another on an edge.
240        let parallel: Vec<Move<Place>> = params
241            .iter()
242            .zip(args)
243            .filter(|(param, _)| param.class == class)
244            .map(|(param, &arg)| Move::new(place(assignment, param.reg), place(assignment, arg)))
245            .collect();
246        let scratch = env.scratch(class);
247        let cycle = *scratch
248            .first()
249            .expect("a class whose values are passed on an edge and which has no scratch register");
250        for mov in moves::sequence(&parallel, Place::Reg(cycle)) {
251            match (mov.to, mov.from) {
252                // No machine here moves one piece of memory into another, so the value goes
253                // through a register, and it is a second scratch rather than the one the ordering
254                // above may be holding a value in for the length of a cycle.
255                (Place::Slot(_), Place::Slot(_)) => {
256                    let through = Place::Reg(*scratch.get(1).expect(
257                        "a class passing a spilled value to a spilled parameter and having only \
258                         one scratch register",
259                    ));
260                    edits.push(Edit { at, mov: Move::new(through, mov.from), class });
261                    edits.push(Edit { at, mov: Move::new(mov.to, through), class });
262                }
263                _ => edits.push(Edit { at, mov, class }),
264            }
265        }
266    }
267    edits
268}
269
270/// How many edges arrive in each block.
271fn preds(func: &Func, blocks: &[Block]) -> Vec<usize> {
272    let mut preds = vec![0; func.block_count()];
273    for &block in blocks {
274        for call in &func[block].succs {
275            preds[call.block.index()] += 1;
276        }
277    }
278    preds
279}
280
281/// Where a register is, whether the allocator put it there or it was already somewhere.
282fn place(assignment: &Assignment, reg: Reg) -> Place {
283    assignment.place(reg).unwrap_or_else(|| Place::Reg(phys(reg)))
284}
285
286/// The physical register a register is, once it has to be one.
287fn phys(reg: Reg) -> PhysReg {
288    reg.phys().expect("a register the assignment says nothing about and that is not a register")
289}
290
291#[cfg(test)]
292mod tests {
293    use rucc_base::Interner;
294    use rucc_mir::{BlockCall, Opcode};
295    use rucc_target::x86_64::{GPR, RAX, RDX, REGS, SYSV};
296
297    use super::*;
298    use crate::assign::assign;
299    use crate::live::Live;
300    use crate::order::Order;
301
302    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
303    fn env() -> Env {
304        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
305        Env::new().with(GPR, order, scratch)
306    }
307
308    /// An environment with that many general purpose registers and one scratch after them.
309    fn narrow(count: usize) -> Env {
310        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 2])
311    }
312
313    /// What a place is called, which is what an assertion reads.
314    fn named(place: Place) -> String {
315        match place {
316            Place::Reg(reg) => REGS.name(GPR, reg).expect("a register").to_string(),
317            Place::Slot(slot) => format!("slot{slot}"),
318        }
319    }
320
321    /// Runs both halves and reports the edits as lines an assertion can read.
322    fn run(func: &mut Func, env: &Env) -> Vec<String> {
323        let order = Order::of(func);
324        let live = Live::of(func, &order);
325        let assignment = assign(func, &order, &live, env);
326        rewrite(func, &assignment, env)
327            .into_iter()
328            .map(|edit| {
329                let at = match edit.at {
330                    At::Before(inst) => format!("before {}", inst.index()),
331                    At::After(inst) => format!("after {}", inst.index()),
332                    At::StartOf(block) => format!("start of {}", block.index()),
333                    At::EndOf(block) => format!("end of {}", block.index()),
334                };
335                format!("{at}: {} = {}", named(edit.mov.to), named(edit.mov.from))
336            })
337            .collect()
338    }
339
340    /// The registers an instruction's operands ended up naming.
341    fn operands(func: &Func, inst: Inst) -> Vec<String> {
342        func[func[inst].operands]
343            .iter()
344            .map(|operand| named(Place::Reg(phys(operand.reg))))
345            .collect()
346    }
347
348    #[test]
349    fn every_operand_ends_up_naming_the_register_its_value_was_given() {
350        let mut names = Interner::new();
351        let mut func = Func::new(names.intern("f"));
352        let opcode = Opcode::new(names.intern("x64.nop"));
353        let block = func.create_block();
354        let first = func.new_vreg(GPR);
355        let second = func.new_vreg(GPR);
356        func.build(block, opcode).def(first, GPR).finish();
357        func.build(block, opcode).def(second, GPR).finish();
358        let read = func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
359
360        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
361        assert_eq!(operands(&func, read), ["rax", "rcx"]);
362    }
363
364    #[test]
365    fn a_register_an_instruction_insists_on_is_moved_into_and_out_of() {
366        let mut names = Interner::new();
367        let mut func = Func::new(names.intern("f"));
368        let opcode = Opcode::new(names.intern("x64.nop"));
369        let block = func.create_block();
370        let dividend = func.new_vreg(GPR);
371        let quotient = func.new_vreg(GPR);
372        func.build(block, opcode).def(dividend, GPR).finish();
373        let divide = func
374            .build(block, opcode)
375            .operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
376            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
377            .finish();
378        func.build(block, opcode).uses(quotient, GPR).finish();
379
380        // The value goes into `rax` in front of the division and the answer comes out of it
381        // behind, which is the move the assignment chose to pay for rather than tie a value to a
382        // register for the whole of its life.
383        assert_eq!(run(&mut func, &env()), ["before 1: rax = rcx", "after 1: rcx = rax"]);
384        assert_eq!(operands(&func, divide), ["rax", "rax"]);
385    }
386
387    #[test]
388    fn a_two_address_instruction_that_did_not_get_its_register_copies_first() {
389        let mut names = Interner::new();
390        let mut func = Func::new(names.intern("f"));
391        let opcode = Opcode::new(names.intern("x64.nop"));
392        let block = func.create_block();
393        let left = func.new_vreg(GPR);
394        let right = func.new_vreg(GPR);
395        let sum = func.new_vreg(GPR);
396        func.build(block, opcode).def(left, GPR).finish();
397        func.build(block, opcode).def(right, GPR).finish();
398        let add = func
399            .build(block, opcode)
400            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
401            .uses(left, GPR)
402            .uses(right, GPR)
403            .finish();
404        func.build(block, opcode).uses(left, GPR).finish();
405
406        // The left value is wanted afterwards, so the answer could not have its register and the
407        // copy in front of the addition is what makes the instruction two address.
408        assert_eq!(run(&mut func, &env()), ["before 2: rdx = rax"]);
409        assert_eq!(operands(&func, add), ["rdx", "rax", "rcx"]);
410    }
411
412    #[test]
413    fn a_two_address_instruction_that_did_get_its_register_copies_nothing() {
414        let mut names = Interner::new();
415        let mut func = Func::new(names.intern("f"));
416        let opcode = Opcode::new(names.intern("x64.nop"));
417        let block = func.create_block();
418        let left = func.new_vreg(GPR);
419        let right = func.new_vreg(GPR);
420        let sum = func.new_vreg(GPR);
421        func.build(block, opcode).def(left, GPR).finish();
422        func.build(block, opcode).def(right, GPR).finish();
423        let add = func
424            .build(block, opcode)
425            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
426            .uses(left, GPR)
427            .uses(right, GPR)
428            .finish();
429        func.build(block, opcode).uses(right, GPR).finish();
430
431        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
432        assert_eq!(operands(&func, add), ["rax", "rax", "rcx"]);
433    }
434
435    #[test]
436    fn a_spilled_value_is_read_into_a_scratch_register_at_each_instruction_that_wants_it() {
437        let mut names = Interner::new();
438        let mut func = Func::new(names.intern("f"));
439        let opcode = Opcode::new(names.intern("x64.nop"));
440        let block = func.create_block();
441        let first = func.new_vreg(GPR);
442        let second = func.new_vreg(GPR);
443        func.build(block, opcode).def(first, GPR).finish();
444        func.build(block, opcode).def(second, GPR).finish();
445        let read = func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
446
447        // One register between two values, so one of them goes to the stack. It is written there
448        // where it is computed and read back where it is wanted, and both ends of that go through
449        // the scratch register that is held out of the allocation order for exactly this.
450        assert_eq!(run(&mut func, &narrow(1)), ["after 1: slot0 = rcx", "before 2: rcx = slot0"]);
451        assert_eq!(operands(&func, read), ["rax", "rcx"]);
452    }
453
454    #[test]
455    fn an_edge_out_of_a_block_with_one_way_to_go_moves_at_the_end_of_it() {
456        let mut names = Interner::new();
457        let mut func = Func::new(names.intern("f"));
458        let opcode = Opcode::new(names.intern("x64.nop"));
459        let head = func.create_block();
460        let tail = func.create_block();
461        let held = func.new_vreg(GPR);
462        let carried = func.new_vreg(GPR);
463        func.build(head, opcode).def(held, GPR).finish();
464        func.build(head, opcode).def(carried, GPR).finish();
465        func.build(head, opcode).uses(held, GPR).finish();
466        let param = func.append_param(tail, GPR);
467        *func.succs_mut(head) = vec![BlockCall::with(tail, vec![carried])];
468        let read = func.build(tail, opcode).uses(param, GPR).finish();
469
470        // The value the edge carries is in the second register, because the first was busy where
471        // the value was written, and the parameter it arrives as is in the first, because by then
472        // it is not. So the edge is a move, and it goes at the end of the block it leaves.
473        assert_eq!(run(&mut func, &env()), ["end of 0: rax = rcx"]);
474        assert_eq!(operands(&func, read), ["rax"]);
475        // Nothing arrives in a block any more and no edge carries anything, which is where SSA
476        // form stops.
477        assert!(func[tail].params.is_empty());
478        assert!(func[head].succs[0].args.is_empty());
479    }
480
481    #[test]
482    fn an_edge_out_of_a_block_with_a_choice_moves_at_the_start_of_where_it_goes() {
483        let mut names = Interner::new();
484        let mut func = Func::new(names.intern("f"));
485        let opcode = Opcode::new(names.intern("x64.nop"));
486        let head = func.create_block();
487        let left = func.create_block();
488        let right = func.create_block();
489        let held = func.new_vreg(GPR);
490        let carried = func.new_vreg(GPR);
491        func.build(head, opcode).def(held, GPR).finish();
492        func.build(head, opcode).def(carried, GPR).finish();
493        func.build(head, opcode).uses(held, GPR).finish();
494        let taken = func.append_param(left, GPR);
495        *func.succs_mut(head) = vec![BlockCall::with(left, vec![carried]), BlockCall::to(right)];
496        func.build(left, opcode).uses(taken, GPR).finish();
497
498        // The move cannot go at the end of the block it leaves, because the other way out of that
499        // block does not want it. It goes at the start of the block it arrives in, which is safe
500        // because nothing else arrives there.
501        assert_eq!(run(&mut func, &env()), ["start of 1: rax = rcx"]);
502    }
503
504    #[test]
505    fn two_values_that_swap_on_an_edge_get_an_order_and_a_scratch_register() {
506        let mut names = Interner::new();
507        let mut func = Func::new(names.intern("f"));
508        let opcode = Opcode::new(names.intern("x64.nop"));
509        let head = func.create_block();
510        let body = func.create_block();
511        let first = func.new_vreg(GPR);
512        let second = func.new_vreg(GPR);
513        func.build(head, opcode).def(first, GPR).finish();
514        func.build(head, opcode).def(second, GPR).finish();
515        let left = func.append_param(body, GPR);
516        let right = func.append_param(body, GPR);
517        *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
518        func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
519        *func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
520
521        // The loop hands each value back the other way round, which is the case no order of two
522        // moves answers, so one of them goes through the scratch register. The edge into the loop
523        // moves nothing, because each value is already where the parameter it feeds lives.
524        assert_eq!(
525            run(&mut func, &env()),
526            ["end of 1: r13 = rcx", "end of 1: rcx = rax", "end of 1: rax = r13"]
527        );
528    }
529
530    #[test]
531    fn a_spilled_value_handed_to_a_spilled_parameter_goes_through_a_register() {
532        let mut names = Interner::new();
533        let mut func = Func::new(names.intern("f"));
534        let opcode = Opcode::new(names.intern("x64.nop"));
535        let head = func.create_block();
536        let body = func.create_block();
537        let first = func.new_vreg(GPR);
538        let second = func.new_vreg(GPR);
539        func.build(head, opcode).def(first, GPR).finish();
540        func.build(head, opcode).def(second, GPR).finish();
541        let left = func.append_param(body, GPR);
542        let right = func.append_param(body, GPR);
543        *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
544        func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
545
546        // One register between the values and the parameters, so a value on the stack is handed to
547        // a parameter on the stack, and no machine here has that instruction. It goes through the
548        // second scratch register rather than the first, which is the one the ordering above is
549        // entitled to be holding a value in.
550        assert_eq!(
551            run(&mut func, &narrow(1)),
552            [
553                "after 1: slot0 = rcx",
554                "before 2: rcx = slot1",
555                "end of 0: rdx = slot0",
556                "end of 0: slot1 = rdx",
557            ]
558        );
559    }
560
561    #[test]
562    #[should_panic(expected = "a critical edge has nowhere to put its moves")]
563    fn a_critical_edge_is_refused() {
564        let mut names = Interner::new();
565        let mut func = Func::new(names.intern("f"));
566        let opcode = Opcode::new(names.intern("x64.nop"));
567        let head = func.create_block();
568        let other = func.create_block();
569        let join = func.create_block();
570        let value = func.new_vreg(GPR);
571        func.build(head, opcode).def(value, GPR).finish();
572        let param = func.append_param(join, GPR);
573        *func.succs_mut(head) = vec![BlockCall::with(join, vec![value]), BlockCall::to(other)];
574        *func.succs_mut(other) = vec![BlockCall::with(join, vec![value])];
575        func.build(join, opcode).uses(param, GPR).finish();
576
577        let _ = run(&mut func, &env());
578    }
579
580    #[test]
581    #[should_panic(expected = "what arrives in a function is not a block parameter")]
582    fn a_parameter_on_the_entry_block_is_refused() {
583        let mut names = Interner::new();
584        let mut func = Func::new(names.intern("f"));
585        let block = func.create_block();
586        let param = func.append_param(block, GPR);
587        let opcode = Opcode::new(names.intern("x64.nop"));
588        func.build(block, opcode).uses(param, GPR).finish();
589
590        let _ = run(&mut func, &env());
591    }
592
593    #[test]
594    fn a_value_already_in_a_register_is_left_where_it_is() {
595        let mut names = Interner::new();
596        let mut func = Func::new(names.intern("f"));
597        let opcode = Opcode::new(names.intern("x64.nop"));
598        let block = func.create_block();
599        let inst = func.build(block, opcode).uses(Reg::physical(RDX), GPR).finish();
600
601        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
602        assert_eq!(operands(&func, inst), ["rdx"]);
603    }
604}