#![allow(non_upper_case_globals, dead_code)]
use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone)]
pub struct X86InstrBundling {
pub microarch: BundleMicroArch,
pub is_64bit: bool,
pub config: BundleConfig,
pub stats: BundleStats,
pub current_bundle: Option<InstrBundle>,
pub bundles: Vec<InstrBundle>,
pub code_offset: u64,
pub labels: HashMap<String, u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BundleMicroArch {
GenericInOrder,
Bonnell,
Saltwell,
Silvermont,
Airmont,
Goldmont,
GoldmontPlus,
Tremont,
Gracemont,
Crestmont,
Skymont,
Darkmont,
OutOfOrder,
}
impl BundleMicroArch {
pub fn bundle_size(&self) -> u32 {
match self {
BundleMicroArch::GenericInOrder => 8,
BundleMicroArch::Bonnell => 8,
BundleMicroArch::Saltwell => 8,
BundleMicroArch::Silvermont => 8,
BundleMicroArch::Airmont => 8,
BundleMicroArch::Goldmont => 16,
BundleMicroArch::GoldmontPlus => 16,
BundleMicroArch::Tremont => 16,
BundleMicroArch::Gracemont => 16,
BundleMicroArch::Crestmont => 16,
BundleMicroArch::Skymont => 16,
BundleMicroArch::Darkmont => 16,
BundleMicroArch::OutOfOrder => 16,
}
}
pub fn decode_width(&self) -> u8 {
match self {
BundleMicroArch::GenericInOrder => 2,
BundleMicroArch::Bonnell => 2,
BundleMicroArch::Saltwell => 2,
BundleMicroArch::Silvermont => 2,
BundleMicroArch::Airmont => 2,
BundleMicroArch::Goldmont => 3,
BundleMicroArch::GoldmontPlus => 3,
BundleMicroArch::Tremont => 6, BundleMicroArch::Gracemont => 6,
BundleMicroArch::Crestmont => 6,
BundleMicroArch::Skymont => 6,
BundleMicroArch::Darkmont => 6,
BundleMicroArch::OutOfOrder => 6,
}
}
pub fn requires_branch_at_bundle_end(&self) -> bool {
match self {
BundleMicroArch::Bonnell => true,
BundleMicroArch::Saltwell => true,
BundleMicroArch::Silvermont => true,
BundleMicroArch::Airmont => true,
_ => false,
}
}
pub fn supports_bundle_locking(&self) -> bool {
match self {
BundleMicroArch::Bonnell
| BundleMicroArch::Saltwell
| BundleMicroArch::Silvermont
| BundleMicroArch::Airmont => true,
BundleMicroArch::Goldmont
| BundleMicroArch::GoldmontPlus
| BundleMicroArch::Tremont
| BundleMicroArch::Gracemont
| BundleMicroArch::Crestmont
| BundleMicroArch::Skymont
| BundleMicroArch::Darkmont => true,
BundleMicroArch::OutOfOrder => false,
_ => false,
}
}
pub fn max_prefix_bytes(&self) -> u8 {
match self {
BundleMicroArch::Bonnell | BundleMicroArch::Saltwell => 2,
BundleMicroArch::Silvermont | BundleMicroArch::Airmont => 3,
BundleMicroArch::Goldmont | BundleMicroArch::GoldmontPlus => 4,
_ => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct BundleConfig {
pub bundle_size_override: u32,
pub max_instrs_per_bundle: u8,
pub enforce_alignment: bool,
pub pad_with_nops: bool,
pub lock_bundles: bool,
pub branch_at_bundle_end: bool,
pub adaptive_bundling: bool,
pub allow_macro_fusion_cross_bundle: bool,
}
impl Default for BundleConfig {
fn default() -> Self {
BundleConfig {
bundle_size_override: 0,
max_instrs_per_bundle: 0,
enforce_alignment: true,
pad_with_nops: true,
lock_bundles: false,
branch_at_bundle_end: false,
adaptive_bundling: false,
allow_macro_fusion_cross_bundle: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BundleStats {
pub total_bundles: u64,
pub total_instructions: u64,
pub nop_padding_bytes: u64,
pub boundary_splits: u64,
pub locked_bundles: u64,
pub avg_instrs_per_bundle: f64,
pub avg_utilization: f64,
}
#[derive(Debug, Clone)]
pub struct BundleInstr {
pub id: u64,
pub mnemonic: String,
pub size: u8,
pub is_branch: bool,
pub is_indirect_branch: bool,
pub is_call: bool,
pub is_return: bool,
pub prefix_bytes: u8,
pub uop_count: u8,
pub is_macro_fused: bool,
pub macro_fuse_partner: Option<u64>,
pub is_nop: bool,
pub original_offset: u64,
pub uses_lock_prefix: bool,
pub is_memory: bool,
pub bytes: Vec<u8>,
}
impl BundleInstr {
pub fn new(id: u64, mnemonic: &str, bytes: Vec<u8>) -> Self {
let size = bytes.len() as u8;
BundleInstr {
id,
mnemonic: mnemonic.to_string(),
size,
is_branch: false,
is_indirect_branch: false,
is_call: false,
is_return: false,
prefix_bytes: 0,
uop_count: 1,
is_macro_fused: false,
macro_fuse_partner: None,
is_nop: false,
original_offset: 0,
uses_lock_prefix: false,
is_memory: false,
bytes,
}
}
pub fn mark_branch(&mut self, is_indirect: bool) {
self.is_branch = true;
self.is_indirect_branch = is_indirect;
}
pub fn mark_call(&mut self) {
self.is_call = true;
self.is_branch = true;
}
pub fn mark_return(&mut self) {
self.is_return = true;
self.is_branch = true;
}
pub fn classify(&self) -> InstrClass {
if self.is_nop {
InstrClass::Nop
} else if self.is_return {
InstrClass::Return
} else if self.is_call {
InstrClass::Call
} else if self.is_indirect_branch {
InstrClass::IndirectBranch
} else if self.is_branch {
InstrClass::ConditionalBranch
} else if self.uses_lock_prefix {
InstrClass::Locked
} else if self.is_memory {
InstrClass::Memory
} else {
InstrClass::Alu
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InstrClass {
Alu,
Memory,
ConditionalBranch,
UnconditionalBranch,
IndirectBranch,
Call,
Return,
Locked,
Nop,
Complex,
}
impl InstrClass {
pub fn terminates_bundle(&self, microarch: BundleMicroArch) -> bool {
match self {
InstrClass::IndirectBranch | InstrClass::Call | InstrClass::Return => true,
InstrClass::ConditionalBranch | InstrClass::UnconditionalBranch => {
microarch.requires_branch_at_bundle_end()
}
InstrClass::Locked => true,
_ => false,
}
}
pub fn forces_new_bundle(&self, microarch: BundleMicroArch) -> bool {
match self {
InstrClass::Locked => true,
InstrClass::Complex => true,
InstrClass::IndirectBranch => true,
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Bundle {
pub id: u64,
pub offset: u64,
pub size: u32,
pub instructions: Vec<BundleInstr>,
pub used_bytes: u32,
pub is_locked: bool,
pub ends_with_branch: bool,
pub alignment: u32,
pub flags: BundleFlags,
}
#[derive(Debug, Clone, Default)]
pub struct BundleFlags {
pub function_entry: bool,
pub loop_header: bool,
pub branch_target: bool,
pub has_padding: bool,
pub fetch_aligned: bool,
pub is_full: bool,
}
pub type InstrBundle = Bundle;
impl X86InstrBundling {
pub fn new(microarch: BundleMicroArch, is_64bit: bool) -> Self {
X86InstrBundling {
microarch,
is_64bit,
config: BundleConfig::default(),
stats: BundleStats::default(),
current_bundle: None,
bundles: vec![],
code_offset: 0,
labels: HashMap::new(),
}
}
pub fn with_config(microarch: BundleMicroArch, is_64bit: bool, config: BundleConfig) -> Self {
let mut engine = Self::new(microarch, is_64bit);
engine.config = config;
engine
}
pub fn bundle_size(&self) -> u32 {
if self.config.bundle_size_override > 0 {
self.config.bundle_size_override
} else {
self.microarch.bundle_size()
}
}
pub fn max_instrs_per_bundle(&self) -> u8 {
if self.config.max_instrs_per_bundle > 0 {
self.config.max_instrs_per_bundle
} else {
self.microarch.decode_width()
}
}
pub fn begin_bundle(&mut self) {
let size = self.bundle_size();
let alignment = if self.config.enforce_alignment {
size
} else {
1
};
if self.config.enforce_alignment && self.code_offset % size as u64 != 0 {
let pad = (size as u64) - (self.code_offset % size as u64);
self.code_offset += pad;
self.stats.nop_padding_bytes += pad;
}
let bundle = Bundle {
id: self.bundles.len() as u64,
offset: self.code_offset,
size,
instructions: vec![],
used_bytes: 0,
is_locked: self.config.lock_bundles,
ends_with_branch: false,
alignment,
flags: BundleFlags::default(),
};
self.current_bundle = Some(bundle);
}
pub fn add_instruction(&mut self, instr: &BundleInstr) -> BundleResult {
let result = self.try_add_instruction(instr);
match &result {
BundleResult::Added => {
self.stats.total_instructions += 1;
}
BundleResult::NewBundleNeeded => {
self.stats.boundary_splits += 1;
}
_ => {}
}
result
}
pub fn try_add_instruction(&mut self, instr: &BundleInstr) -> BundleResult {
if self.current_bundle.is_none() {
self.begin_bundle();
}
let bundle = self.current_bundle.as_ref().unwrap();
let bundle_size = bundle.size;
let max_instrs = self.max_instrs_per_bundle();
if bundle.instructions.len() >= max_instrs as usize {
return BundleResult::NewBundleNeeded;
}
let remaining = bundle_size - bundle.used_bytes;
if (instr.size as u32) > remaining {
return BundleResult::NewBundleNeeded;
}
let class = instr.classify();
if class.forces_new_bundle(self.microarch) && !bundle.instructions.is_empty() {
return BundleResult::NewBundleNeeded;
}
if instr.prefix_bytes > self.microarch.max_prefix_bytes() {
if matches!(
self.microarch,
BundleMicroArch::Bonnell | BundleMicroArch::Saltwell
) {
if !bundle.instructions.is_empty() {
return BundleResult::NewBundleNeeded;
}
}
}
if instr.is_macro_fused {
let partner_id = instr.macro_fuse_partner;
let partner_in_bundle = bundle.instructions.iter().any(|i| Some(i.id) == partner_id);
if partner_in_bundle {
if (instr.size as u32) > remaining {
if self.config.allow_macro_fusion_cross_bundle {
} else {
return BundleResult::FusionPairSplit;
}
}
}
}
if class.terminates_bundle(self.microarch) && !bundle.instructions.is_empty() {
if (instr.size as u32) <= remaining {
return BundleResult::Added;
} else {
return BundleResult::NewBundleNeeded;
}
}
if instr.is_nop && (instr.size as u32) <= remaining {
return BundleResult::Added;
}
BundleResult::Added
}
pub fn commit_instruction(&mut self, instr: BundleInstr) {
if self.current_bundle.is_none() {
self.begin_bundle();
}
let bundle = self.current_bundle.as_mut().unwrap();
let class = instr.classify();
bundle.used_bytes += instr.size as u32;
bundle.instructions.push(instr);
if class.terminates_bundle(self.microarch) {
bundle.ends_with_branch = true;
self.finish_bundle();
}
}
pub fn finish_bundle(&mut self) {
if let Some(mut bundle) = self.current_bundle.take() {
if self.config.pad_with_nops && bundle.used_bytes < bundle.size {
let pad_size = bundle.size - bundle.used_bytes;
let nop_instr = BundleInstr {
id: u64::MAX,
mnemonic: "NOP".to_string(),
size: pad_size as u8,
is_branch: false,
is_indirect_branch: false,
is_call: false,
is_return: false,
prefix_bytes: 0,
uop_count: 0,
is_macro_fused: false,
macro_fuse_partner: None,
is_nop: true,
original_offset: self.code_offset,
uses_lock_prefix: false,
is_memory: false,
bytes: vec![0x90; pad_size as usize],
};
bundle.instructions.push(nop_instr);
bundle.used_bytes = bundle.size;
bundle.flags.has_padding = true;
self.stats.nop_padding_bytes += pad_size as u64;
}
bundle.flags.is_full = bundle.used_bytes >= bundle.size;
if bundle.instructions.iter().any(|i| i.is_branch) {
bundle.ends_with_branch = true;
}
self.stats.total_bundles += 1;
if self.config.lock_bundles {
self.stats.locked_bundles += 1;
}
self.code_offset += bundle.size as u64;
self.bundles.push(bundle);
}
}
pub fn flush(&mut self) -> Vec<Bundle> {
self.finish_bundle();
let bundles = self.bundles.clone();
self.update_stats();
bundles
}
pub fn bundle_instructions(&mut self, instrs: &[BundleInstr]) -> Vec<Bundle> {
self.begin_bundle();
for instr in instrs {
match self.add_instruction(instr) {
BundleResult::Added => {
self.commit_instruction(instr.clone());
}
BundleResult::NewBundleNeeded => {
self.finish_bundle();
self.begin_bundle();
if self.add_instruction(instr) == BundleResult::Added {
self.commit_instruction(instr.clone());
} else {
let bundle = self.current_bundle.as_mut().unwrap();
bundle.instructions.push(instr.clone());
bundle.used_bytes = instr.size as u32;
if instr.classify().terminates_bundle(self.microarch) {
bundle.ends_with_branch = true;
}
self.finish_bundle();
}
}
BundleResult::FusionPairSplit => {
self.finish_bundle();
self.begin_bundle();
self.commit_instruction(instr.clone());
}
BundleResult::Illegal => {
}
}
}
self.flush()
}
fn update_stats(&mut self) {
if self.stats.total_bundles > 0 {
self.stats.avg_instrs_per_bundle =
self.stats.total_instructions as f64 / self.stats.total_bundles as f64;
let total_capacity = self.stats.total_bundles * self.bundle_size() as u64;
let total_used: u64 = self.bundles.iter().map(|b| b.used_bytes as u64).sum();
if total_capacity > 0 {
self.stats.avg_utilization = (total_used as f64 / total_capacity as f64) * 100.0;
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BundleResult {
Added,
NewBundleNeeded,
FusionPairSplit,
Illegal,
}
pub fn check_bundle_boundary(instr_offset: u64, instr_size: u8, bundle_size: u32) -> Option<u32> {
let bundle_end = ((instr_offset / bundle_size as u64) + 1) * bundle_size as u64;
let instr_end = instr_offset + instr_size as u64;
if instr_end > bundle_end {
Some((bundle_end - instr_offset) as u32)
} else {
None
}
}
pub fn compute_bundle_alignment(current_offset: u64, bundle_size: u32) -> u32 {
let misalignment = current_offset % bundle_size as u64;
if misalignment == 0 {
0
} else {
(bundle_size as u64 - misalignment) as u32
}
}
pub fn diagnose_boundary_crossing(
instr_offset: u64,
instr_size: u8,
bundle_size: u32,
) -> BoundaryCrossingInfo {
let bundle_end = ((instr_offset / bundle_size as u64) + 1) * bundle_size as u64;
let instr_end = instr_offset + instr_size as u64;
BoundaryCrossingInfo {
crosses_boundary: instr_end > bundle_end,
bundle_size,
offset: instr_offset,
instr_size,
bytes_in_current_bundle: if instr_end > bundle_end {
(bundle_end - instr_offset) as u8
} else {
instr_size
},
bytes_in_next_bundle: if instr_end > bundle_end {
(instr_end - bundle_end) as u8
} else {
0
},
next_bundle_offset: bundle_end,
}
}
#[derive(Debug, Clone)]
pub struct BoundaryCrossingInfo {
pub crosses_boundary: bool,
pub bundle_size: u32,
pub offset: u64,
pub instr_size: u8,
pub bytes_in_current_bundle: u8,
pub bytes_in_next_bundle: u8,
pub next_bundle_offset: u64,
}
impl BoundaryCrossingInfo {
pub fn needs_nop_sled(&self) -> bool {
self.crosses_boundary
}
pub fn nop_sled_size(&self) -> u32 {
if !self.crosses_boundary {
0
} else {
self.bytes_in_current_bundle as u32
}
}
}
#[derive(Debug, Clone)]
pub struct BundleLock {
pub locked: bool,
pub granularity: LockGranularity,
pub locked_instr_count: u8,
pub exception_pc: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockGranularity {
Bundle,
Instruction,
Pair,
None,
}
impl BundleLock {
pub fn bundle_lock() -> Self {
BundleLock {
locked: true,
granularity: LockGranularity::Bundle,
locked_instr_count: 0,
exception_pc: 0,
}
}
pub fn instruction_lock() -> Self {
BundleLock {
locked: true,
granularity: LockGranularity::Instruction,
locked_instr_count: 0,
exception_pc: 0,
}
}
pub fn unlocked() -> Self {
BundleLock {
locked: false,
granularity: LockGranularity::None,
locked_instr_count: 0,
exception_pc: 0,
}
}
}
pub fn lock_bundles(bundles: &mut [Bundle], microarch: BundleMicroArch) {
if !microarch.supports_bundle_locking() {
return;
}
for bundle in bundles.iter_mut() {
bundle.is_locked = true;
}
}
pub fn is_exception_recoverable(bundle: &Bundle, faulting_instr_index: usize) -> bool {
if !bundle.is_locked {
return false;
}
faulting_instr_index < bundle.instructions.len()
}
pub fn checkpoint_registers_for_bundle(is_64bit: bool) -> Vec<&'static str> {
let mut regs = vec!["RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI"];
if is_64bit {
regs.extend_from_slice(&["R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15"]);
}
regs.extend_from_slice(&[
"XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7",
]);
if is_64bit {
regs.extend_from_slice(&[
"XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15",
]);
}
regs.push("RFLAGS");
regs
}
pub mod atom_rules {
use super::*;
pub fn validate_atom_bundle(bundle: &Bundle) -> Result<(), String> {
if bundle.size != 8 {
return Err(format!("Atom bundle must be 8 bytes, got {}", bundle.size));
}
if bundle.instructions.len() > 2 {
return Err(format!(
"Atom bundle max 2 instructions, got {}",
bundle.instructions.len()
));
}
let mut byte_pos = 0u32;
for (i, instr) in bundle.instructions.iter().enumerate() {
if byte_pos + instr.size as u32 > 8 {
return Err(format!(
"Instruction {} (id={}) crosses 8-byte boundary at position {}",
i, instr.id, byte_pos
));
}
if instr.is_branch && i != bundle.instructions.len() - 1 {
return Err(format!(
"Branch instruction {} (id={}) is not the last in the bundle",
i, instr.id
));
}
byte_pos += instr.size as u32;
}
Ok(())
}
pub fn apply_atom_constraints(instrs: &[BundleInstr], is_64bit: bool) -> Vec<Bundle> {
let config = BundleConfig {
bundle_size_override: 8,
max_instrs_per_bundle: 2,
enforce_alignment: true,
pad_with_nops: true,
lock_bundles: false,
branch_at_bundle_end: true,
adaptive_bundling: false,
allow_macro_fusion_cross_bundle: false,
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Bonnell, is_64bit, config);
engine.bundle_instructions(instrs)
}
}
pub mod silvermont_rules {
use super::*;
pub fn validate_silvermont_bundle(bundle: &Bundle) -> Result<(), String> {
if bundle.size != 8 {
return Err(format!(
"Silvermont bundle must be 8 bytes, got {}",
bundle.size
));
}
if bundle.instructions.len() > 2 {
return Err(format!(
"Silvermont bundle max 2 instructions, got {}",
bundle.instructions.len()
));
}
let mut byte_pos = 0u32;
for (i, instr) in bundle.instructions.iter().enumerate() {
if instr.size > 11 {
return Err(format!(
"Instruction {} exceeds Silvermont max size of 11 bytes (got {})",
i, instr.size
));
}
if byte_pos + instr.size as u32 > 8 {
return Err(format!(
"Instruction {} crosses Silvermont 8-byte bundle boundary",
i
));
}
if instr.is_branch && i != bundle.instructions.len() - 1 {
return Err(format!("Branch must end Silvermont bundle"));
}
byte_pos += instr.size as u32;
}
Ok(())
}
pub fn apply_silvermont_constraints(instrs: &[BundleInstr], is_64bit: bool) -> Vec<Bundle> {
let config = BundleConfig {
bundle_size_override: 8,
max_instrs_per_bundle: 2,
enforce_alignment: true,
pad_with_nops: true,
lock_bundles: false,
branch_at_bundle_end: true,
adaptive_bundling: false,
allow_macro_fusion_cross_bundle: false,
};
let mut engine =
X86InstrBundling::with_config(BundleMicroArch::Silvermont, is_64bit, config);
engine.bundle_instructions(instrs)
}
}
pub mod goldmont_rules {
use super::*;
pub fn validate_goldmont_bundle(bundle: &Bundle) -> Result<(), String> {
if bundle.size != 16 {
return Err(format!(
"Goldmont bundle must be 16 bytes, got {}",
bundle.size
));
}
if bundle.instructions.len() > 3 {
return Err(format!(
"Goldmont bundle max 3 instructions, got {}",
bundle.instructions.len()
));
}
let mut byte_pos = 0u32;
for (i, instr) in bundle.instructions.iter().enumerate() {
if instr.is_nop {
continue; }
if byte_pos + instr.size as u32 > 16 {
return Err(format!(
"Instruction {} crosses Goldmont 16-byte bundle boundary",
i
));
}
byte_pos += instr.size as u32;
}
Ok(())
}
pub fn apply_goldmont_constraints(instrs: &[BundleInstr], is_64bit: bool) -> Vec<Bundle> {
let config = BundleConfig {
bundle_size_override: 16,
max_instrs_per_bundle: 3,
enforce_alignment: true,
pad_with_nops: true,
lock_bundles: false,
branch_at_bundle_end: false,
adaptive_bundling: false,
allow_macro_fusion_cross_bundle: true,
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Goldmont, is_64bit, config);
engine.bundle_instructions(instrs)
}
}
pub mod tremont_rules {
use super::*;
pub fn validate_tremont_bundle(bundle: &Bundle) -> Result<(), String> {
if bundle.size != 16 {
return Err(format!(
"Tremont bundle must be 16 bytes, got {}",
bundle.size
));
}
if bundle.instructions.len() > 3 {
return Err(format!(
"Tremont bundle max 3 instructions, got {}",
bundle.instructions.len()
));
}
let mut byte_pos = 0u32;
for (i, instr) in bundle.instructions.iter().enumerate() {
if instr.is_nop {
continue;
}
if byte_pos + instr.size as u32 > 16 {
return Err(format!("Instruction {} crosses Tremont bundle boundary", i));
}
if instr.is_indirect_branch && i != bundle.instructions.len() - 1 {
return Err(format!("Indirect branch must end Tremont bundle"));
}
byte_pos += instr.size as u32;
}
Ok(())
}
pub fn apply_tremont_constraints(instrs: &[BundleInstr], is_64bit: bool) -> Vec<Bundle> {
let config = BundleConfig {
bundle_size_override: 16,
max_instrs_per_bundle: 3,
enforce_alignment: true,
pad_with_nops: true,
lock_bundles: true,
branch_at_bundle_end: true,
adaptive_bundling: true,
allow_macro_fusion_cross_bundle: false,
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Tremont, is_64bit, config);
engine.bundle_instructions(instrs)
}
}
pub fn check_branch_at_bundle_end(
bundle: &Bundle,
microarch: BundleMicroArch,
) -> Result<(), String> {
if !microarch.requires_branch_at_bundle_end() {
return Ok(());
}
for (i, instr) in bundle.instructions.iter().enumerate() {
if instr.is_branch && i != bundle.instructions.len() - 1 {
return Err(format!(
"Branch at position {} in bundle {} is not the last instruction \
(required by {:?})",
i, bundle.id, microarch
));
}
}
Ok(())
}
pub fn enforce_branch_at_bundle_end(bundle: &mut Bundle, _microarch: BundleMicroArch) {
if bundle.instructions.is_empty() {
return;
}
let last_is_branch = bundle
.instructions
.last()
.map(|i| i.is_branch)
.unwrap_or(false);
if !last_is_branch {
let branch_pos = bundle.instructions.iter().position(|i| i.is_branch);
if let Some(pos) = branch_pos {
if pos < bundle.instructions.len() - 1 {
let _split_instrs = bundle.instructions.split_off(pos + 1);
bundle.ends_with_branch = true;
let used = bundle
.instructions
.iter()
.map(|i| i.size as u32)
.sum::<u32>();
if used < bundle.size {
let pad = bundle.size - used;
let nop = BundleInstr {
id: u64::MAX,
mnemonic: "NOP".to_string(),
size: pad as u8,
is_branch: false,
is_indirect_branch: false,
is_call: false,
is_return: false,
prefix_bytes: 0,
uop_count: 0,
is_macro_fused: false,
macro_fuse_partner: None,
is_nop: true,
original_offset: 0,
uses_lock_prefix: false,
is_memory: false,
bytes: vec![0x90; pad as usize],
};
bundle.instructions.push(nop);
bundle.used_bytes = bundle.size;
bundle.flags.has_padding = true;
}
}
}
}
}
pub fn optimize_bundle_packing(bundles: &mut [Bundle], microarch: BundleMicroArch) {
for bundle in bundles.iter_mut() {
if bundle.flags.is_full || bundle.instructions.len() <= 1 {
continue;
}
let mut instrs = bundle.instructions.clone();
let branch_idx = instrs.iter().position(|i| i.is_branch);
let has_branch = branch_idx.is_some();
let mut non_branch: Vec<_> = instrs
.iter()
.enumerate()
.filter(|(_, i)| !i.is_branch && !i.is_nop)
.collect();
non_branch.sort_by_key(|(_, i)| i.size);
let mut new_instrs = Vec::new();
let mut used = 0u32;
for (_orig_idx, instr) in &non_branch {
if used + instr.size as u32 <= bundle.size {
used += instr.size as u32;
new_instrs.push((*instr).clone());
}
}
if let Some(idx) = branch_idx {
if used + instrs[idx].size as u32 <= bundle.size {
new_instrs.push(instrs[idx].clone());
used += instrs[idx].size as u32;
}
}
if used < bundle.size {
let pad = bundle.size - used;
let nop = BundleInstr {
id: u64::MAX,
mnemonic: "NOP".to_string(),
size: pad as u8,
is_branch: false,
is_indirect_branch: false,
is_call: false,
is_return: false,
prefix_bytes: 0,
uop_count: 0,
is_macro_fused: false,
macro_fuse_partner: None,
is_nop: true,
original_offset: 0,
uses_lock_prefix: false,
is_memory: false,
bytes: vec![0x90; pad as usize],
};
new_instrs.push(nop);
}
bundle.instructions = new_instrs;
bundle.used_bytes = bundle.size;
}
}
pub fn bundle_utilization(bundle: &Bundle) -> f64 {
if bundle.size == 0 {
return 100.0;
}
(bundle.used_bytes as f64 / bundle.size as f64) * 100.0
}
pub fn find_least_utilized_bundle(bundles: &[Bundle]) -> Option<&Bundle> {
bundles.iter().filter(|b| b.size > 0).min_by(|a, b| {
a.used_bytes
.partial_cmp(&b.used_bytes)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
impl fmt::Display for Bundle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Bundle #{} @ 0x{:04X} ({}B, {} used, {} instrs",
self.id,
self.offset,
self.size,
self.used_bytes,
self.instructions.len()
)?;
if self.is_locked {
write!(f, ", LOCKED")?;
}
if self.ends_with_branch {
write!(f, ", ENDS_BR")?;
}
if self.flags.has_padding {
write!(f, ", PADDED")?;
}
if self.flags.loop_header {
write!(f, ", LOOP_HDR")?;
}
if self.flags.function_entry {
write!(f, ", FUNC_ENTRY")?;
}
write!(f, "):")?;
for instr in &self.instructions {
write!(
f,
"\n {:04X}: {} ({:?}, {}B)",
self.offset,
instr.mnemonic,
instr.classify(),
instr.size
)?;
}
Ok(())
}
}
impl X86InstrBundling {
pub fn dump_bundles(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"=== X86 Instruction Bundles (microarch: {:?}) ===\n",
self.microarch
));
output.push_str(&format!(
"Bundle size: {} bytes, max {} instrs/bundle\n",
self.bundle_size(),
self.max_instrs_per_bundle()
));
output.push_str(&format!("Total bundles: {}\n", self.bundles.len()));
output.push_str(&format!(
"Total instructions: {}\n\n",
self.stats.total_instructions
));
for bundle in &self.bundles {
output.push_str(&format!("{}\n", bundle));
}
output.push_str(&format!(
"\nAverage instructions/bundle: {:.2}\n",
self.stats.avg_instrs_per_bundle
));
output.push_str(&format!(
"Average utilization: {:.1}%\n",
self.stats.avg_utilization
));
output.push_str(&format!(
"NOP padding bytes: {}\n",
self.stats.nop_padding_bytes
));
output
}
}
pub fn make_atom_bundling() -> X86InstrBundling {
X86InstrBundling::new(BundleMicroArch::Bonnell, true)
}
pub fn make_silvermont_bundling() -> X86InstrBundling {
X86InstrBundling::new(BundleMicroArch::Silvermont, true)
}
pub fn make_goldmont_bundling() -> X86InstrBundling {
X86InstrBundling::new(BundleMicroArch::Goldmont, true)
}
pub fn make_tremont_bundling() -> X86InstrBundling {
X86InstrBundling::new(BundleMicroArch::Tremont, true)
}
pub fn make_test_instr(id: u64, mnemonic: &str, size: u8) -> BundleInstr {
BundleInstr {
id,
mnemonic: mnemonic.to_string(),
size,
is_branch: false,
is_indirect_branch: false,
is_call: false,
is_return: false,
prefix_bytes: 0,
uop_count: 1,
is_macro_fused: false,
macro_fuse_partner: None,
is_nop: false,
original_offset: 0,
uses_lock_prefix: false,
is_memory: false,
bytes: vec![0x90; size as usize],
}
}
pub fn make_test_branch(id: u64, mnemonic: &str, size: u8, is_indirect: bool) -> BundleInstr {
let mut instr = make_test_instr(id, mnemonic, size);
instr.mark_branch(is_indirect);
instr
}
pub fn make_test_call(id: u64, size: u8) -> BundleInstr {
let mut instr = make_test_instr(id, "CALL", size);
instr.mark_call();
instr
}
pub fn make_test_return(id: u64, size: u8) -> BundleInstr {
let mut instr = make_test_instr(id, "RET", size);
instr.mark_return();
instr
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bundle_creation() {
let mut engine = make_atom_bundling();
engine.begin_bundle();
assert!(engine.current_bundle.is_some());
let bundle = engine.current_bundle.as_ref().unwrap();
assert_eq!(bundle.size, 8);
}
#[test]
fn test_simple_bundling() {
let mut engine = make_atom_bundling();
let instrs = vec![
make_test_instr(1, "ADD", 2),
make_test_instr(2, "MOV", 3),
make_test_instr(3, "SUB", 2),
make_test_instr(4, "XOR", 2),
];
let bundles = engine.bundle_instructions(&instrs);
assert!(bundles.len() >= 2);
}
#[test]
fn test_branch_ends_bundle_atom() {
let mut engine = make_atom_bundling();
let instrs = vec![
make_test_instr(1, "CMP", 2),
make_test_branch(2, "JE", 2, false),
make_test_instr(3, "MOV", 3),
];
let bundles = engine.bundle_instructions(&instrs);
let branch_bundle = &bundles[0];
assert!(branch_bundle.ends_with_branch);
assert!(branch_bundle.instructions.len() <= 2);
}
#[test]
fn test_atom_bundle_validation() {
let mut bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2), make_test_instr(2, "MOV", 2)],
used_bytes: 4,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(atom_rules::validate_atom_bundle(&bundle).is_ok());
bundle.instructions.push(make_test_instr(3, "SUB", 2));
assert!(atom_rules::validate_atom_bundle(&bundle).is_err());
}
#[test]
fn test_silvermont_bundle_validation() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2), make_test_instr(2, "MOV", 2)],
used_bytes: 4,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(silvermont_rules::validate_silvermont_bundle(&bundle).is_ok());
}
#[test]
fn test_goldmont_bundle_validation() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 16,
instructions: vec![
make_test_instr(1, "ADD", 2),
make_test_instr(2, "MOV", 3),
make_test_instr(3, "SUB", 2),
],
used_bytes: 7,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
};
assert!(goldmont_rules::validate_goldmont_bundle(&bundle).is_ok());
let invalid_bundle = Bundle {
id: 1,
offset: 16,
size: 16,
instructions: vec![
make_test_instr(1, "ADD", 2),
make_test_instr(2, "MOV", 2),
make_test_instr(3, "SUB", 2),
make_test_instr(4, "XOR", 2),
],
used_bytes: 8,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
};
assert!(goldmont_rules::validate_goldmont_bundle(&invalid_bundle).is_err());
}
#[test]
fn test_tremont_bundle_validation() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 16,
instructions: vec![
make_test_instr(1, "ADD", 3),
make_test_instr(2, "MOV", 4),
make_test_instr(3, "SUB", 2),
],
used_bytes: 9,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
};
assert!(tremont_rules::validate_tremont_bundle(&bundle).is_ok());
}
#[test]
fn test_boundary_crossing_check() {
let crossing = diagnose_boundary_crossing(6, 4, 8);
assert!(crossing.crosses_boundary);
assert_eq!(crossing.bytes_in_current_bundle, 2);
assert_eq!(crossing.bytes_in_next_bundle, 2);
assert_eq!(crossing.next_bundle_offset, 8);
}
#[test]
fn test_no_boundary_crossing() {
let crossing = diagnose_boundary_crossing(4, 2, 8);
assert!(!crossing.crosses_boundary);
assert_eq!(crossing.bytes_in_current_bundle, 2);
assert_eq!(crossing.bytes_in_next_bundle, 0);
}
#[test]
fn test_boundary_crossing_nop_sled() {
let crossing = diagnose_boundary_crossing(7, 3, 8);
assert!(crossing.needs_nop_sled());
assert_eq!(crossing.nop_sled_size(), 1); }
#[test]
fn test_bundle_alignment_computation() {
assert_eq!(compute_bundle_alignment(0, 8), 0);
assert_eq!(compute_bundle_alignment(8, 8), 0);
assert_eq!(compute_bundle_alignment(3, 8), 5);
assert_eq!(compute_bundle_alignment(15, 16), 1);
}
#[test]
fn test_bundle_locking() {
let mut bundles = vec![Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2)],
used_bytes: 2,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
}];
lock_bundles(&mut bundles, BundleMicroArch::Silvermont);
assert!(bundles[0].is_locked);
}
#[test]
fn test_exception_recoverability() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2), make_test_instr(2, "MOV", 2)],
used_bytes: 4,
is_locked: true,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(is_exception_recoverable(&bundle, 0));
assert!(is_exception_recoverable(&bundle, 1));
assert!(!is_exception_recoverable(&bundle, 2));
let unlocked = Bundle {
is_locked: false,
..bundle.clone()
};
assert!(!is_exception_recoverable(&unlocked, 0));
}
#[test]
fn test_checkpoint_registers() {
let regs_64 = checkpoint_registers_for_bundle(true);
assert!(regs_64.contains(&"RAX"));
assert!(regs_64.contains(&"R15"));
assert!(regs_64.contains(&"XMM15"));
assert!(regs_64.contains(&"RFLAGS"));
let regs_32 = checkpoint_registers_for_bundle(false);
assert!(regs_32.contains(&"RAX"));
assert!(!regs_32.contains(&"R8"));
}
#[test]
fn test_bundle_flush() {
let mut engine = make_atom_bundling();
engine.begin_bundle();
engine.commit_instruction(make_test_instr(1, "ADD", 2));
engine.commit_instruction(make_test_instr(2, "MOV", 2));
let bundles = engine.flush();
assert_eq!(bundles.len(), 1);
assert_eq!(bundles[0].instructions.len(), 2);
}
#[test]
fn test_bundle_padding() {
let mut engine = make_atom_bundling();
engine.begin_bundle();
engine.commit_instruction(make_test_instr(1, "ADD", 2));
let bundles = engine.flush();
assert_eq!(bundles[0].used_bytes, 8); assert!(bundles[0].flags.has_padding);
}
#[test]
fn test_bundle_optimization() {
let mut bundles = vec![Bundle {
id: 0,
offset: 0,
size: 16,
instructions: vec![make_test_instr(1, "LONG", 8), make_test_instr(2, "SHRT", 2)],
used_bytes: 10,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
}];
optimize_bundle_packing(&mut bundles, BundleMicroArch::Goldmont);
assert_eq!(bundles[0].instructions[0].mnemonic, "SHRT");
}
#[test]
fn test_bundle_utilization() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 16,
instructions: vec![],
used_bytes: 8,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
};
assert_eq!(bundle_utilization(&bundle), 50.0);
}
#[test]
fn test_find_least_utilized() {
let bundles = vec![
Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![],
used_bytes: 2,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
},
Bundle {
id: 1,
offset: 8,
size: 8,
instructions: vec![],
used_bytes: 7,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
},
];
let least = find_least_utilized_bundle(&bundles);
assert!(least.is_some());
assert_eq!(least.unwrap().id, 0);
}
#[test]
fn test_instr_classification() {
let mut instr = make_test_instr(1, "ADD", 2);
assert_eq!(instr.classify(), InstrClass::Alu);
instr.mark_branch(false);
assert_eq!(instr.classify(), InstrClass::ConditionalBranch);
instr.mark_branch(true);
assert_eq!(instr.classify(), InstrClass::IndirectBranch);
instr.mark_call();
assert_eq!(instr.classify(), InstrClass::Call);
instr.mark_return();
assert_eq!(instr.classify(), InstrClass::Return);
}
#[test]
fn test_instr_class_terminates_bundle() {
assert!(InstrClass::IndirectBranch.terminates_bundle(BundleMicroArch::Bonnell));
assert!(InstrClass::Call.terminates_bundle(BundleMicroArch::Bonnell));
assert!(InstrClass::Return.terminates_bundle(BundleMicroArch::Bonnell));
assert!(!InstrClass::Alu.terminates_bundle(BundleMicroArch::Bonnell));
}
#[test]
fn test_instr_class_forces_new_bundle() {
assert!(InstrClass::Locked.forces_new_bundle(BundleMicroArch::Bonnell));
assert!(InstrClass::Complex.forces_new_bundle(BundleMicroArch::Bonnell));
assert!(!InstrClass::Alu.forces_new_bundle(BundleMicroArch::Bonnell));
}
#[test]
fn test_branch_at_bundle_end_enforcement() {
let mut bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![
make_test_instr(1, "ADD", 2),
make_test_branch(2, "JE", 2, false),
make_test_instr(3, "MOV", 2),
],
used_bytes: 6,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
enforce_branch_at_bundle_end(&mut bundle, BundleMicroArch::Bonnell);
let last = bundle.instructions.last().unwrap();
assert!(last.is_branch);
assert!(bundle.ends_with_branch);
}
#[test]
fn test_check_branch_at_bundle_end() {
let valid_bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![
make_test_instr(1, "ADD", 2),
make_test_branch(2, "JE", 2, false),
],
used_bytes: 4,
is_locked: false,
ends_with_branch: true,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(check_branch_at_bundle_end(&valid_bundle, BundleMicroArch::Bonnell).is_ok());
let invalid_bundle = Bundle {
id: 1,
offset: 8,
size: 8,
instructions: vec![
make_test_branch(1, "JE", 2, false),
make_test_instr(2, "MOV", 2),
],
used_bytes: 4,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(check_branch_at_bundle_end(&invalid_bundle, BundleMicroArch::Bonnell).is_err());
}
#[test]
fn test_bundle_display() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2)],
used_bytes: 2,
is_locked: true,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags {
function_entry: true,
..BundleFlags::default()
},
};
let display = format!("{}", bundle);
assert!(display.contains("Bundle #0"));
assert!(display.contains("ADD"));
assert!(display.contains("LOCKED"));
assert!(display.contains("FUNC_ENTRY"));
}
#[test]
fn test_dump_bundles() {
let mut engine = make_goldmont_bundling();
engine.bundle_instructions(&[make_test_instr(1, "ADD", 3), make_test_instr(2, "MOV", 4)]);
let dump = engine.dump_bundles();
assert!(dump.contains("Goldmont"));
assert!(dump.contains("ADD"));
assert!(dump.contains("MOV"));
}
#[test]
fn test_microarch_bundle_sizes() {
assert_eq!(BundleMicroArch::Bonnell.bundle_size(), 8);
assert_eq!(BundleMicroArch::Silvermont.bundle_size(), 8);
assert_eq!(BundleMicroArch::Goldmont.bundle_size(), 16);
assert_eq!(BundleMicroArch::Tremont.bundle_size(), 16);
assert_eq!(BundleMicroArch::OutOfOrder.bundle_size(), 16);
}
#[test]
fn test_microarch_decode_widths() {
assert_eq!(BundleMicroArch::Bonnell.decode_width(), 2);
assert_eq!(BundleMicroArch::Goldmont.decode_width(), 3);
assert_eq!(BundleMicroArch::Tremont.decode_width(), 6);
}
#[test]
fn test_microarch_branch_at_end_requirement() {
assert!(BundleMicroArch::Bonnell.requires_branch_at_bundle_end());
assert!(BundleMicroArch::Silvermont.requires_branch_at_bundle_end());
assert!(!BundleMicroArch::Goldmont.requires_branch_at_bundle_end());
assert!(!BundleMicroArch::OutOfOrder.requires_branch_at_bundle_end());
}
#[test]
fn test_microarch_bundle_locking_support() {
assert!(BundleMicroArch::Silvermont.supports_bundle_locking());
assert!(BundleMicroArch::Goldmont.supports_bundle_locking());
assert!(!BundleMicroArch::OutOfOrder.supports_bundle_locking());
}
#[test]
fn test_config_override_bundle_size() {
let config = BundleConfig {
bundle_size_override: 32,
..BundleConfig::default()
};
let engine = X86InstrBundling::with_config(BundleMicroArch::Bonnell, true, config);
assert_eq!(engine.bundle_size(), 32);
}
#[test]
fn test_config_max_instrs_override() {
let config = BundleConfig {
max_instrs_per_bundle: 4,
..BundleConfig::default()
};
let engine = X86InstrBundling::with_config(BundleMicroArch::Bonnell, true, config);
assert_eq!(engine.max_instrs_per_bundle(), 4);
}
#[test]
fn test_large_instruction_bundling() {
let mut engine = make_atom_bundling();
let results = engine.bundle_instructions(&[
make_test_instr(1, "VERYLONG", 8),
make_test_instr(2, "ADD", 2),
]);
assert!(results.len() >= 2);
assert_eq!(results[0].instructions[0].size, 8);
}
#[test]
fn test_atom_rules_apply() {
let instrs = vec![
make_test_instr(1, "ADD", 2),
make_test_instr(2, "MOV", 2),
make_test_instr(3, "SUB", 2),
];
let bundles = atom_rules::apply_atom_constraints(&instrs, true);
assert!(bundles.len() >= 2);
for b in &bundles {
let non_nop_count = b.instructions.iter().filter(|i| !i.is_nop).count();
assert!(non_nop_count <= 2);
}
}
#[test]
fn test_silvermont_rules_apply() {
let instrs = vec![make_test_instr(1, "ADD", 2), make_test_instr(2, "MOV", 2)];
let bundles = silvermont_rules::apply_silvermont_constraints(&instrs, true);
assert_eq!(bundles.len(), 1);
assert_eq!(bundles[0].size, 8);
}
#[test]
fn test_goldmont_rules_apply() {
let instrs = vec![
make_test_instr(1, "ADD", 3),
make_test_instr(2, "MOV", 4),
make_test_instr(3, "SUB", 2),
];
let bundles = goldmont_rules::apply_goldmont_constraints(&instrs, true);
assert!(!bundles.is_empty());
assert_eq!(bundles[0].size, 16);
}
#[test]
fn test_tremont_rules_apply() {
let instrs = vec![
make_test_instr(1, "ADD", 3),
make_test_instr(2, "MOV", 4),
make_test_instr(3, "SUB", 2),
];
let bundles = tremont_rules::apply_tremont_constraints(&instrs, true);
assert!(!bundles.is_empty());
assert_eq!(bundles[0].size, 16);
}
#[test]
fn test_bundle_result_equality() {
assert_eq!(BundleResult::Added, BundleResult::Added);
assert_ne!(BundleResult::Added, BundleResult::NewBundleNeeded);
}
#[test]
fn test_boundary_crossing_info_display() {
let info = diagnose_boundary_crossing(7, 3, 8);
assert!(info.crosses_boundary);
assert_eq!(info.bytes_in_current_bundle, 1);
assert_eq!(info.bytes_in_next_bundle, 2);
}
#[test]
fn test_empty_bundle() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![],
used_bytes: 0,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(atom_rules::validate_atom_bundle(&bundle).is_ok());
}
#[test]
fn test_lock_granularity() {
let lock = BundleLock::bundle_lock();
assert!(lock.locked);
assert_eq!(lock.granularity, LockGranularity::Bundle);
let ilock = BundleLock::instruction_lock();
assert_eq!(ilock.granularity, LockGranularity::Instruction);
let unlocked = BundleLock::unlocked();
assert!(!unlocked.locked);
}
#[test]
fn test_macro_fusion_cross_bundle() {
let config = BundleConfig {
allow_macro_fusion_cross_bundle: false,
..BundleConfig::default()
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Goldmont, true, config);
let mut fused1 = make_test_instr(1, "CMP", 3);
fused1.is_macro_fused = true;
fused1.macro_fuse_partner = Some(2);
let mut fused2 = make_test_instr(2, "JE", 2);
fused2.is_macro_fused = true;
fused2.macro_fuse_partner = Some(1);
engine.bundle_instructions(&[fused1, fused2]);
assert!(engine.bundles.len() >= 1);
}
#[test]
fn test_stats_after_bundling() {
let mut engine = make_atom_bundling();
engine.bundle_instructions(&[make_test_instr(1, "ADD", 2), make_test_instr(2, "MOV", 2)]);
assert!(engine.stats.total_bundles >= 1);
assert_eq!(engine.stats.total_instructions, 2);
}
#[test]
fn test_adaptive_bundling() {
let config = BundleConfig {
adaptive_bundling: true,
bundle_size_override: 0,
max_instrs_per_bundle: 0,
..BundleConfig::default()
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Tremont, true, config);
engine.bundle_instructions(&[
make_test_instr(1, "ADD", 3),
make_test_instr(2, "MOV", 4),
make_test_instr(3, "SUB", 2),
]);
for b in &engine.bundles {
assert!(b.instructions.len() <= 3);
}
}
#[test]
fn test_bundle_stats_accumulation() {
let mut engine = make_atom_bundling();
engine.bundle_instructions(&[
make_test_instr(1, "ADD", 2),
make_test_instr(2, "MOV", 2),
make_test_instr(3, "SUB", 2),
make_test_instr(4, "XOR", 2),
]);
assert_eq!(engine.stats.total_instructions, 4);
assert!(engine.stats.total_bundles >= 2);
}
#[test]
fn test_bundle_ends_with_call() {
let mut engine = X86InstrBundling::new(BundleMicroArch::Goldmont, true);
let call = make_test_call(1, 5);
engine.bundle_instructions(&[call]);
let bundle = &engine.bundles[0];
assert!(bundle.ends_with_branch);
}
#[test]
fn test_bundle_ends_with_return() {
let mut engine = X86InstrBundling::new(BundleMicroArch::Goldmont, true);
let ret = make_test_return(1, 1);
engine.bundle_instructions(&[ret]);
let bundle = &engine.bundles[0];
assert!(bundle.ends_with_branch);
}
#[test]
fn test_bundle_rejects_oversized_instr() {
let mut engine = make_atom_bundling();
let instrs = vec![make_test_instr(1, "BIG", 10)];
let bundles = engine.bundle_instructions(&instrs);
assert!(!bundles.is_empty());
}
#[test]
fn test_bundle_fusion_pair_handling() {
let config = BundleConfig {
allow_macro_fusion_cross_bundle: false,
..BundleConfig::default()
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Goldmont, true, config);
let mut c = make_test_instr(1, "CMP", 3);
c.is_macro_fused = true;
c.macro_fuse_partner = Some(2);
let mut j = make_test_branch(2, "JE", 2, false);
j.is_macro_fused = true;
j.macro_fuse_partner = Some(1);
engine.bundle_instructions(&[c, j]);
assert!(!engine.bundles.is_empty());
}
#[test]
fn test_boundary_crossing_exact_fit() {
let crossing = diagnose_boundary_crossing(0, 8, 8);
assert!(!crossing.crosses_boundary);
assert_eq!(crossing.bytes_in_current_bundle, 8);
assert_eq!(crossing.bytes_in_next_bundle, 0);
}
#[test]
fn test_boundary_crossing_just_over() {
let crossing = diagnose_boundary_crossing(5, 4, 8);
assert!(crossing.crosses_boundary);
assert_eq!(crossing.bytes_in_current_bundle, 3);
assert_eq!(crossing.bytes_in_next_bundle, 1);
}
#[test]
fn test_bundle_alignment_large_offset() {
assert_eq!(compute_bundle_alignment(100, 16), 12);
assert_eq!(compute_bundle_alignment(112, 16), 0);
}
#[test]
fn test_lock_bundles_silvermont() {
let mut bundles = vec![
Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "ADD", 2)],
used_bytes: 2,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
},
Bundle {
id: 1,
offset: 8,
size: 8,
instructions: vec![make_test_instr(2, "MOV", 2)],
used_bytes: 2,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
},
];
lock_bundles(&mut bundles, BundleMicroArch::Silvermont);
assert!(bundles.iter().all(|b| b.is_locked));
}
#[test]
fn test_checkpoint_registers_count() {
let regs_64 = checkpoint_registers_for_bundle(true);
assert_eq!(regs_64.len(), 33);
let regs_32 = checkpoint_registers_for_bundle(false);
assert_eq!(regs_32.len(), 17);
}
#[test]
fn test_instr_class_discriminants() {
let classes = [
InstrClass::Alu,
InstrClass::Memory,
InstrClass::ConditionalBranch,
InstrClass::UnconditionalBranch,
InstrClass::IndirectBranch,
InstrClass::Call,
InstrClass::Return,
InstrClass::Locked,
InstrClass::Nop,
InstrClass::Complex,
];
for (i, a) in classes.iter().enumerate() {
for (j, b) in classes.iter().enumerate() {
if i == j {
assert_eq!(a, b);
} else {
assert_ne!(a, b);
}
}
}
}
#[test]
fn test_microarch_max_prefix_bytes() {
assert_eq!(BundleMicroArch::Bonnell.max_prefix_bytes(), 2);
assert_eq!(BundleMicroArch::Silvermont.max_prefix_bytes(), 3);
assert_eq!(BundleMicroArch::Goldmont.max_prefix_bytes(), 4);
}
#[test]
fn test_optimize_bundle_packing_idempotent() {
let mut bundles = vec![Bundle {
id: 0,
offset: 0,
size: 16,
instructions: vec![make_test_instr(1, "B", 2), make_test_instr(2, "A", 10)],
used_bytes: 12,
is_locked: false,
ends_with_branch: false,
alignment: 16,
flags: BundleFlags::default(),
}];
optimize_bundle_packing(&mut bundles, BundleMicroArch::Goldmont);
assert!(goldmont_rules::validate_goldmont_bundle(&bundles[0]).is_ok());
}
#[test]
fn test_find_least_utilized_empty_list() {
let bundles: Vec<Bundle> = vec![];
assert!(find_least_utilized_bundle(&bundles).is_none());
}
#[test]
fn test_bundle_utilization_full() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![],
used_bytes: 8,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert_eq!(bundle_utilization(&bundle), 100.0);
}
#[test]
fn test_bundle_utilization_empty() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![],
used_bytes: 0,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert_eq!(bundle_utilization(&bundle), 0.0);
}
#[test]
fn test_bundle_utilization_zero_size() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 0,
instructions: vec![],
used_bytes: 0,
is_locked: false,
ends_with_branch: false,
alignment: 1,
flags: BundleFlags::default(),
};
assert_eq!(bundle_utilization(&bundle), 100.0);
}
#[test]
fn test_enforce_branch_at_bundle_end_empty() {
let mut bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![],
used_bytes: 0,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
enforce_branch_at_bundle_end(&mut bundle, BundleMicroArch::Bonnell);
assert!(bundle.instructions.is_empty());
}
#[test]
fn test_enforce_branch_at_bundle_end_branch_already_last() {
let mut bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![
make_test_instr(1, "ADD", 2),
make_test_branch(2, "JE", 2, false),
],
used_bytes: 4,
is_locked: false,
ends_with_branch: true,
alignment: 8,
flags: BundleFlags::default(),
};
enforce_branch_at_bundle_end(&mut bundle, BundleMicroArch::Bonnell);
assert!(bundle.instructions.last().unwrap().is_branch);
}
#[test]
fn test_dump_bundles_output_format() {
let mut engine = make_atom_bundling();
engine.bundle_instructions(&[make_test_instr(1, "PUSH", 1), make_test_instr(2, "MOV", 3)]);
let dump = engine.dump_bundles();
assert!(dump.contains("Bonnell"));
assert!(dump.contains("Bundle size:"));
assert!(dump.contains("Total bundles:"));
assert!(dump.contains("Average"));
}
#[test]
fn test_make_test_instr_defaults() {
let instr = make_test_instr(42, "TEST", 4);
assert_eq!(instr.id, 42);
assert_eq!(instr.mnemonic, "TEST");
assert_eq!(instr.size, 4);
assert!(!instr.is_branch);
assert!(!instr.is_call);
assert!(!instr.is_return);
assert!(!instr.is_nop);
assert_eq!(instr.classify(), InstrClass::Alu);
}
#[test]
fn test_config_default_values() {
let config = BundleConfig::default();
assert_eq!(config.bundle_size_override, 0);
assert_eq!(config.max_instrs_per_bundle, 0);
assert!(config.enforce_alignment);
assert!(config.pad_with_nops);
assert!(!config.lock_bundles);
assert!(!config.branch_at_bundle_end);
assert!(!config.adaptive_bundling);
assert!(!config.allow_macro_fusion_cross_bundle);
}
#[test]
fn test_engine_stats_updated_after_flush() {
let mut engine = make_goldmont_bundling();
engine.bundle_instructions(&[make_test_instr(1, "ADD", 3), make_test_instr(2, "MOV", 4)]);
engine.flush();
assert!(engine.stats.total_bundles > 0);
assert!(engine.stats.avg_instrs_per_bundle > 0.0);
assert!(engine.stats.avg_utilization > 0.0);
}
#[test]
fn test_locked_bundle_count() {
let config = BundleConfig {
lock_bundles: true,
..BundleConfig::default()
};
let mut engine = X86InstrBundling::with_config(BundleMicroArch::Tremont, true, config);
engine.bundle_instructions(&[make_test_instr(1, "ADD", 3), make_test_instr(2, "MOV", 4)]);
assert!(engine.stats.locked_bundles > 0);
}
#[test]
fn test_bundle_instruction_ordering() {
let mut engine = make_atom_bundling();
engine.bundle_instructions(&[
make_test_instr(1, "FIRST", 2),
make_test_instr(2, "SECOND", 2),
]);
let bundle = &engine.bundles[0];
assert_eq!(bundle.instructions[0].mnemonic, "FIRST");
assert_eq!(bundle.instructions[1].mnemonic, "SECOND");
}
#[test]
fn test_all_microarch_bundle_sizes_consistent() {
let microarchs = [
BundleMicroArch::Bonnell,
BundleMicroArch::Saltwell,
BundleMicroArch::Silvermont,
BundleMicroArch::Airmont,
BundleMicroArch::Goldmont,
BundleMicroArch::GoldmontPlus,
BundleMicroArch::Tremont,
BundleMicroArch::Gracemont,
BundleMicroArch::Crestmont,
BundleMicroArch::Skymont,
BundleMicroArch::Darkmont,
BundleMicroArch::OutOfOrder,
];
for ma in µarchs {
let size = ma.bundle_size();
assert!(
size == 8 || size == 16,
"{:?} has unexpected bundle size {}",
ma,
size
);
}
}
#[test]
fn test_lock_granularity_variants() {
assert_eq!(LockGranularity::Bundle, LockGranularity::Bundle);
assert_ne!(LockGranularity::Bundle, LockGranularity::None);
assert_ne!(LockGranularity::Instruction, LockGranularity::None);
assert_ne!(LockGranularity::Pair, LockGranularity::None);
}
#[test]
fn test_bundle_flags_default() {
let flags = BundleFlags::default();
assert!(!flags.function_entry);
assert!(!flags.loop_header);
assert!(!flags.branch_target);
assert!(!flags.has_padding);
assert!(!flags.fetch_aligned);
assert!(!flags.is_full);
}
#[test]
fn test_bundle_display_includes_flags() {
let mut bundle = Bundle {
id: 5,
offset: 0x40,
size: 8,
instructions: vec![make_test_branch(1, "JMP", 2, true)],
used_bytes: 2,
is_locked: true,
ends_with_branch: true,
alignment: 8,
flags: BundleFlags {
loop_header: true,
branch_target: true,
has_padding: true,
..BundleFlags::default()
},
};
let display = format!("{}", bundle);
assert!(display.contains("Bundle #5"));
assert!(display.contains("0x0040"));
assert!(display.contains("LOCKED"));
assert!(display.contains("ENDS_BR"));
assert!(display.contains("PADDED"));
assert!(display.contains("LOOP_HDR"));
}
#[test]
fn test_instr_class_terminates_bundle_all_microarchs() {
for ma in &[
BundleMicroArch::Bonnell,
BundleMicroArch::Silvermont,
BundleMicroArch::Goldmont,
BundleMicroArch::Tremont,
BundleMicroArch::OutOfOrder,
] {
assert!(InstrClass::Return.terminates_bundle(*ma));
assert!(InstrClass::IndirectBranch.terminates_bundle(*ma));
assert!(InstrClass::Call.terminates_bundle(*ma));
}
}
#[test]
fn test_try_add_to_full_bundle() {
let mut engine = make_atom_bundling();
engine.begin_bundle();
engine.commit_instruction(make_test_instr(1, "BIG", 6));
let result = engine.try_add_instruction(&make_test_instr(2, "ADD", 3));
assert_eq!(result, BundleResult::NewBundleNeeded);
}
#[test]
fn test_begin_bundle_alignment() {
let mut engine = make_atom_bundling();
engine.code_offset = 5;
engine.begin_bundle();
assert_eq!(engine.code_offset, 8);
}
#[test]
fn test_silvermont_allows_larger_instructions() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "LONG", 8)],
used_bytes: 8,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(silvermont_rules::validate_silvermont_bundle(&bundle).is_ok());
}
#[test]
fn test_silvermont_rejects_oversize() {
let bundle = Bundle {
id: 0,
offset: 0,
size: 8,
instructions: vec![make_test_instr(1, "TOOLONG", 12)],
used_bytes: 12,
is_locked: false,
ends_with_branch: false,
alignment: 8,
flags: BundleFlags::default(),
};
assert!(silvermont_rules::validate_silvermont_bundle(&bundle).is_err());
}
#[test]
fn test_boundary_crossing_info_nop_sled_zero_when_no_crossing() {
let info = diagnose_boundary_crossing(0, 4, 8);
assert!(!info.needs_nop_sled());
assert_eq!(info.nop_sled_size(), 0);
}
#[test]
fn test_bundle_microarch_comparison() {
assert_ne!(
BundleMicroArch::Bonnell.bundle_size(),
BundleMicroArch::Goldmont.bundle_size()
);
assert_ne!(
BundleMicroArch::Silvermont.decode_width(),
BundleMicroArch::Tremont.decode_width()
);
}
}