use crate::global_isel::gisel_machine_ir::{
GInstruction, GMachineBasicBlock, GMachineFunction, GOpcode, MOperand, VReg,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegisterBank {
GPR,
FPR,
CCR,
}
impl RegisterBank {
pub fn name(&self) -> &'static str {
match self {
RegisterBank::GPR => "GPR",
RegisterBank::FPR => "FPR",
RegisterBank::CCR => "CCR",
}
}
pub fn size_in_bits(&self) -> u32 {
match self {
RegisterBank::GPR => 64,
RegisterBank::FPR => 128,
RegisterBank::CCR => 1,
}
}
}
#[derive(Debug, Clone)]
pub struct BankMapping {
pub vreg: VReg,
pub bank: RegisterBank,
}
impl BankMapping {
pub fn new(vreg: VReg, bank: RegisterBank) -> Self {
BankMapping { vreg, bank }
}
}
#[derive(Debug, Clone)]
pub struct TargetRegBankInfo {
pub target_name: String,
pub gpr: RegisterBankInfo,
pub fpr: RegisterBankInfo,
pub ccr: RegisterBankInfo,
}
#[derive(Debug, Clone)]
pub struct RegisterBankInfo {
pub bank: RegisterBank,
pub num_regs: u32,
pub reg_size_bits: u32,
}
impl TargetRegBankInfo {
pub fn x86() -> Self {
TargetRegBankInfo {
target_name: "x86_64".to_string(),
gpr: RegisterBankInfo {
bank: RegisterBank::GPR,
num_regs: 16,
reg_size_bits: 64,
},
fpr: RegisterBankInfo {
bank: RegisterBank::FPR,
num_regs: 32, reg_size_bits: 256,
},
ccr: RegisterBankInfo {
bank: RegisterBank::CCR,
num_regs: 1,
reg_size_bits: 1,
},
}
}
pub fn arm() -> Self {
TargetRegBankInfo {
target_name: "arm".to_string(),
gpr: RegisterBankInfo {
bank: RegisterBank::GPR,
num_regs: 16,
reg_size_bits: 32,
},
fpr: RegisterBankInfo {
bank: RegisterBank::FPR,
num_regs: 32, reg_size_bits: 64,
},
ccr: RegisterBankInfo {
bank: RegisterBank::CCR,
num_regs: 1,
reg_size_bits: 1,
},
}
}
pub fn riscv() -> Self {
TargetRegBankInfo {
target_name: "riscv64".to_string(),
gpr: RegisterBankInfo {
bank: RegisterBank::GPR,
num_regs: 32,
reg_size_bits: 64,
},
fpr: RegisterBankInfo {
bank: RegisterBank::FPR,
num_regs: 32, reg_size_bits: 64,
},
ccr: RegisterBankInfo {
bank: RegisterBank::CCR,
num_regs: 0, reg_size_bits: 0,
},
}
}
}
struct BankSelectionState {
assignments: HashMap<VReg, RegisterBank>,
bank_info: TargetRegBankInfo,
total_cost: u64,
copy_count: usize,
}
impl BankSelectionState {
fn new(bank_info: TargetRegBankInfo) -> Self {
BankSelectionState {
assignments: HashMap::new(),
bank_info,
total_cost: 0,
copy_count: 0,
}
}
fn assign(&mut self, vreg: VReg, bank: RegisterBank) {
self.assignments.insert(vreg, bank);
}
fn get_bank(&self, vreg: VReg) -> Option<RegisterBank> {
self.assignments.get(&vreg).copied()
}
}
pub struct CostModel {
bank_transition_costs: HashMap<(RegisterBank, RegisterBank), u64>,
}
impl CostModel {
pub fn new() -> Self {
let mut costs = HashMap::new();
costs.insert((RegisterBank::GPR, RegisterBank::GPR), 0);
costs.insert((RegisterBank::FPR, RegisterBank::FPR), 0);
costs.insert((RegisterBank::CCR, RegisterBank::CCR), 0);
costs.insert((RegisterBank::GPR, RegisterBank::FPR), 10);
costs.insert((RegisterBank::FPR, RegisterBank::GPR), 10);
costs.insert((RegisterBank::GPR, RegisterBank::CCR), 5);
costs.insert((RegisterBank::CCR, RegisterBank::GPR), 5);
costs.insert((RegisterBank::FPR, RegisterBank::CCR), 15);
costs.insert((RegisterBank::CCR, RegisterBank::FPR), 15);
CostModel {
bank_transition_costs: costs,
}
}
pub fn transition_cost(&self, from: RegisterBank, to: RegisterBank) -> u64 {
self.bank_transition_costs
.get(&(from, to))
.copied()
.unwrap_or(20)
}
pub fn instruction_mapping_cost(
&self,
inst: &GInstruction,
assignments: &HashMap<VReg, RegisterBank>,
) -> u64 {
let mut cost = 0;
let pref_bank = default_bank_for_opcode(inst.opcode);
for op in &inst.operands {
if let Some(vreg) = op.as_vreg() {
if let Some(&bank) = assignments.get(&vreg) {
if bank != pref_bank {
cost += self.transition_cost(bank, pref_bank);
}
}
}
}
cost
}
}
impl Default for CostModel {
fn default() -> Self {
Self::new()
}
}
fn default_bank_for_opcode(opcode: GOpcode) -> RegisterBank {
match opcode {
GOpcode::G_FADD
| GOpcode::G_FSUB
| GOpcode::G_FMUL
| GOpcode::G_FDIV
| GOpcode::G_FREM
| GOpcode::G_FNEG
| GOpcode::G_FABS
| GOpcode::G_FCEIL
| GOpcode::G_FFLOOR
| GOpcode::G_FSQRT
| GOpcode::G_FPOW
| GOpcode::G_FEXP
| GOpcode::G_FLOG
| GOpcode::G_FMA => RegisterBank::FPR,
GOpcode::G_FCMP => RegisterBank::CCR,
GOpcode::G_ICMP => RegisterBank::GPR,
_ => RegisterBank::GPR,
}
}
pub struct GreedyRegBankSelect {
pub bank_info: TargetRegBankInfo,
pub cost_model: CostModel,
pub copies_inserted: usize,
pub instructions_processed: usize,
}
impl GreedyRegBankSelect {
pub fn new(bank_info: TargetRegBankInfo) -> Self {
GreedyRegBankSelect {
bank_info,
cost_model: CostModel::new(),
copies_inserted: 0,
instructions_processed: 0,
}
}
pub fn run(&mut self, mf: &mut GMachineFunction) {
self.copies_inserted = 0;
self.instructions_processed = 0;
let mut state = BankSelectionState::new(self.bank_info.clone());
for block_idx in 0..mf.blocks.len() {
self.process_block(&mut state, mf, block_idx);
}
}
fn process_block(
&mut self,
state: &mut BankSelectionState,
mf: &mut GMachineFunction,
block_idx: usize,
) {
let block = &mf.blocks[block_idx];
let mut instructions = block.instructions.clone();
let mut new_instructions: Vec<GInstruction> = Vec::new();
for inst in instructions.drain(..) {
self.instructions_processed += 1;
let preferred_bank = default_bank_for_opcode(inst.opcode);
if let Some(def) = inst.def_vreg() {
state.assign(def, preferred_bank);
}
let mut modified_inst = inst.clone();
for i in 1..modified_inst.operands.len() {
if let Some(vreg) = modified_inst.operands[i].as_vreg() {
let actual_bank = state.get_bank(vreg).unwrap_or(preferred_bank);
if actual_bank != preferred_bank {
let copy_dst = state.bank_info.gpr.num_regs + self.copies_inserted as u32 + 1000;
let copy_inst = GInstruction::with_operands(
GOpcode::COPY,
vec![MOperand::vreg(copy_dst), MOperand::vreg(vreg)],
);
new_instructions.push(copy_inst);
modified_inst.operands[i] = MOperand::vreg(copy_dst);
state.assign(copy_dst, preferred_bank);
self.copies_inserted += 1;
}
}
}
new_instructions.push(modified_inst);
}
mf.blocks[block_idx].instructions = new_instructions;
}
}
impl Default for GreedyRegBankSelect {
fn default() -> Self {
Self::new(TargetRegBankInfo::x86())
}
}
pub struct ExhaustiveRegBankSelect {
pub bank_info: TargetRegBankInfo,
pub cost_model: CostModel,
pub max_group_size: usize,
}
impl ExhaustiveRegBankSelect {
pub fn new(bank_info: TargetRegBankInfo) -> Self {
ExhaustiveRegBankSelect {
bank_info,
cost_model: CostModel::new(),
max_group_size: 6,
}
}
pub fn run(&mut self, mf: &mut GMachineFunction) {
for block_idx in 0..mf.blocks.len() {
self.process_block(mf, block_idx);
}
}
fn process_block(&mut self, mf: &mut GMachineFunction, block_idx: usize) {
let block = &mf.blocks[block_idx];
let inst_count = block.instruction_count();
if inst_count == 0 {
return;
}
let mut vregs: Vec<VReg> = Vec::new();
let mut vreg_set: std::collections::HashSet<VReg> = std::collections::HashSet::new();
for inst in &block.instructions {
for op in &inst.operands {
if let Some(vreg) = op.as_vreg() {
if vreg_set.insert(vreg) {
vregs.push(vreg);
}
}
}
}
if vregs.len() > self.max_group_size * 5 {
return;
}
let banks = [RegisterBank::GPR, RegisterBank::FPR, RegisterBank::CCR];
let num_vregs = vregs.len();
let total_combos = 3_u64.pow(num_vregs as u32);
let mut best_cost = u64::MAX;
let mut best_assignment: HashMap<VReg, RegisterBank> = HashMap::new();
let max_combos = total_combos.min(3_u64.pow(self.max_group_size as u32));
for combo in 0..max_combos {
let mut assignment = HashMap::new();
let mut temp = combo;
for &vreg in &vregs {
let bank_idx = (temp % 3) as usize;
assignment.insert(vreg, banks[bank_idx]);
temp /= 3;
}
let cost = self.evaluate_assignment(&block.instructions, &assignment);
if cost < best_cost {
best_cost = cost;
best_assignment = assignment;
}
}
self.apply_assignment(mf, block_idx, &best_assignment);
}
fn evaluate_assignment(
&self,
instructions: &[GInstruction],
assignments: &HashMap<VReg, RegisterBank>,
) -> u64 {
let mut total = 0;
for inst in instructions {
total += self.cost_model.instruction_mapping_cost(inst, assignments);
}
total
}
fn apply_assignment(
&self,
mf: &mut GMachineFunction,
block_idx: usize,
assignments: &HashMap<VReg, RegisterBank>,
) {
let block = &mf.blocks[block_idx];
let mut new_instructions: Vec<GInstruction> = Vec::new();
for inst in &block.instructions {
let pref_bank = default_bank_for_opcode(inst.opcode);
let mut modified = inst.clone();
for i in 0..modified.operands.len() {
if let Some(vreg) = modified.operands[i].as_vreg() {
let assigned = assignments.get(&vreg).copied().unwrap_or(pref_bank);
if assigned != pref_bank {
let copy_dst = vreg + 5000;
let copy_inst = GInstruction::with_operands(
GOpcode::COPY,
vec![MOperand::vreg(copy_dst), MOperand::vreg(vreg)],
);
new_instructions.push(copy_inst);
modified.operands[i] = MOperand::vreg(copy_dst);
}
}
}
new_instructions.push(modified);
}
mf.blocks[block_idx].instructions = new_instructions;
}
}
impl Default for ExhaustiveRegBankSelect {
fn default() -> Self {
Self::new(TargetRegBankInfo::x86())
}
}
pub struct RepairingPlacement {
copies_inserted: usize,
cost_model: CostModel,
}
impl RepairingPlacement {
pub fn new() -> Self {
RepairingPlacement {
copies_inserted: 0,
cost_model: CostModel::new(),
}
}
pub fn repair(&mut self, mf: &mut GMachineFunction, assignments: &HashMap<VReg, RegisterBank>) -> usize {
self.copies_inserted = 0;
for block_idx in 0..mf.blocks.len() {
let block = &mf.blocks[block_idx];
let mut repaired: Vec<GInstruction> = Vec::new();
for inst in &block.instructions {
let pref_bank = default_bank_for_opcode(inst.opcode);
let mut modified = inst.clone();
let mut needs_copy_before = false;
let mut copy_src = 0;
let mut copy_dst = 0;
let mut copy_idx = 0;
for (i, op) in inst.operands.iter().enumerate() {
if let Some(vreg) = op.as_vreg() {
let bank = assignments.get(&vreg).copied().unwrap_or(pref_bank);
if bank != pref_bank {
copy_src = vreg;
copy_dst = vreg + 7000;
modified.operands[i] = MOperand::vreg(copy_dst);
needs_copy_before = true;
copy_idx = i;
}
}
}
if needs_copy_before {
let copy_inst = GInstruction::with_operands(
GOpcode::COPY,
vec![MOperand::vreg(copy_dst), MOperand::vreg(copy_src)],
);
repaired.push(copy_inst);
self.copies_inserted += 1;
let _ = copy_idx;
}
repaired.push(modified);
}
mf.blocks[block_idx].instructions = repaired;
}
self.copies_inserted
}
}
impl Default for RepairingPlacement {
fn default() -> Self {
Self::new()
}
}
pub struct RegBankSelectPass {
pub bank_info: TargetRegBankInfo,
pub greedy: GreedyRegBankSelect,
pub exhaustive: ExhaustiveRegBankSelect,
pub repair: RepairingPlacement,
pub strategy: RegBankSelectStrategy,
pub copies_inserted: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegBankSelectStrategy {
Greedy,
Exhaustive,
Hybrid,
}
impl RegBankSelectPass {
pub fn new(bank_info: TargetRegBankInfo, strategy: RegBankSelectStrategy) -> Self {
RegBankSelectPass {
greedy: GreedyRegBankSelect::new(bank_info.clone()),
exhaustive: ExhaustiveRegBankSelect::new(bank_info.clone()),
repair: RepairingPlacement::new(),
bank_info,
strategy,
copies_inserted: 0,
}
}
pub fn run(&mut self, mf: &mut GMachineFunction) {
self.copies_inserted = 0;
match self.strategy {
RegBankSelectStrategy::Greedy => {
self.greedy.run(mf);
self.copies_inserted = self.greedy.copies_inserted;
}
RegBankSelectStrategy::Exhaustive => {
self.exhaustive.run(mf);
}
RegBankSelectStrategy::Hybrid => {
self.greedy.run(mf);
self.exhaustive.run(mf);
self.copies_inserted = self.greedy.copies_inserted;
}
}
}
}
impl Default for RegBankSelectPass {
fn default() -> Self {
Self::new(TargetRegBankInfo::x86(), RegBankSelectStrategy::Greedy)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_bank_names() {
assert_eq!(RegisterBank::GPR.name(), "GPR");
assert_eq!(RegisterBank::FPR.name(), "FPR");
assert_eq!(RegisterBank::CCR.name(), "CCR");
}
#[test]
fn test_default_bank_for_opcode() {
assert_eq!(default_bank_for_opcode(GOpcode::G_ADD), RegisterBank::GPR);
assert_eq!(default_bank_for_opcode(GOpcode::G_FADD), RegisterBank::FPR);
assert_eq!(default_bank_for_opcode(GOpcode::G_ICMP), RegisterBank::GPR);
assert_eq!(default_bank_for_opcode(GOpcode::G_FCMP), RegisterBank::CCR);
}
#[test]
fn test_cost_model_same_bank() {
let cost_model = CostModel::new();
assert_eq!(cost_model.transition_cost(RegisterBank::GPR, RegisterBank::GPR), 0);
assert_eq!(cost_model.transition_cost(RegisterBank::FPR, RegisterBank::FPR), 0);
}
#[test]
fn test_cost_model_cross_bank() {
let cost_model = CostModel::new();
assert!(cost_model.transition_cost(RegisterBank::GPR, RegisterBank::FPR) > 0);
assert!(cost_model.transition_cost(RegisterBank::FPR, RegisterBank::CCR) > 0);
}
#[test]
fn test_target_reg_bank_info_x86() {
let info = TargetRegBankInfo::x86();
assert_eq!(info.target_name, "x86_64");
assert_eq!(info.gpr.bank, RegisterBank::GPR);
assert_eq!(info.gpr.num_regs, 16);
assert_eq!(info.fpr.num_regs, 32);
}
#[test]
fn test_target_reg_bank_info_arm() {
let info = TargetRegBankInfo::arm();
assert_eq!(info.target_name, "arm");
assert_eq!(info.gpr.reg_size_bits, 32);
}
#[test]
fn test_target_reg_bank_info_riscv() {
let info = TargetRegBankInfo::riscv();
assert_eq!(info.target_name, "riscv64");
assert_eq!(info.gpr.num_regs, 32);
assert_eq!(info.fpr.num_regs, 32);
assert_eq!(info.ccr.num_regs, 0); }
#[test]
fn test_greedy_regbank_select() {
let mut mf = GMachineFunction::new("test".to_string());
let entry = mf.push_block("entry".to_string());
let vreg0 = mf.new_vreg();
let vreg1 = mf.new_vreg();
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_CONSTANT,
vec![MOperand::vreg(vreg0), MOperand::imm(42)],
));
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_FADD,
vec![MOperand::vreg(vreg1), MOperand::vreg(vreg0), MOperand::vreg(vreg0)],
));
let mut selector = GreedyRegBankSelect::new(TargetRegBankInfo::x86());
selector.run(&mut mf);
assert!(selector.instructions_processed >= 2);
}
#[test]
fn test_greedy_regbank_default() {
let selector = GreedyRegBankSelect::default();
assert_eq!(selector.bank_info.target_name, "x86_64");
assert_eq!(selector.copies_inserted, 0);
}
#[test]
fn test_repairing_placement() {
let mut mf = GMachineFunction::new("test".to_string());
let entry = mf.push_block("entry".to_string());
let vreg0 = mf.new_vreg();
let vreg1 = mf.new_vreg();
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_CONSTANT,
vec![MOperand::vreg(vreg0), MOperand::imm(42)],
));
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_FADD,
vec![MOperand::vreg(vreg1), MOperand::vreg(vreg0), MOperand::vreg(vreg0)],
));
let mut assignments = HashMap::new();
assignments.insert(vreg0, RegisterBank::GPR);
assignments.insert(vreg1, RegisterBank::FPR);
let mut repair = RepairingPlacement::new();
let copies = repair.repair(&mut mf, &assignments);
assert!(copies > 0 || mf.blocks[entry].instruction_count() > 2);
}
#[test]
fn test_regbank_select_pass_greedy() {
let mut mf = GMachineFunction::new("test".to_string());
let entry = mf.push_block("entry".to_string());
let vreg = mf.new_vreg();
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_CONSTANT,
vec![MOperand::vreg(vreg), MOperand::imm(0)],
));
let mut pass = RegBankSelectPass::new(
TargetRegBankInfo::x86(),
RegBankSelectStrategy::Greedy,
);
pass.run(&mut mf);
assert!(pass.greedy.instructions_processed >= 1);
}
#[test]
fn test_regbank_select_strategy() {
assert_eq!(
RegBankSelectStrategy::Greedy,
RegBankSelectStrategy::Greedy
);
assert_ne!(
RegBankSelectStrategy::Greedy,
RegBankSelectStrategy::Exhaustive
);
}
#[test]
fn test_exhaustive_regbank_select() {
let mut mf = GMachineFunction::new("test".to_string());
let entry = mf.push_block("entry".to_string());
let vreg0 = mf.new_vreg();
let vreg1 = mf.new_vreg();
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_ADD,
vec![MOperand::vreg(vreg0), MOperand::vreg(1), MOperand::vreg(2)],
));
mf.blocks[entry].push_instruction(GInstruction::with_operands(
GOpcode::G_FADD,
vec![MOperand::vreg(vreg1), MOperand::vreg(vreg0), MOperand::vreg(vreg0)],
));
let mut selector = ExhaustiveRegBankSelect::new(TargetRegBankInfo::x86());
selector.run(&mut mf);
}
#[test]
fn test_bank_mapping() {
let mapping = BankMapping::new(42, RegisterBank::FPR);
assert_eq!(mapping.vreg, 42);
assert_eq!(mapping.bank, RegisterBank::FPR);
}
#[test]
fn test_register_bank_size() {
assert_eq!(RegisterBank::GPR.size_in_bits(), 64);
assert_eq!(RegisterBank::FPR.size_in_bits(), 128);
assert_eq!(RegisterBank::CCR.size_in_bits(), 1);
}
}