use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
pub const MIN_CASES_FOR_JUMP_TABLE: usize = 4;
pub const MIN_DENSITY_FOR_JUMP_TABLE: f64 = 0.40;
pub const MAX_CASES_FOR_BIT_TEST: usize = 4;
pub const MAX_BIT_TEST_VALUE: u64 = 63;
pub const JUMP_TABLE_ENTRY_SIZE: u32 = 4;
pub const JUMP_TABLE_ALIGNMENT: u32 = 16;
pub const JUMP_TABLE_ENTRY_ALIGNMENT: u32 = 4;
pub const MAX_JUMP_TABLE_RANGE: u64 = 8192;
pub const MIN_CLUSTER_DENSITY_IMPROVEMENT: f64 = 0.10;
pub const JMP_REG_ENCODING_SIZE: u32 = 2;
pub const JMP_MEM_INDEX_ENCODING_SIZE: u32 = 7;
pub const LEA_RIP_ENCODING_SIZE: u32 = 7;
pub const MAX_CLUSTERS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CaseValue {
pub value: i64,
pub target: u32,
}
impl CaseValue {
pub fn new(value: i64, target: u32) -> Self {
CaseValue { value, target }
}
}
#[derive(Debug, Clone)]
pub struct CaseCluster {
pub start: i64,
pub end: i64,
pub target: u32,
pub count: u32,
pub is_default: bool,
}
impl CaseCluster {
pub fn new(start: i64, end: i64, target: u32) -> Self {
let count = if end >= start {
(end - start + 1) as u32
} else {
0
};
CaseCluster {
start,
end,
target,
count,
is_default: false,
}
}
pub fn span(&self) -> u32 {
self.count
}
pub fn range_size(&self) -> u32 {
if self.end >= self.start {
(self.end - self.start + 1) as u32
} else {
0
}
}
pub fn density(&self) -> f64 {
if self.range_size() == 0 {
return 0.0;
}
self.count as f64 / self.range_size() as f64
}
pub fn overlaps(&self, other: &CaseCluster) -> bool {
self.start <= other.end && other.start <= self.end
}
pub fn merge(&self, other: &CaseCluster) -> CaseCluster {
let start = self.start.min(other.start);
let end = self.end.max(other.end);
let target = if self.is_default {
other.target
} else {
self.target
};
CaseCluster {
start,
end,
target,
count: self.count + other.count,
is_default: self.is_default || other.is_default,
}
}
pub fn contains(&self, case: &CaseValue) -> bool {
case.value >= self.start && case.value <= self.end
}
}
#[derive(Debug, Clone)]
pub struct JumpTableEntry {
pub index: u32,
pub case_value: i64,
pub target_label: String,
pub is_default: bool,
pub is_fallthrough: bool,
}
impl JumpTableEntry {
pub fn new(index: u32, case_value: i64, target_label: String) -> Self {
JumpTableEntry {
index,
case_value,
target_label,
is_default: false,
is_fallthrough: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LoweringStrategy {
JumpTable,
BinaryTree,
BitTest,
LinearChain,
Trivial,
}
impl LoweringStrategy {
pub fn name(&self) -> &'static str {
match self {
LoweringStrategy::JumpTable => "jump_table",
LoweringStrategy::BinaryTree => "binary_tree",
LoweringStrategy::BitTest => "bit_test",
LoweringStrategy::LinearChain => "linear_chain",
LoweringStrategy::Trivial => "trivial",
}
}
pub fn uses_jump_table(&self) -> bool {
matches!(self, LoweringStrategy::JumpTable)
}
pub fn uses_constant_pool(&self) -> bool {
matches!(self, LoweringStrategy::JumpTable)
}
}
#[derive(Debug, Clone)]
pub struct DensityAnalysis {
pub min_value: i64,
pub max_value: i64,
pub range: u64,
pub case_count: u32,
pub default_target: u32,
pub density: f64,
pub table_size_bytes: u32,
pub tree_depth: u32,
pub bit_test_possible: bool,
pub recommended_strategy: LoweringStrategy,
pub jump_table_code_size: u32,
pub binary_tree_code_size: u32,
pub linear_chain_code_size: u32,
pub has_duplicates: bool,
pub duplicates_collapsed: u32,
}
impl DensityAnalysis {
pub fn new() -> Self {
DensityAnalysis {
min_value: 0,
max_value: 0,
range: 0,
case_count: 0,
default_target: 0,
density: 0.0,
table_size_bytes: 0,
tree_depth: 0,
bit_test_possible: false,
recommended_strategy: LoweringStrategy::Trivial,
jump_table_code_size: 0,
binary_tree_code_size: 0,
linear_chain_code_size: 0,
has_duplicates: false,
duplicates_collapsed: 0,
}
}
pub fn analyze(
cases: &[CaseValue],
default_target: u32,
min_cases_for_jump_table: usize,
min_density: f64,
) -> Self {
let mut analysis = DensityAnalysis::new();
analysis.default_target = default_target;
if cases.is_empty() {
analysis.recommended_strategy = LoweringStrategy::Trivial;
return analysis;
}
let mut sorted = cases.to_vec();
sorted.sort_by_key(|c| c.value);
let mut seen: HashSet<i64> = HashSet::new();
let mut deduped: Vec<CaseValue> = Vec::new();
for c in &sorted {
if seen.contains(&c.value) {
analysis.has_duplicates = true;
analysis.duplicates_collapsed += 1;
if let Some(last) = deduped.iter_mut().rev().find(|x| x.value == c.value) {
last.target = c.target;
}
} else {
seen.insert(c.value);
deduped.push(*c);
}
}
analysis.case_count = deduped.len() as u32;
analysis.min_value = deduped.first().map(|c| c.value).unwrap_or(0);
analysis.max_value = deduped.last().map(|c| c.value).unwrap_or(0);
if analysis.max_value >= analysis.min_value {
analysis.range = (analysis.max_value - analysis.min_value) as u64;
} else {
analysis.range = 0;
}
if analysis.range > 0 {
analysis.density = analysis.case_count as f64 / (analysis.range + 1) as f64;
} else if analysis.case_count > 0 {
analysis.density = 1.0;
}
analysis.table_size_bytes = (analysis.range as u32 + 1) * JUMP_TABLE_ENTRY_SIZE;
analysis.tree_depth = if analysis.case_count > 0 {
(analysis.case_count as f64).log2().ceil() as u32
} else {
0
};
analysis.jump_table_code_size = 15 + analysis.table_size_bytes;
analysis.binary_tree_code_size = analysis.tree_depth * 6;
analysis.linear_chain_code_size = analysis.case_count * 5;
analysis.bit_test_possible = analysis.case_count <= MAX_CASES_FOR_BIT_TEST as u32
&& analysis.max_value as u64 <= MAX_BIT_TEST_VALUE
&& analysis.case_count > 0;
analysis.recommended_strategy =
analysis.select_strategy(min_cases_for_jump_table, min_density);
analysis
}
pub fn select_strategy(&self, min_cases: usize, min_density: f64) -> LoweringStrategy {
if self.case_count == 0 {
return LoweringStrategy::Trivial;
}
let jump_table_viable = self.case_count as usize >= min_cases
&& self.density >= min_density
&& self.range <= MAX_JUMP_TABLE_RANGE;
if jump_table_viable {
if self.table_size_bytes < 4096 {
return LoweringStrategy::JumpTable;
}
if self.jump_table_code_size < self.binary_tree_code_size {
return LoweringStrategy::JumpTable;
}
}
if self.bit_test_possible && self.case_count <= MAX_CASES_FOR_BIT_TEST as u32 {
return LoweringStrategy::BitTest;
}
if self.case_count <= 3 {
return LoweringStrategy::LinearChain;
}
if self.binary_tree_code_size < self.linear_chain_code_size {
LoweringStrategy::BinaryTree
} else {
LoweringStrategy::LinearChain
}
}
}
impl Default for DensityAnalysis {
fn default() -> Self {
DensityAnalysis::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JumpTableReloc {
PC32,
PC8,
Abs64,
Abs32,
}
impl JumpTableReloc {
pub fn size(&self) -> u32 {
match self {
JumpTableReloc::PC32 => 4,
JumpTableReloc::PC8 => 1,
JumpTableReloc::Abs64 => 8,
JumpTableReloc::Abs32 => 4,
}
}
pub fn is_pc_relative(&self) -> bool {
matches!(self, JumpTableReloc::PC32 | JumpTableReloc::PC8)
}
pub fn elf_name(&self) -> &'static str {
match self {
JumpTableReloc::PC32 => "R_X86_64_PC32",
JumpTableReloc::PC8 => "R_X86_64_PC8",
JumpTableReloc::Abs64 => "R_X86_64_64",
JumpTableReloc::Abs32 => "R_X86_64_32",
}
}
}
#[derive(Debug, Clone)]
pub struct X86JumpTable {
pub cases: Vec<CaseValue>,
pub default_target: u32,
pub entries: Vec<JumpTableEntry>,
pub density_analysis: DensityAnalysis,
pub strategy: LoweringStrategy,
pub clusters: Vec<CaseCluster>,
pub table_offset: u32,
pub table_size_bytes: u32,
pub alignment: u32,
pub label: String,
pub is_pic: bool,
pub reloc_type: JumpTableReloc,
pub asm_label: String,
pub in_constant_pool: bool,
pub fallthrough_count: u32,
pub stats: JumpTableStats,
}
#[derive(Debug, Clone, Default)]
pub struct JumpTableStats {
pub total_cases: u32,
pub clustered_cases: u32,
pub num_clusters: u32,
pub table_entries: u32,
pub table_bytes: u32,
pub cycles_saved_est: u32,
pub used_strategy_name: String,
}
impl X86JumpTable {
pub fn new(default_target: u32, is_pic: bool) -> Self {
X86JumpTable {
cases: Vec::new(),
default_target,
entries: Vec::new(),
density_analysis: DensityAnalysis::new(),
strategy: LoweringStrategy::Trivial,
clusters: Vec::new(),
table_offset: 0,
table_size_bytes: 0,
alignment: JUMP_TABLE_ALIGNMENT,
label: String::new(),
is_pic,
reloc_type: if is_pic {
JumpTableReloc::PC32
} else {
JumpTableReloc::Abs32
},
asm_label: String::new(),
in_constant_pool: false,
fallthrough_count: 0,
stats: JumpTableStats::default(),
}
}
pub fn add_case(&mut self, value: i64, target: u32) {
self.cases.push(CaseValue::new(value, target));
}
pub fn add_cases(&mut self, cases: &[(i64, u32)]) {
for &(value, target) in cases {
self.add_case(value, target);
}
}
pub fn build(&mut self) {
self.density_analysis = DensityAnalysis::analyze(
&self.cases,
self.default_target,
MIN_CASES_FOR_JUMP_TABLE,
MIN_DENSITY_FOR_JUMP_TABLE,
);
self.strategy = self.density_analysis.recommended_strategy;
if self.strategy == LoweringStrategy::JumpTable {
self.form_clusters();
self.generate_entries();
self.compute_layout();
}
self.stats.total_cases = self.cases.len() as u32;
self.stats.clustered_cases = self.clusters.iter().map(|c| c.count).sum();
self.stats.num_clusters = self.clusters.len() as u32;
self.stats.table_entries = self.entries.len() as u32;
self.stats.table_bytes = self.table_size_bytes;
self.stats.used_strategy_name = self.strategy.name().to_string();
let linear_cycles = self.stats.total_cases as i64 * 2;
match self.strategy {
LoweringStrategy::JumpTable => {
let jt_cycles = 4 + self.stats.table_entries as i64;
let saved = linear_cycles - jt_cycles;
self.stats.cycles_saved_est = saved.max(0) as u32;
}
LoweringStrategy::BinaryTree => {
let bt_cycles = self.density_analysis.tree_depth as i64 * 3;
let saved = linear_cycles - bt_cycles;
self.stats.cycles_saved_est = saved.max(0) as u32;
}
_ => {
self.stats.cycles_saved_est = 0;
}
}
}
fn form_clusters(&mut self) {
if self.cases.is_empty() {
return;
}
let mut sorted = self.cases.clone();
sorted.sort_by_key(|c| c.value);
let mut clusters: Vec<CaseCluster> = Vec::new();
let mut current: Option<CaseCluster> = None;
for case in &sorted {
match &mut current {
None => {
current = Some(CaseCluster::new(case.value, case.value, case.target));
}
Some(cluster) => {
if case.value == cluster.end + 1 && case.target == cluster.target {
cluster.end = case.value;
cluster.count += 1;
} else {
clusters.push(cluster.clone());
current = Some(CaseCluster::new(case.value, case.value, case.target));
}
}
}
}
if let Some(cluster) = current {
clusters.push(cluster);
}
if !clusters.is_empty() {
let min_val = clusters.first().map(|c| c.start).unwrap();
let max_val = clusters.last().map(|c| c.end).unwrap();
if min_val > self.density_analysis.min_value {
}
}
self.clusters = clusters;
}
fn generate_entries(&mut self) {
self.entries.clear();
let min_val = self.density_analysis.min_value;
let range = self.density_analysis.range;
let mut target_map: BTreeMap<i64, u32> = BTreeMap::new();
for case in &self.cases {
target_map.insert(case.value, case.target);
}
for i in 0..=range {
let value = min_val + i as i64;
let target = target_map
.get(&value)
.copied()
.unwrap_or(self.default_target);
let is_default = target == self.default_target;
let target_label = format!(".LBB0_{}", target);
let entry = JumpTableEntry {
index: i as u32,
case_value: value,
target_label,
is_default,
is_fallthrough: false,
};
self.entries.push(entry);
}
self.optimize_fallthrough();
}
fn optimize_fallthrough(&mut self) {
let mut i = 0;
self.fallthrough_count = 0;
while i < self.entries.len() {
let entry = &self.entries[i];
if entry.is_default && i > 0 {
if self.entries[i - 1].target_label == entry.target_label {
self.fallthrough_count += 1;
}
}
i += 1;
}
}
fn compute_layout(&mut self) {
self.table_size_bytes = self.entries.len() as u32 * JUMP_TABLE_ENTRY_SIZE;
self.alignment = JUMP_TABLE_ALIGNMENT;
self.table_offset = 0; }
pub fn generate_label(&mut self, switch_id: u32) {
self.label = format!(".LJTI{}_{}", switch_id, self.default_target);
self.asm_label = format!("LJTI{}_{}", switch_id, self.default_target);
}
pub fn place_in_constant_pool(&mut self, pool_offset: u32) {
self.table_offset = pool_offset;
self.in_constant_pool = true;
}
pub fn get_entry(&self, index: u32) -> Option<&JumpTableEntry> {
self.entries.get(index as usize)
}
pub fn get_entry_offset(&self, index: u32) -> Option<u32> {
if (index as usize) < self.entries.len() {
Some(index * JUMP_TABLE_ENTRY_SIZE)
} else {
None
}
}
pub fn get_cluster_entries(&self, cluster: &CaseCluster) -> Vec<&JumpTableEntry> {
let min_val = self.density_analysis.min_value;
let start_idx = (cluster.start - min_val).max(0) as u32;
let end_idx = (cluster.end - min_val).max(0) as u32;
self.entries
.iter()
.filter(|e| e.index >= start_idx && e.index <= end_idx)
.collect()
}
pub fn unique_target_count(&self) -> usize {
let mut targets: HashSet<&str> = HashSet::new();
for entry in &self.entries {
targets.insert(entry.target_label.as_str());
}
targets.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn is_pic(&self) -> bool {
self.is_pic
}
pub fn emit_dispatch_sequence(&self, cond_reg: &str, temp_reg: &str) -> Vec<String> {
let mut seq = Vec::new();
match self.strategy {
LoweringStrategy::JumpTable => {
let min_val = self.density_analysis.min_value;
let range = self.density_analysis.range;
seq.push(format!(" CMP {}, {}", cond_reg, min_val));
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!(" JA {}", default_label));
if self.is_pic {
seq.push(format!(" LEA {}, [RIP + {}]", temp_reg, self.asm_label));
seq.push(format!(
" MOVSXD {}, [{} + {}*{}]",
temp_reg, temp_reg, cond_reg, JUMP_TABLE_ENTRY_SIZE
));
if range > 0 {
seq.push(format!(" ADD {}, {}", temp_reg, cond_reg));
}
} else {
seq.push(format!(" LEA {}, [{}]", temp_reg, self.asm_label));
seq.push(format!(
" JMP [{} + {}*{}]",
temp_reg, cond_reg, JUMP_TABLE_ENTRY_SIZE
));
}
}
LoweringStrategy::BinaryTree => {
seq.push(format!(
" ; binary tree dispatch for {} cases",
self.stats.total_cases
));
self.emit_binary_tree(&mut seq, &self.cases, cond_reg, 0);
}
LoweringStrategy::BitTest => {
let mask: u64 = self
.cases
.iter()
.map(|c| 1u64 << (c.value as u32))
.fold(0, |a, b| a | b);
seq.push(format!(" MOV {}, {}", temp_reg, mask));
seq.push(format!(" BT {}, {}", temp_reg, cond_reg));
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!(" JNC {}", default_label));
seq.push(format!(" TZCNT {}, {}", temp_reg, cond_reg));
}
LoweringStrategy::LinearChain => {
for case in &self.cases {
let label = format!(".LBB0_{}", case.target);
seq.push(format!(" CMP {}, {}", cond_reg, case.value));
seq.push(format!(" JE {}", label));
}
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!(" JMP {}", default_label));
}
LoweringStrategy::Trivial => {
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!(" JMP {}", default_label));
}
}
seq
}
fn emit_binary_tree(
&self,
seq: &mut Vec<String>,
cases: &[CaseValue],
cond_reg: &str,
depth: usize,
) {
if cases.is_empty() {
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!("{}JMP {}", " ".repeat(depth), default_label));
return;
}
if cases.len() == 1 {
let label = format!(".LBB0_{}", cases[0].target);
seq.push(format!(
"{}CMP {}, {}",
" ".repeat(depth),
cond_reg,
cases[0].value
));
seq.push(format!("{}JE {}", " ".repeat(depth), label));
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!("{}JMP {}", " ".repeat(depth), default_label));
return;
}
if cases.len() == 2 {
let label0 = format!(".LBB0_{}", cases[0].target);
let label1 = format!(".LBB0_{}", cases[1].target);
seq.push(format!(
"{}CMP {}, {}",
" ".repeat(depth),
cond_reg,
cases[0].value
));
seq.push(format!("{}JE {}", " ".repeat(depth), label0));
seq.push(format!(
"{}CMP {}, {}",
" ".repeat(depth),
cond_reg,
cases[1].value
));
seq.push(format!("{}JE {}", " ".repeat(depth), label1));
let default_label = format!(".LBB0_{}", self.default_target);
seq.push(format!("{}JMP {}", " ".repeat(depth), default_label));
return;
}
let mid = cases.len() / 2;
let pivot = cases[mid].value;
let label_ge = format!(".LBBT_{}_GE", depth);
seq.push(format!(
"{}CMP {}, {}",
" ".repeat(depth),
cond_reg,
pivot
));
seq.push(format!("{}JGE {}", " ".repeat(depth), label_ge));
self.emit_binary_tree(seq, &cases[..mid], cond_reg, depth + 1);
seq.push(format!("{}{}:", " ".repeat(depth), label_ge));
self.emit_binary_tree(seq, &cases[mid..], cond_reg, depth + 1);
}
pub fn emit_table_data(&self) -> Vec<String> {
let mut data = Vec::new();
data.push(format!(" .balign {}", JUMP_TABLE_ALIGNMENT));
data.push(format!("{}:", self.asm_label));
for entry in &self.entries {
if self.is_pic {
data.push(format!(" .long {}-.", entry.target_label));
} else {
data.push(format!(" .quad {}", entry.target_label));
}
}
data
}
pub fn to_constant_pool_data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(self.table_size_bytes as usize);
for entry in &self.entries {
if self.is_pic {
data.extend_from_slice(&[0u8; 4]);
} else {
data.extend_from_slice(&[0u8; 8]);
}
}
data
}
}
impl Default for X86JumpTable {
fn default() -> Self {
X86JumpTable::new(0, false)
}
}
#[derive(Debug, Clone, Default)]
pub struct JumpTableManager {
pub tables: HashMap<u32, X86JumpTable>,
pub total_table_bytes: u32,
pub total_entries: u32,
pub is_pic: bool,
next_id: u32,
}
impl JumpTableManager {
pub fn new(is_pic: bool) -> Self {
JumpTableManager {
tables: HashMap::new(),
total_table_bytes: 0,
total_entries: 0,
is_pic,
next_id: 0,
}
}
pub fn create_table(&mut self, default_target: u32) -> (u32, &mut X86JumpTable) {
let id = self.next_id;
self.next_id += 1;
let mut table = X86JumpTable::new(default_target, self.is_pic);
table.generate_label(id);
self.tables.insert(id, table);
(id, self.tables.get_mut(&id).unwrap())
}
pub fn add_case(&mut self, table_id: u32, value: i64, target: u32) {
if let Some(table) = self.tables.get_mut(&table_id) {
table.add_case(value, target);
}
}
pub fn build_all(&mut self) {
for table in self.tables.values_mut() {
table.build();
}
self.total_table_bytes = self.tables.values().map(|t| t.table_size_bytes).sum();
self.total_entries = self.tables.values().map(|t| t.entries.len() as u32).sum();
}
pub fn build_table(&mut self, table_id: u32) {
if let Some(table) = self.tables.get_mut(&table_id) {
table.build();
self.recompute_stats();
}
}
pub fn get_table(&self, table_id: u32) -> Option<&X86JumpTable> {
self.tables.get(&table_id)
}
pub fn get_table_mut(&mut self, table_id: u32) -> Option<&mut X86JumpTable> {
self.tables.get_mut(&table_id)
}
pub fn remove_table(&mut self, table_id: u32) -> Option<X86JumpTable> {
let table = self.tables.remove(&table_id);
if table.is_some() {
self.recompute_stats();
}
table
}
pub fn iter(&self) -> impl Iterator<Item = (&u32, &X86JumpTable)> {
self.tables.iter()
}
pub fn count(&self) -> usize {
self.tables.len()
}
pub fn is_empty(&self) -> bool {
self.tables.is_empty()
}
pub fn clear(&mut self) {
self.tables.clear();
self.total_table_bytes = 0;
self.total_entries = 0;
self.next_id = 0;
}
pub fn emit_all_dispatch_sequences(
&self,
cond_reg: &str,
temp_reg: &str,
) -> HashMap<u32, Vec<String>> {
let mut sequences = HashMap::new();
for (&id, table) in &self.tables {
sequences.insert(id, table.emit_dispatch_sequence(cond_reg, temp_reg));
}
sequences
}
pub fn emit_all_table_data(&self) -> Vec<String> {
let mut data = Vec::new();
for table in self.tables.values() {
data.extend(table.emit_table_data());
}
data
}
pub fn strategy_summary(&self) -> BTreeMap<LoweringStrategy, usize> {
let mut summary = BTreeMap::new();
for table in self.tables.values() {
*summary.entry(table.strategy).or_insert(0) += 1;
}
summary
}
fn recompute_stats(&mut self) {
self.total_table_bytes = self.tables.values().map(|t| t.table_size_bytes).sum();
self.total_entries = self.tables.values().map(|t| t.entries.len() as u32).sum();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_case_value_creation() {
let c = CaseValue::new(42, 5);
assert_eq!(c.value, 42);
assert_eq!(c.target, 5);
}
#[test]
fn test_cluster_creation() {
let cluster = CaseCluster::new(0, 4, 10);
assert_eq!(cluster.start, 0);
assert_eq!(cluster.end, 4);
assert_eq!(cluster.target, 10);
assert_eq!(cluster.span(), 5);
assert!(!cluster.is_default);
}
#[test]
fn test_cluster_merge() {
let c1 = CaseCluster::new(0, 4, 10);
let c2 = CaseCluster::new(5, 9, 10);
let merged = c1.merge(&c2);
assert_eq!(merged.start, 0);
assert_eq!(merged.end, 9);
assert_eq!(merged.target, 10);
assert_eq!(merged.count, 10);
}
#[test]
fn test_cluster_overlaps() {
let c1 = CaseCluster::new(0, 5, 10);
let c2 = CaseCluster::new(4, 9, 20);
assert!(c1.overlaps(&c2));
let c3 = CaseCluster::new(6, 10, 30);
assert!(!c1.overlaps(&c3));
}
#[test]
fn test_cluster_contains() {
let c = CaseCluster::new(10, 20, 5);
assert!(c.contains(&CaseValue::new(15, 5)));
assert!(!c.contains(&CaseValue::new(25, 5)));
assert!(c.contains(&CaseValue::new(10, 5)));
assert!(c.contains(&CaseValue::new(20, 5)));
}
#[test]
fn test_dense_cases_jump_table() {
let cases: Vec<CaseValue> = (0..10).map(|i| CaseValue::new(i as i64, i)).collect();
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert_eq!(analysis.case_count, 10);
assert_eq!(analysis.density, 1.0);
assert_eq!(analysis.recommended_strategy, LoweringStrategy::JumpTable);
}
#[test]
fn test_sparse_cases_binary_tree() {
let cases = vec![
CaseValue::new(1, 10),
CaseValue::new(100, 20),
CaseValue::new(200, 30),
CaseValue::new(300, 40),
];
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert_eq!(analysis.density, 4.0 / 300.0); assert_eq!(analysis.recommended_strategy, LoweringStrategy::BinaryTree);
}
#[test]
fn test_few_cases_linear() {
let cases = vec![
CaseValue::new(1, 10),
CaseValue::new(5, 20),
CaseValue::new(9, 30),
];
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert_eq!(analysis.recommended_strategy, LoweringStrategy::LinearChain);
}
#[test]
fn test_bit_test_possible() {
let cases = vec![
CaseValue::new(0, 10),
CaseValue::new(3, 20),
CaseValue::new(7, 30),
CaseValue::new(15, 40),
];
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert!(analysis.bit_test_possible);
assert_eq!(analysis.recommended_strategy, LoweringStrategy::BitTest);
}
#[test]
fn test_empty_cases_trivial() {
let cases: Vec<CaseValue> = vec![];
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert_eq!(analysis.recommended_strategy, LoweringStrategy::Trivial);
}
#[test]
fn test_duplicate_detection() {
let cases = vec![
CaseValue::new(1, 10),
CaseValue::new(1, 20), CaseValue::new(2, 30),
];
let analysis = DensityAnalysis::analyze(&cases, 99, 4, 0.40);
assert!(analysis.has_duplicates);
assert_eq!(analysis.duplicates_collapsed, 1);
assert_eq!(analysis.case_count, 2);
}
#[test]
fn test_build_dense_jump_table() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..8 {
jt.add_case(i, i * 10);
}
jt.build();
assert_eq!(jt.strategy, LoweringStrategy::JumpTable);
assert_eq!(jt.entries.len(), 8);
assert_eq!(jt.clusters.len(), 8); }
#[test]
fn test_cluster_formation() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.add_case(1, 10);
jt.add_case(2, 10);
jt.add_case(5, 20);
jt.add_case(6, 20);
jt.add_case(7, 20);
jt.build();
assert_eq!(jt.clusters.len(), 2);
assert_eq!(jt.clusters[0].start, 0);
assert_eq!(jt.clusters[0].end, 2);
assert_eq!(jt.clusters[1].start, 5);
assert_eq!(jt.clusters[1].end, 7);
}
#[test]
fn test_sparse_case_binary_tree() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(1, 10);
jt.add_case(100, 20);
jt.add_case(200, 30);
jt.add_case(300, 40);
jt.build();
assert_eq!(jt.strategy, LoweringStrategy::BinaryTree);
}
#[test]
fn test_table_entry_count() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..5 {
jt.add_case(i, i * 2);
}
jt.build();
assert_eq!(jt.entries.len(), 5);
}
#[test]
fn test_table_size_bytes() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..5 {
jt.add_case(i, i);
}
jt.build();
assert_eq!(jt.table_size_bytes, 5 * JUMP_TABLE_ENTRY_SIZE);
}
#[test]
fn test_pic_reloc_type() {
let jt_pic = X86JumpTable::new(0, true);
let jt_nonpic = X86JumpTable::new(0, false);
assert_eq!(jt_pic.reloc_type, JumpTableReloc::PC32);
assert_eq!(jt_nonpic.reloc_type, JumpTableReloc::Abs32);
assert!(jt_pic.is_pic);
assert!(!jt_nonpic.is_pic);
}
#[test]
fn test_pic_emit_dispatch() {
let mut jt = X86JumpTable::new(99, true);
for i in 0..3 {
jt.add_case(i, i * 5);
}
jt.build();
jt.generate_label(0);
let seq = jt.emit_dispatch_sequence("eax", "ecx");
assert!(!seq.is_empty());
let has_lea_rip = seq.iter().any(|s| s.contains("LEA") && s.contains("RIP"));
assert!(has_lea_rip);
}
#[test]
fn test_jump_table_dispatch() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.add_case(1, 20);
jt.add_case(2, 30);
jt.add_case(3, 40);
jt.build();
jt.generate_label(0);
let seq = jt.emit_dispatch_sequence("eax", "edx");
assert!(seq.len() >= 2);
assert!(seq.iter().any(|s| s.contains("CMP")));
}
#[test]
fn test_linear_chain_dispatch() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(5, 10);
jt.add_case(12, 20);
jt.build();
let seq = jt.emit_dispatch_sequence("eax", "edx");
let cmp_count = seq.iter().filter(|s| s.contains("CMP")).count();
assert!(cmp_count >= 2);
}
#[test]
fn test_trivial_dispatch() {
let jt = X86JumpTable::new(99, false);
let seq = jt.emit_dispatch_sequence("eax", "edx");
assert!(seq.iter().any(|s| s.contains("JMP")));
}
#[test]
fn test_emit_table_data() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..3 {
jt.add_case(i, i * 10);
}
jt.build();
jt.generate_label(0);
let data = jt.emit_table_data();
assert!(data.iter().any(|s| s.contains(".balign")));
assert!(data.iter().any(|s| s.contains(".LJTI0_99")));
}
#[test]
fn test_emit_table_data_pic() {
let mut jt = X86JumpTable::new(99, true);
for i in 0..3 {
jt.add_case(i, i * 10);
}
jt.build();
jt.generate_label(0);
let data = jt.emit_table_data();
assert!(data.iter().any(|s| s.contains(".long")));
}
#[test]
fn test_constant_pool_data() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..2 {
jt.add_case(i, i);
}
jt.build();
let data = jt.to_constant_pool_data();
assert_eq!(data.len(), 16);
}
#[test]
fn test_constant_pool_data_pic() {
let mut jt = X86JumpTable::new(99, true);
for i in 0..2 {
jt.add_case(i, i);
}
jt.build();
let data = jt.to_constant_pool_data();
assert_eq!(data.len(), 8);
}
#[test]
fn test_place_in_constant_pool() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.build();
jt.place_in_constant_pool(256);
assert_eq!(jt.table_offset, 256);
assert!(jt.in_constant_pool);
}
#[test]
fn test_get_entry() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..5 {
jt.add_case(i, i * 2);
}
jt.build();
let entry = jt.get_entry(0).unwrap();
assert_eq!(entry.case_value, 0);
let entry = jt.get_entry(4).unwrap();
assert_eq!(entry.case_value, 4);
assert!(jt.get_entry(5).is_none());
}
#[test]
fn test_get_entry_offset() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.add_case(1, 20);
jt.build();
assert_eq!(jt.get_entry_offset(0), Some(0));
assert_eq!(jt.get_entry_offset(1), Some(4));
assert_eq!(jt.get_entry_offset(2), None);
}
#[test]
fn test_get_cluster_entries() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..10 {
jt.add_case(i, i / 3); }
jt.build();
let cluster = CaseCluster::new(0, 2, 0);
let entries = jt.get_cluster_entries(&cluster);
assert_eq!(entries.len(), 3);
}
#[test]
fn test_unique_target_count() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.add_case(1, 20);
jt.add_case(2, 10); jt.add_case(3, 30);
jt.add_case(4, 30); jt.build();
let count = jt.unique_target_count();
assert!(count >= 3);
}
#[test]
fn test_reloc_types() {
assert_eq!(JumpTableReloc::PC32.size(), 4);
assert_eq!(JumpTableReloc::Abs64.size(), 8);
assert!(JumpTableReloc::PC32.is_pc_relative());
assert!(!JumpTableReloc::Abs32.is_pc_relative());
assert_eq!(JumpTableReloc::PC32.elf_name(), "R_X86_64_PC32");
assert_eq!(JumpTableReloc::Abs64.elf_name(), "R_X86_64_64");
}
#[test]
fn test_strategy_names() {
assert_eq!(LoweringStrategy::JumpTable.name(), "jump_table");
assert_eq!(LoweringStrategy::BinaryTree.name(), "binary_tree");
assert_eq!(LoweringStrategy::BitTest.name(), "bit_test");
assert_eq!(LoweringStrategy::LinearChain.name(), "linear_chain");
assert_eq!(LoweringStrategy::Trivial.name(), "trivial");
}
#[test]
fn test_strategy_uses_jump_table() {
assert!(LoweringStrategy::JumpTable.uses_jump_table());
assert!(!LoweringStrategy::BinaryTree.uses_jump_table());
assert!(!LoweringStrategy::BitTest.uses_jump_table());
}
#[test]
fn test_manager_create_table() {
let mut mgr = JumpTableManager::new(false);
let (id, table) = mgr.create_table(99);
assert_eq!(id, 0);
table.add_case(0, 10);
table.add_case(1, 20);
table.add_case(2, 30);
table.add_case(3, 40);
table.build();
assert_eq!(table.strategy, LoweringStrategy::JumpTable);
}
#[test]
fn test_manager_multiple_tables() {
let mut mgr = JumpTableManager::new(false);
let (id1, _) = mgr.create_table(99);
let (id2, _) = mgr.create_table(100);
assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(mgr.count(), 2);
}
#[test]
fn test_manager_build_all() {
let mut mgr = JumpTableManager::new(false);
let (id1, t1) = mgr.create_table(99);
t1.add_case(0, 10);
t1.add_case(1, 20);
t1.add_case(2, 30);
t1.add_case(3, 40);
let (id2, t2) = mgr.create_table(100);
t2.add_case(1, 10);
t2.add_case(100, 20);
mgr.build_all();
let t1 = mgr.get_table(id1).unwrap();
let t2 = mgr.get_table(id2).unwrap();
assert_eq!(t1.strategy, LoweringStrategy::JumpTable);
assert_eq!(t2.strategy, LoweringStrategy::BinaryTree);
assert!(mgr.total_table_bytes > 0);
assert!(mgr.total_entries > 0);
}
#[test]
fn test_manager_get_table() {
let mut mgr = JumpTableManager::new(false);
let (id, _) = mgr.create_table(99);
assert!(mgr.get_table(id).is_some());
assert!(mgr.get_table(999).is_none());
}
#[test]
fn test_manager_remove_table() {
let mut mgr = JumpTableManager::new(false);
let (id, _) = mgr.create_table(99);
assert_eq!(mgr.count(), 1);
let removed = mgr.remove_table(id);
assert!(removed.is_some());
assert_eq!(mgr.count(), 0);
}
#[test]
fn test_manager_clear() {
let mut mgr = JumpTableManager::new(false);
mgr.create_table(99);
mgr.create_table(100);
mgr.clear();
assert_eq!(mgr.count(), 0);
assert!(mgr.is_empty());
}
#[test]
fn test_manager_strategy_summary() {
let mut mgr = JumpTableManager::new(false);
let (_, t1) = mgr.create_table(99);
t1.add_case(0, 10);
t1.add_case(1, 20);
t1.add_case(2, 30);
t1.add_case(3, 40);
let (_, t2) = mgr.create_table(100);
t2.add_case(1, 10);
t2.add_case(100, 20);
mgr.build_all();
let summary = mgr.strategy_summary();
assert_eq!(summary.get(&LoweringStrategy::JumpTable), Some(&1));
assert_eq!(summary.get(&LoweringStrategy::BinaryTree), Some(&1));
}
#[test]
fn test_manager_emit_all_dispatch() {
let mut mgr = JumpTableManager::new(false);
let (_, t1) = mgr.create_table(99);
t1.add_case(0, 10);
t1.add_case(1, 20);
t1.add_case(2, 30);
t1.add_case(3, 40);
mgr.build_all();
let seqs = mgr.emit_all_dispatch_sequences("eax", "ecx");
assert_eq!(seqs.len(), 1);
assert!(!seqs[&0].is_empty());
}
#[test]
fn test_manager_emit_all_table_data() {
let mut mgr = JumpTableManager::new(false);
let (_, t1) = mgr.create_table(99);
t1.add_case(0, 10);
t1.add_case(1, 20);
mgr.build_all();
let data = mgr.emit_all_table_data();
assert!(!data.is_empty());
assert!(data.iter().any(|s| s.contains(".balign")));
}
#[test]
fn test_jump_table_default() {
let jt = X86JumpTable::default();
assert!(jt.entries.is_empty());
assert!(!jt.is_pic);
assert_eq!(jt.strategy, LoweringStrategy::Trivial);
}
#[test]
fn test_manager_default() {
let mgr = JumpTableManager::default();
assert!(mgr.is_empty());
assert!(!mgr.is_pic);
}
#[test]
fn test_density_analysis_default() {
let a = DensityAnalysis::default();
assert_eq!(a.case_count, 0);
assert_eq!(a.recommended_strategy, LoweringStrategy::Trivial);
}
#[test]
fn test_jump_table_stats() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..10 {
jt.add_case(i, i);
}
jt.build();
assert_eq!(jt.stats.total_cases, 10);
assert_eq!(jt.stats.table_entries, 10);
assert!(jt.stats.cycles_saved_est > 0);
}
#[test]
fn test_single_case() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(42, 10);
jt.build();
assert_eq!(jt.strategy, LoweringStrategy::LinearChain);
assert_eq!(jt.entries.len(), 0);
}
#[test]
fn test_all_same_target() {
let mut jt = X86JumpTable::new(99, false);
for i in 0..5 {
jt.add_case(i, 10); }
jt.build();
assert_eq!(jt.clusters.len(), 1);
assert_eq!(jt.clusters[0].start, 0);
assert_eq!(jt.clusters[0].end, 4);
}
#[test]
fn test_negative_case_values() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(-5, 10);
jt.add_case(-3, 20);
jt.add_case(0, 30);
jt.add_case(2, 40);
jt.build();
let analysis = &jt.density_analysis;
assert_eq!(analysis.min_value, -5);
assert_eq!(analysis.max_value, 2);
assert_eq!(analysis.range, 7);
}
#[test]
fn test_default_target_in_entries() {
let mut jt = X86JumpTable::new(99, false);
jt.add_case(0, 10);
jt.add_case(2, 20); jt.build();
let entry = jt.get_entry(1).unwrap();
assert!(entry.is_default);
}
}