first impl
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
use alloc::vec;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{string::String, vec};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{convert::TryFrom, println};
|
||||
|
||||
use crate::InstPosition;
|
||||
use crate::{
|
||||
cfg::CFGInfo, Allocation, Block, Edit, Function, Inst, MachineEnv, Operand, OperandConstraint,
|
||||
OperandKind, OperandPos, Output, PReg, PRegSet, ProgPoint, RegAllocError, RegClass, SpillSlot,
|
||||
@@ -26,17 +28,18 @@ struct PRegData {
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
struct BlockData {
|
||||
pub allocated: bool,
|
||||
pub reg_allocated: bool,
|
||||
pub params_allocated: bool,
|
||||
}
|
||||
|
||||
struct ReadOnlyData {
|
||||
pub preorder: Vec<Block>,
|
||||
pub postorder: Vec<Block>,
|
||||
pub reg_order_int: Vec<PReg>,
|
||||
pub reg_order_float: Vec<PReg>,
|
||||
}
|
||||
|
||||
impl ReadOnlyData {
|
||||
pub fn init<F: Function>(func: &F, mach_env: &MachineEnv) -> Self {
|
||||
pub fn init<F: Function>(func: &F, mach_env: &MachineEnv, cfg: &CFGInfo) -> Self {
|
||||
let reg_order_int = {
|
||||
let class = RegClass::Int as usize;
|
||||
let amount = mach_env.preferred_regs_by_class[class].len()
|
||||
@@ -60,14 +63,14 @@ impl ReadOnlyData {
|
||||
Self {
|
||||
reg_order_int,
|
||||
reg_order_float,
|
||||
preorder: Self::calc_preorder(func),
|
||||
postorder: cfg.postorder.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reg_order(&self, class: RegClass) -> &[PReg] {
|
||||
match class {
|
||||
RegClass::Int => &self.reg_order_int,
|
||||
RegClass::Float => &self.reg_order_int,
|
||||
RegClass::Float => &self.reg_order_float,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +176,22 @@ impl<'a, F: Function> FastAllocState<'a, F> {
|
||||
let mut inst_alloc_offsets = Vec::with_capacity(func.num_insts());
|
||||
inst_alloc_offsets.resize(func.num_insts(), 0);
|
||||
|
||||
// we need to create the alloc array beforehand because it needs to be sorted by inst index
|
||||
// which we cannot guarantee when iterating through the blocks in reverse post-order
|
||||
let allocs = {
|
||||
let block_count = func.num_blocks();
|
||||
let mut cur_idx = 0;
|
||||
for i in 0..block_count {
|
||||
for inst in func.block_insns(Block::new(i)).iter() {
|
||||
inst_alloc_offsets[inst.index()] = cur_idx as u32;
|
||||
cur_idx += func.inst_operands(inst).len();
|
||||
}
|
||||
}
|
||||
let mut allocs = Vec::with_capacity(cur_idx);
|
||||
allocs.resize(cur_idx, Allocation::none());
|
||||
allocs
|
||||
};
|
||||
|
||||
Self {
|
||||
vregs,
|
||||
pregs,
|
||||
@@ -186,7 +205,7 @@ impl<'a, F: Function> FastAllocState<'a, F> {
|
||||
stack_slot_count_float: u8::try_from(func.spillslot_size(RegClass::Float))
|
||||
.expect("that's a big float"),
|
||||
|
||||
allocs: Vec::new(),
|
||||
allocs,
|
||||
inst_alloc_offsets,
|
||||
edits: Vec::new(),
|
||||
safepoint_slots: Vec::new(),
|
||||
@@ -296,8 +315,15 @@ impl<'a, F: Function> FastAllocState<'a, F> {
|
||||
}
|
||||
|
||||
pub fn assign_preg(&mut self, preg: PReg, vreg: VReg) {
|
||||
// TODO: somewhere assign_preg is called without making sure the vreg is clear (or inspite of it)
|
||||
// need to make sure this is intended behavior
|
||||
self.clear_preg(preg);
|
||||
|
||||
self.pregs[preg.index()].vreg = Some(vreg.vreg() as u32);
|
||||
self.vregs[vreg.vreg()].preg = Some(preg);
|
||||
if self.vregs[vreg.vreg()].reftype {
|
||||
self.reftype_vregs_in_pregs_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_preg(&mut self, preg: PReg) {
|
||||
@@ -356,18 +382,38 @@ pub fn run<F: Function>(
|
||||
}
|
||||
|
||||
let mut state = FastAllocState::init(func, mach_env, &cfg);
|
||||
let const_state = ReadOnlyData::init(func, mach_env);
|
||||
let const_state = ReadOnlyData::init(func, mach_env, &cfg);
|
||||
|
||||
let len = const_state.preorder.len();
|
||||
state.blocks[func.entry_block().index()].params_allocated = true;
|
||||
|
||||
let len = const_state.postorder.len();
|
||||
for i in 0..len {
|
||||
let block = const_state.preorder[i];
|
||||
// when handling branches later, we already have an input mapping for the block params
|
||||
state.blocks[block.index()].allocated = true;
|
||||
let block = const_state.postorder[len - 1 - i];
|
||||
if state.blocks[block.index()].reg_allocated {
|
||||
trace!("Block {} already allocated. Skipping", i);
|
||||
continue;
|
||||
}
|
||||
state.blocks[block.index()].reg_allocated = true;
|
||||
|
||||
trace!("Allocating block {}", i);
|
||||
|
||||
allocate_block_insts(&mut state, &const_state, block)?;
|
||||
handle_out_block_params(&mut state, &const_state, block)?;
|
||||
}
|
||||
|
||||
// we do not iterate the blocks in their index order so the order of edits might not be sorted by progpoint
|
||||
// however it should be nearly sorted
|
||||
state.edits.sort_by_key(|entry| entry.0);
|
||||
|
||||
trace!("Edits:");
|
||||
for edit in &state.edits {
|
||||
match edit.1 {
|
||||
Edit::Move { from, to } => {
|
||||
trace!("At {:?} from {} to {}", edit.0, from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Output {
|
||||
num_spillslots: state.cur_stack_slot_idx as usize,
|
||||
edits: state.edits,
|
||||
@@ -385,15 +431,29 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
block: Block,
|
||||
) -> Result<(), RegAllocError> {
|
||||
for inst in state.func.block_insns(block).iter() {
|
||||
let edit_start_idx = state.edits.len();
|
||||
let clobbers = state.func.inst_clobbers(inst);
|
||||
let operands = state.func.inst_operands(inst);
|
||||
let req_refs_on_stack = state.func.requires_refs_on_stack(inst);
|
||||
let alloc_idx = state.inst_alloc_offsets[inst.index()] as usize;
|
||||
|
||||
let alloc_idx = state.allocs.len();
|
||||
state.inst_alloc_offsets[inst.index()] = alloc_idx as u32;
|
||||
state
|
||||
.allocs
|
||||
.resize(alloc_idx + operands.len(), Allocation::none());
|
||||
trace!(
|
||||
"Allocating Inst {} (refs_on_stack: {}, is_ret: {}, is_branch: {}, alloc_idx: {})",
|
||||
inst.index(),
|
||||
req_refs_on_stack,
|
||||
state.func.is_ret(inst),
|
||||
state.func.is_branch(inst),
|
||||
alloc_idx
|
||||
);
|
||||
let mut str = String::new();
|
||||
for preg in clobbers {
|
||||
if str.is_empty() {
|
||||
str.push_str(&format!("{}", preg));
|
||||
} else {
|
||||
str.push_str(&format!(", {}", preg));
|
||||
}
|
||||
}
|
||||
trace!("Clobbers: {}", str);
|
||||
|
||||
// keep track of which pregs where allocated so we can clear them later on
|
||||
// TODO: wouldnt need this if we look up the inst a vreg was allocated at
|
||||
@@ -413,6 +473,7 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
for vreg in state.reftype_vregs {
|
||||
let data = &state.vregs[vreg.vreg()];
|
||||
if let Some(slot) = data.slot_idx {
|
||||
trace!("Marking vreg {} as saved on stack at {}", vreg, slot);
|
||||
state
|
||||
.safepoint_slots
|
||||
.push((pos, Allocation::stack(SpillSlot::new(slot as usize))));
|
||||
@@ -421,9 +482,32 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
}
|
||||
|
||||
// we allocate fixed defs/uses and stack allocations first
|
||||
trace!("First alloc pass");
|
||||
for (i, op) in operands.iter().enumerate() {
|
||||
let vreg = op.vreg();
|
||||
|
||||
trace!("Operand {}: {}", i, op);
|
||||
if vreg == VReg::invalid() {
|
||||
// it seems cranelift emits fixed reg uses with invalid vregs, handle them here
|
||||
// TODO: treat them like normal vregs by just using last_vreg_index+1 for them?
|
||||
match op.constraint() {
|
||||
OperandConstraint::FixedReg(reg) => {
|
||||
state.clear_preg(reg);
|
||||
regs_allocated.push(reg);
|
||||
state.allocs[alloc_idx + i] = Allocation::reg(reg);
|
||||
trace!("Chose {} for operand {}", reg, i);
|
||||
late_write_disallow_regs.add(reg);
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"Invalid op constraint {:?} for invalid vreg",
|
||||
op.constraint()
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
match op.constraint() {
|
||||
OperandConstraint::FixedReg(reg) => {
|
||||
match op.kind() {
|
||||
@@ -450,10 +534,12 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
return Err(RegAllocError::TooManyLiveRegs);
|
||||
}
|
||||
|
||||
trace!("Operand {}'s allocation may not be used by a late def", i);
|
||||
// late uses cannot share a register with late defs
|
||||
late_write_disallow_regs.add(reg);
|
||||
}
|
||||
regs_allocated.push(reg);
|
||||
trace!("Chose {} for operand {}", reg, i);
|
||||
}
|
||||
OperandKind::Def => {
|
||||
if op.pos() == OperandPos::Late {
|
||||
@@ -469,6 +555,7 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
panic!("early def shares reg or is clobbered");
|
||||
return Err(RegAllocError::TooManyLiveRegs);
|
||||
}
|
||||
trace!("Operand {}'s allocation may not be used by a late def", i);
|
||||
// early defs cannot share a register with late defs
|
||||
late_write_disallow_regs.add(reg);
|
||||
}
|
||||
@@ -527,11 +614,11 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
state.move_to_stack(tmp_reg, vreg, ProgPoint::after(inst));
|
||||
regs_allocated.push(tmp_reg);
|
||||
} else {
|
||||
println!("2");
|
||||
state.alloc_stack_slot(vreg);
|
||||
state.move_to_stack(reg, vreg, ProgPoint::after(inst));
|
||||
regs_allocated.push(reg);
|
||||
}
|
||||
trace!("Chose {} for operand {}", reg, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,14 +629,16 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
if let Some(slot) = &state.vregs[vreg.vreg()].slot_idx {
|
||||
state.allocs[alloc_idx + i] =
|
||||
Allocation::stack(SpillSlot::new(*slot as usize));
|
||||
trace!("Chose slot {} for operand {}", slot, i);
|
||||
} else {
|
||||
return Err(RegAllocError::SSA(vreg, inst));
|
||||
}
|
||||
}
|
||||
OperandKind::Def => {
|
||||
state.allocs[alloc_idx + i] = Allocation::stack(SpillSlot::new(
|
||||
state.alloc_stack_slot(vreg) as usize,
|
||||
));
|
||||
let slot = state.alloc_stack_slot(vreg);
|
||||
state.allocs[alloc_idx + i] =
|
||||
Allocation::stack(SpillSlot::new(slot as usize));
|
||||
trace!("Chose slot {} for operand {}", slot, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,12 +647,18 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
}
|
||||
|
||||
// alloc non-fixed uses and early defs in registers
|
||||
trace!("Second alloc pass");
|
||||
for (i, op) in operands.iter().enumerate() {
|
||||
if op.kind() == OperandKind::Def && op.pos() == OperandPos::Late {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace!("Operand {}: {}", i, op);
|
||||
|
||||
let vreg = op.vreg();
|
||||
if vreg == VReg::invalid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match op.constraint() {
|
||||
OperandConstraint::Reg => {
|
||||
@@ -592,9 +687,10 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
// early def
|
||||
state.assign_preg(reg, vreg);
|
||||
state.alloc_stack_slot(vreg);
|
||||
println!("3");
|
||||
state.move_to_stack(reg, vreg, ProgPoint::after(inst));
|
||||
}
|
||||
|
||||
trace!("Chose {} for operand {}", reg, i);
|
||||
allocated = true;
|
||||
break;
|
||||
}
|
||||
@@ -603,6 +699,7 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
continue;
|
||||
}
|
||||
|
||||
trace!("Ran out of registers for operand {}", i);
|
||||
// No register available
|
||||
// TODO: try to evict vreg that does not need to be in a preg
|
||||
panic!("Out of registers: {:?}", regs_allocated);
|
||||
@@ -616,12 +713,18 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
}
|
||||
|
||||
// alloc non-fixed late defs and reuse
|
||||
trace!("Third alloc pass");
|
||||
for (i, op) in operands.iter().enumerate() {
|
||||
if op.kind() != OperandKind::Def || op.pos() != OperandPos::Late {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace!("Operand {}: {}", i, op);
|
||||
let vreg = op.vreg();
|
||||
if vreg == VReg::invalid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match op.constraint() {
|
||||
OperandConstraint::Reg => {
|
||||
// find first non-allocated register
|
||||
@@ -639,8 +742,8 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
state.clear_preg(reg);
|
||||
state.assign_preg(reg, vreg);
|
||||
state.alloc_stack_slot(vreg);
|
||||
println!("4");
|
||||
state.move_to_stack(reg, vreg, ProgPoint::after(inst));
|
||||
trace!("Chose {} for operand {}", reg, i);
|
||||
allocated = true;
|
||||
break;
|
||||
}
|
||||
@@ -663,7 +766,6 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
state.clear_preg(preg);
|
||||
state.assign_preg(preg, vreg);
|
||||
state.alloc_stack_slot(vreg);
|
||||
println!("5");
|
||||
state.move_to_stack(preg, vreg, ProgPoint::after(inst));
|
||||
}
|
||||
_ => {
|
||||
@@ -674,8 +776,28 @@ fn allocate_block_insts<'a, F: Function>(
|
||||
|
||||
// clear out all allocated regs
|
||||
for reg in regs_allocated {
|
||||
trace!("Clearing {}", reg);
|
||||
state.clear_preg(reg);
|
||||
}
|
||||
|
||||
// fixup edit order
|
||||
let mut first_post_pos = None;
|
||||
for i in edit_start_idx..state.edits.len() {
|
||||
debug_assert!(state.edits[i].0.inst() == inst);
|
||||
match first_post_pos {
|
||||
None => {
|
||||
if state.edits[i].0.pos() == InstPosition::After {
|
||||
first_post_pos = Some(i);
|
||||
}
|
||||
}
|
||||
Some(pos) => {
|
||||
if state.edits[i].0.pos() == InstPosition::Before {
|
||||
state.edits.swap(pos, i);
|
||||
first_post_pos = Some(pos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -686,13 +808,52 @@ fn handle_out_block_params<'a, F: Function>(
|
||||
const_state: &ReadOnlyData,
|
||||
block: Block,
|
||||
) -> Result<(), RegAllocError> {
|
||||
trace!("Allocating outgoing blockparams for {}", block.index());
|
||||
let last_inst = state.func.block_insns(block).last();
|
||||
if !state.func.is_branch(last_inst) {
|
||||
trace!("Last inst {} is not a branch", last_inst.index());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut pregs_used_by_br = PRegSet::empty();
|
||||
{
|
||||
let alloc_start = state.inst_alloc_offsets[last_inst.index()] as usize;
|
||||
let alloc_end = if last_inst.index() + 1 == state.inst_alloc_offsets.len() {
|
||||
state.inst_alloc_offsets.len()
|
||||
} else {
|
||||
state.inst_alloc_offsets[last_inst.index() + 1] as usize
|
||||
};
|
||||
for i in alloc_start..alloc_end {
|
||||
if let Some(reg) = state.allocs[i].clone().as_reg() {
|
||||
pregs_used_by_br.add(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wouldn't need this if the edits for this were made before the moves for the branch inst but that has its own share of problems i think
|
||||
let tmp_reg_int = 'block: {
|
||||
for reg in const_state.reg_order(RegClass::Int) {
|
||||
if !pregs_used_by_br.contains(*reg) {
|
||||
break 'block *reg;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No usable tmp_reg for block param handling");
|
||||
};
|
||||
|
||||
let tmp_reg_float = 'block: {
|
||||
for reg in const_state.reg_order(RegClass::Float) {
|
||||
if !pregs_used_by_br.contains(*reg) {
|
||||
break 'block *reg;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No usable tmp_reg for block param handling");
|
||||
};
|
||||
|
||||
let succs = state.func.block_succs(block);
|
||||
if succs.len() == 1 && state.blocks[succs[0].index()].allocated {
|
||||
if succs.len() == 1 && state.blocks[succs[0].index()].params_allocated {
|
||||
trace!("Only one allocated successor, moving allocations");
|
||||
let succ = succs[0];
|
||||
// move values to the already allocated places
|
||||
let in_params = state.func.block_params(succ);
|
||||
@@ -706,7 +867,11 @@ fn handle_out_block_params<'a, F: Function>(
|
||||
debug_assert!(state.vregs[in_vreg.vreg()].slot_idx.is_some());
|
||||
debug_assert!(state.vregs[out_vreg.vreg()].slot_idx.is_some());
|
||||
|
||||
let tmp_reg = const_state.reg_order(out_vreg.class())[0];
|
||||
let tmp_reg = if out_vreg.class() == RegClass::Int {
|
||||
tmp_reg_int
|
||||
} else {
|
||||
tmp_reg_float
|
||||
};
|
||||
let out_slot = state.vregs[out_vreg.vreg()].slot_idx.unwrap();
|
||||
let in_slot = state.vregs[in_vreg.vreg()].slot_idx.unwrap();
|
||||
|
||||
@@ -726,12 +891,20 @@ fn handle_out_block_params<'a, F: Function>(
|
||||
));
|
||||
}
|
||||
} else {
|
||||
trace!("Successors not allocated. Creating allocation");
|
||||
|
||||
let mut allocs = SmallVec::<[(VReg, u32); 4]>::new();
|
||||
// set incoming block params of successor to the current stack slot
|
||||
for (i, &succ) in state.func.block_succs(block).iter().enumerate() {
|
||||
if state.blocks[succ.index()].allocated {
|
||||
trace!("Creating block {}", succ.index());
|
||||
if state.blocks[succ.index()].params_allocated {
|
||||
return Err(RegAllocError::CritEdge(block, succ));
|
||||
}
|
||||
|
||||
// we allocate the params here
|
||||
// TODO: can there be a problem if the same successor occurs multiple times?
|
||||
state.blocks[succ.index()].params_allocated = true;
|
||||
|
||||
let in_params = state.func.block_params(succ);
|
||||
let out_params = state.func.branch_blockparams(block, last_inst, i);
|
||||
debug_assert_eq!(in_params.len(), out_params.len());
|
||||
@@ -743,29 +916,56 @@ fn handle_out_block_params<'a, F: Function>(
|
||||
debug_assert!(state.vregs[out_vreg.vreg()].slot_idx.is_some());
|
||||
let out_slot_idx = state.vregs[out_vreg.vreg()].slot_idx.unwrap();
|
||||
|
||||
// TODO: if out_vreg dies at this edge, we could reuse its stack slot
|
||||
// TODO: we should also be able to reuse the slot if the successor only has one predecessor (us); check with AE
|
||||
let mut no_alias = false;
|
||||
if !vregs_passed.contains(&out_vreg) {
|
||||
state.vregs[in_vreg.vreg()].slot_idx = Some(out_slot_idx);
|
||||
let mut alloced = false;
|
||||
for alloc in &allocs {
|
||||
if alloc.0 != out_vreg {
|
||||
continue;
|
||||
}
|
||||
|
||||
// we can use the already moved into stack slot
|
||||
state.vregs[in_vreg.vreg()].slot_idx = Some(alloc.1);
|
||||
vregs_passed.push(out_vreg);
|
||||
alloced = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if alloced {
|
||||
continue;
|
||||
}
|
||||
vregs_passed.push(out_vreg);
|
||||
no_alias = true;
|
||||
}
|
||||
|
||||
// need to duplicate to avoid aliasing or create a new stack slot
|
||||
// TODO: this creates multiple duplications for multiple blocks, can be avoided
|
||||
let tmp_reg = if out_vreg.class() == RegClass::Int {
|
||||
tmp_reg_int
|
||||
} else {
|
||||
// need to duplicate to avoid aliasing
|
||||
// TODO: this creates multiple duplications for multiple blocks, can be avoided
|
||||
let tmp_reg = const_state.reg_order(out_vreg.class())[0];
|
||||
let slot = state.create_stack_slot(out_vreg.class());
|
||||
state.edits.push((
|
||||
ProgPoint::before(last_inst),
|
||||
Edit::Move {
|
||||
from: Allocation::stack(SpillSlot::new(out_slot_idx as usize)),
|
||||
to: Allocation::reg(tmp_reg),
|
||||
},
|
||||
));
|
||||
state.edits.push((
|
||||
ProgPoint::before(last_inst),
|
||||
Edit::Move {
|
||||
from: Allocation::reg(tmp_reg),
|
||||
to: Allocation::stack(SpillSlot::new(slot as usize)),
|
||||
},
|
||||
));
|
||||
state.vregs[in_vreg.vreg()].slot_idx = Some(slot);
|
||||
tmp_reg_float
|
||||
};
|
||||
let slot = state.create_stack_slot(out_vreg.class());
|
||||
state.edits.push((
|
||||
ProgPoint::before(last_inst),
|
||||
Edit::Move {
|
||||
from: Allocation::stack(SpillSlot::new(out_slot_idx as usize)),
|
||||
to: Allocation::reg(tmp_reg),
|
||||
},
|
||||
));
|
||||
state.edits.push((
|
||||
ProgPoint::before(last_inst),
|
||||
Edit::Move {
|
||||
from: Allocation::reg(tmp_reg),
|
||||
to: Allocation::stack(SpillSlot::new(slot as usize)),
|
||||
},
|
||||
));
|
||||
state.vregs[in_vreg.vreg()].slot_idx = Some(slot);
|
||||
|
||||
if no_alias {
|
||||
allocs.push((out_vreg, slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user