use crate::{ir::IrModule, ComputationGraph, IrFunction, JitError, JitResult, NodeId};
use std::collections::{HashMap, HashSet, VecDeque};
use torsh_core::{DType, Shape};
pub struct PartialEvaluator {
config: PartialEvalConfig,
constant_folder: ConstantFolder,
specializer: FunctionSpecializer,
dead_code_eliminator: DeadCodeEliminator,
loop_optimizer: LoopOptimizer,
symbolic_executor: SymbolicExecutor,
}
impl PartialEvaluator {
pub fn new(config: PartialEvalConfig) -> Self {
Self {
constant_folder: ConstantFolder::new(),
specializer: FunctionSpecializer::new(),
dead_code_eliminator: DeadCodeEliminator::new(),
loop_optimizer: LoopOptimizer::new(),
symbolic_executor: SymbolicExecutor::new(),
config,
}
}
pub fn evaluate_graph(&mut self, graph: &ComputationGraph) -> JitResult<OptimizedGraph> {
let mut working_graph = graph.clone();
let mut statistics = EvaluationStatistics::new();
let symbolic_info = self.symbolic_executor.execute(&working_graph)?;
statistics.symbolic_execution_time = symbolic_info.execution_time;
if self.config.enable_constant_folding {
let fold_result = self.constant_folder.fold_constants(&mut working_graph)?;
statistics.constants_folded = fold_result.constants_folded;
statistics.constant_folding_time = fold_result.execution_time;
}
if self.config.enable_specialization {
let spec_result = self
.specializer
.specialize_functions(&mut working_graph, &symbolic_info)?;
statistics.functions_specialized = spec_result.functions_specialized;
statistics.specialization_time = spec_result.execution_time;
}
if self.config.enable_dead_code_elimination {
let dce_result = self.dead_code_eliminator.eliminate(&mut working_graph)?;
statistics.dead_nodes_removed = dce_result.nodes_removed;
statistics.dead_code_elimination_time = dce_result.execution_time;
}
if self.config.enable_loop_optimization {
let loop_result = self.loop_optimizer.optimize_loops(&mut working_graph)?;
statistics.loops_optimized = loop_result.loops_optimized;
statistics.loop_optimization_time = loop_result.execution_time;
}
Ok(OptimizedGraph {
graph: working_graph,
statistics,
optimizations_applied: self.get_applied_optimizations(),
})
}
pub fn evaluate_ir(&mut self, ir_module: &IrModule) -> JitResult<OptimizedIrModule> {
let mut working_module = ir_module.clone();
let mut statistics = IrEvaluationStatistics::new();
let func_result = self.evaluate_function(&mut working_module)?;
statistics.merge(func_result);
self.optimize_module(&mut working_module)?;
Ok(OptimizedIrModule {
module: working_module,
statistics,
})
}
fn evaluate_function(
&mut self,
function: &mut IrFunction,
) -> JitResult<IrEvaluationStatistics> {
let mut stats = IrEvaluationStatistics::new();
let deps = self.build_dependency_graph(function)?;
let data_flow = self.analyze_data_flow(function, &deps)?;
let const_result = self.propagate_constants(function, &data_flow)?;
stats.constants_propagated = const_result.constants_propagated;
let dead_result = self.eliminate_dead_instructions(function, &deps)?;
stats.dead_instructions_removed = dead_result.instructions_removed;
let strength_result = self.perform_strength_reduction(function)?;
stats.strength_reductions = strength_result.reductions_applied;
Ok(stats)
}
fn build_dependency_graph(&self, function: &IrFunction) -> JitResult<DependencyGraph> {
let mut deps = DependencyGraph::new();
for (idx, instruction) in function.instructions().enumerate() {
let inst_id = InstructionId(idx);
deps.add_instruction(inst_id, instruction.clone());
for operand in instruction.operands() {
let dep_id = InstructionId(operand.0 as usize);
deps.add_dependency(inst_id, dep_id);
}
}
Ok(deps)
}
fn analyze_data_flow(
&self,
function: &IrFunction,
deps: &DependencyGraph,
) -> JitResult<DataFlowInfo> {
let mut data_flow = DataFlowInfo::new();
let mut reaching_defs: HashMap<InstructionId, HashSet<InstructionId>> = HashMap::new();
for (inst_id, instruction) in deps.instructions() {
let mut defs = HashSet::new();
for dep_id in deps.dependencies(inst_id) {
if let Some(dep_defs) = reaching_defs.get(dep_id) {
defs.extend(dep_defs.iter().cloned());
}
}
if instruction.produces_value() {
defs.insert(*inst_id);
}
reaching_defs.insert(*inst_id, defs);
}
data_flow.reaching_definitions = reaching_defs;
let mut live_vars: HashMap<InstructionId, HashSet<InstructionId>> = HashMap::new();
let instructions: Vec<_> = deps.instructions().collect();
for (inst_id, instruction) in instructions.iter().rev() {
let mut live = HashSet::new();
for user_id in deps.users(inst_id) {
if let Some(user_live) = live_vars.get(user_id) {
live.extend(user_live.iter().cloned());
}
}
if instruction.produces_value() {
live.remove(inst_id);
}
for operand in instruction.operands() {
let op_id = InstructionId(operand.0 as usize);
live.insert(op_id);
}
live_vars.insert(**inst_id, live);
}
data_flow.live_variables = live_vars;
Ok(data_flow)
}
fn propagate_constants(
&mut self,
function: &mut IrFunction,
data_flow: &DataFlowInfo,
) -> JitResult<ConstantPropagationResult> {
let mut constants_propagated = 0;
let mut constant_values: HashMap<crate::ir::IrValue, ConstantValue> = HashMap::new();
for (idx, instruction) in function.instructions_mut().enumerate() {
let inst_id = crate::ir::IrValue(idx as u32);
let mut all_constant = true;
let mut operand_values = Vec::new();
for operand in instruction.operands() {
if let Some(const_val) = constant_values.get(operand) {
operand_values.push(Some(const_val.clone()));
} else {
operand_values.push(None);
all_constant = false;
}
}
if all_constant && self.can_evaluate_at_compile_time(instruction) {
if let Some(result) = self.evaluate_instruction(instruction, &operand_values)? {
constant_values.insert(inst_id, result.clone());
constants_propagated += 1;
}
}
}
Ok(ConstantPropagationResult {
constants_propagated,
execution_time: std::time::Duration::from_millis(1), })
}
fn can_evaluate_at_compile_time(&self, instruction: &crate::ir::Instruction) -> bool {
use crate::ir::IrOpcode;
match instruction.opcode {
IrOpcode::Add
| IrOpcode::Sub
| IrOpcode::Mul
| IrOpcode::Div
| IrOpcode::Neg
| IrOpcode::Sqrt
| IrOpcode::Exp
| IrOpcode::Log => true,
_ => false,
}
}
fn evaluate_instruction(
&self,
instruction: &crate::ir::Instruction,
operands: &[Option<ConstantValue>],
) -> JitResult<Option<ConstantValue>> {
use crate::ir::IrOpcode;
match instruction.opcode {
IrOpcode::Add => {
if let (Some(Some(a)), Some(Some(b))) = (operands.get(0), operands.get(1)) {
Ok(Some(self.add_constants(a, b)?))
} else {
Ok(None)
}
}
IrOpcode::Sub => {
if let (Some(Some(a)), Some(Some(b))) = (operands.get(0), operands.get(1)) {
Ok(Some(self.sub_constants(a, b)?))
} else {
Ok(None)
}
}
IrOpcode::Mul => {
if let (Some(Some(a)), Some(Some(b))) = (operands.get(0), operands.get(1)) {
Ok(Some(self.mul_constants(a, b)?))
} else {
Ok(None)
}
}
IrOpcode::Div => {
if let (Some(Some(a)), Some(Some(b))) = (operands.get(0), operands.get(1)) {
Ok(Some(self.div_constants(a, b)?))
} else {
Ok(None)
}
}
IrOpcode::Neg => {
if let Some(Some(a)) = operands.get(0) {
Ok(Some(self.neg_constant(a)?))
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
fn add_constants(&self, a: &ConstantValue, b: &ConstantValue) -> JitResult<ConstantValue> {
match (a, b) {
(ConstantValue::Float32(a), ConstantValue::Float32(b)) => {
Ok(ConstantValue::Float32(a + b))
}
(ConstantValue::Float64(a), ConstantValue::Float64(b)) => {
Ok(ConstantValue::Float64(a + b))
}
(ConstantValue::Int32(a), ConstantValue::Int32(b)) => Ok(ConstantValue::Int32(a + b)),
(ConstantValue::Int64(a), ConstantValue::Int64(b)) => Ok(ConstantValue::Int64(a + b)),
_ => Err(JitError::CompilationError(
"Incompatible types for addition".to_string(),
)),
}
}
fn sub_constants(&self, a: &ConstantValue, b: &ConstantValue) -> JitResult<ConstantValue> {
match (a, b) {
(ConstantValue::Float32(a), ConstantValue::Float32(b)) => {
Ok(ConstantValue::Float32(a - b))
}
(ConstantValue::Float64(a), ConstantValue::Float64(b)) => {
Ok(ConstantValue::Float64(a - b))
}
(ConstantValue::Int32(a), ConstantValue::Int32(b)) => Ok(ConstantValue::Int32(a - b)),
(ConstantValue::Int64(a), ConstantValue::Int64(b)) => Ok(ConstantValue::Int64(a - b)),
_ => Err(JitError::CompilationError(
"Incompatible types for subtraction".to_string(),
)),
}
}
fn mul_constants(&self, a: &ConstantValue, b: &ConstantValue) -> JitResult<ConstantValue> {
match (a, b) {
(ConstantValue::Float32(a), ConstantValue::Float32(b)) => {
Ok(ConstantValue::Float32(a * b))
}
(ConstantValue::Float64(a), ConstantValue::Float64(b)) => {
Ok(ConstantValue::Float64(a * b))
}
(ConstantValue::Int32(a), ConstantValue::Int32(b)) => Ok(ConstantValue::Int32(a * b)),
(ConstantValue::Int64(a), ConstantValue::Int64(b)) => Ok(ConstantValue::Int64(a * b)),
_ => Err(JitError::CompilationError(
"Incompatible types for multiplication".to_string(),
)),
}
}
fn div_constants(&self, a: &ConstantValue, b: &ConstantValue) -> JitResult<ConstantValue> {
match (a, b) {
(ConstantValue::Float32(a), ConstantValue::Float32(b)) => {
if *b == 0.0 {
Err(JitError::CompilationError("Division by zero".to_string()))
} else {
Ok(ConstantValue::Float32(a / b))
}
}
(ConstantValue::Float64(a), ConstantValue::Float64(b)) => {
if *b == 0.0 {
Err(JitError::CompilationError("Division by zero".to_string()))
} else {
Ok(ConstantValue::Float64(a / b))
}
}
(ConstantValue::Int32(a), ConstantValue::Int32(b)) => {
if *b == 0 {
Err(JitError::CompilationError("Division by zero".to_string()))
} else {
Ok(ConstantValue::Int32(a / b))
}
}
(ConstantValue::Int64(a), ConstantValue::Int64(b)) => {
if *b == 0 {
Err(JitError::CompilationError("Division by zero".to_string()))
} else {
Ok(ConstantValue::Int64(a / b))
}
}
_ => Err(JitError::CompilationError(
"Incompatible types for division".to_string(),
)),
}
}
fn neg_constant(&self, a: &ConstantValue) -> JitResult<ConstantValue> {
match a {
ConstantValue::Float32(a) => Ok(ConstantValue::Float32(-a)),
ConstantValue::Float64(a) => Ok(ConstantValue::Float64(-a)),
ConstantValue::Int32(a) => Ok(ConstantValue::Int32(-a)),
ConstantValue::Int64(a) => Ok(ConstantValue::Int64(-a)),
_ => Err(JitError::CompilationError(
"Cannot negate this constant type".to_string(),
)),
}
}
fn eliminate_dead_instructions(
&mut self,
function: &mut IrFunction,
deps: &DependencyGraph,
) -> JitResult<DeadInstructionResult> {
let mut instructions_removed = 0;
let mut to_remove = HashSet::new();
for (inst_id, _) in deps.instructions() {
if deps.users(inst_id).is_empty()
&& !self.has_side_effects(deps.get_instruction(inst_id))
{
to_remove.insert(*inst_id);
}
}
function.retain_instructions(|idx, _| {
let inst_id = InstructionId(idx);
if to_remove.contains(&inst_id) {
instructions_removed += 1;
false
} else {
true
}
});
Ok(DeadInstructionResult {
instructions_removed,
execution_time: std::time::Duration::from_millis(1), })
}
fn has_side_effects(&self, instruction: &crate::ir::Instruction) -> bool {
use crate::ir::IrOpcode;
match instruction.opcode {
IrOpcode::Store | IrOpcode::Call => true,
_ => false,
}
}
fn perform_strength_reduction(
&mut self,
ir_module: &mut crate::ir::IrModule,
) -> JitResult<StrengthReductionResult> {
let mut reductions_applied = 0;
for (_block_id, block) in ir_module.blocks.iter_mut() {
for instruction in &mut block.instructions {
use crate::ir::IrOpcode;
match instruction.opcode {
IrOpcode::Mul => {
reductions_applied += 1;
}
IrOpcode::Div => {
reductions_applied += 1;
}
_ => {}
}
}
}
Ok(StrengthReductionResult {
reductions_applied,
execution_time: std::time::Duration::from_millis(1), })
}
fn optimize_module(&mut self, module: &mut IrModule) -> JitResult<()> {
let _ = module.remove_unused_functions();
if self.config.enable_inlining {
module.inline_small_functions()?;
}
Ok(())
}
fn get_applied_optimizations(&self) -> Vec<OptimizationType> {
let mut optimizations = Vec::new();
if self.config.enable_constant_folding {
optimizations.push(OptimizationType::ConstantFolding);
}
if self.config.enable_specialization {
optimizations.push(OptimizationType::FunctionSpecialization);
}
if self.config.enable_dead_code_elimination {
optimizations.push(OptimizationType::DeadCodeElimination);
}
if self.config.enable_loop_optimization {
optimizations.push(OptimizationType::LoopOptimization);
}
optimizations
}
}
#[derive(Debug, Clone)]
pub struct PartialEvalConfig {
pub enable_constant_folding: bool,
pub enable_specialization: bool,
pub enable_dead_code_elimination: bool,
pub enable_loop_optimization: bool,
pub enable_inlining: bool,
pub inline_threshold: usize,
pub max_unroll_iterations: usize,
pub aggressive_optimization: bool,
}
impl Default for PartialEvalConfig {
fn default() -> Self {
Self {
enable_constant_folding: true,
enable_specialization: true,
enable_dead_code_elimination: true,
enable_loop_optimization: true,
enable_inlining: true,
inline_threshold: 50,
max_unroll_iterations: 8,
aggressive_optimization: false,
}
}
}
pub struct ConstantFolder {
evaluation_depth: usize,
}
impl ConstantFolder {
pub fn new() -> Self {
Self {
evaluation_depth: 0,
}
}
pub fn fold_constants(
&mut self,
graph: &mut ComputationGraph,
) -> JitResult<ConstantFoldingResult> {
let mut constants_folded = 0;
let start_time = std::time::Instant::now();
let mut constant_nodes = HashMap::new();
for (node_id, node) in graph.nodes() {
if self.is_constant_node(node) {
constant_nodes.insert(node_id, self.extract_constant_value(node)?);
}
}
let mut changed = true;
while changed {
changed = false;
let node_ids: Vec<_> = graph.nodes().map(|(id, _)| id).collect();
for node_id in node_ids {
if let Some(node) = graph.node(node_id).cloned() {
if !constant_nodes.contains_key(&node_id)
&& self.can_fold_node(&node, &constant_nodes)
{
if let Ok(value) = self.evaluate_node(&node, &constant_nodes) {
constant_nodes.insert(node_id, value);
constants_folded += 1;
changed = true;
}
}
}
}
}
Ok(ConstantFoldingResult {
constants_folded,
execution_time: start_time.elapsed(),
})
}
fn is_constant_node(&self, node: &crate::graph::Node) -> bool {
matches!(node.op, crate::graph::Operation::Input)
}
fn extract_constant_value(&self, node: &crate::graph::Node) -> JitResult<ConstantValue> {
Ok(ConstantValue::Float32(0.0))
}
fn can_fold_node(
&self,
node: &crate::graph::Node,
constants: &HashMap<NodeId, ConstantValue>,
) -> bool {
for input in &node.inputs {
if !constants.contains_key(input) {
return false;
}
}
true
}
fn evaluate_node(
&self,
node: &crate::graph::Node,
constants: &HashMap<NodeId, ConstantValue>,
) -> JitResult<ConstantValue> {
Ok(ConstantValue::Float32(1.0))
}
}
pub struct FunctionSpecializer {
specializations: HashMap<String, Vec<SpecializedFunction>>,
}
impl FunctionSpecializer {
pub fn new() -> Self {
Self {
specializations: HashMap::new(),
}
}
pub fn specialize_functions(
&mut self,
graph: &mut ComputationGraph,
symbolic_info: &SymbolicExecutionInfo,
) -> JitResult<SpecializationResult> {
let mut functions_specialized = 0;
let start_time = std::time::Instant::now();
for (node_id, node) in graph.nodes() {
if let Some(spec_params) = self.identify_specialization_opportunity(node, symbolic_info)
{
if self.should_specialize(node, &spec_params) {
self.create_specialized_version(node, spec_params)?;
functions_specialized += 1;
}
}
}
Ok(SpecializationResult {
functions_specialized,
execution_time: start_time.elapsed(),
})
}
fn identify_specialization_opportunity(
&self,
node: &crate::graph::Node,
symbolic_info: &SymbolicExecutionInfo,
) -> Option<SpecializationParameters> {
None }
fn should_specialize(
&self,
node: &crate::graph::Node,
params: &SpecializationParameters,
) -> bool {
true }
fn create_specialized_version(
&mut self,
node: &crate::graph::Node,
params: SpecializationParameters,
) -> JitResult<()> {
Ok(()) }
}
pub struct DeadCodeEliminator;
impl DeadCodeEliminator {
pub fn new() -> Self {
Self
}
pub fn eliminate(
&mut self,
graph: &mut ComputationGraph,
) -> JitResult<DeadCodeEliminationResult> {
let mut nodes_removed = 0;
let start_time = std::time::Instant::now();
let mut reachable = HashSet::new();
let mut queue = VecDeque::new();
for (node_id, node) in graph.nodes() {
if node.is_output {
queue.push_back(node_id);
reachable.insert(node_id);
}
}
while let Some(node_id) = queue.pop_front() {
if let Some(node) = graph.node(node_id) {
for input_id in &node.inputs {
if !reachable.contains(input_id) {
reachable.insert(*input_id);
queue.push_back(*input_id);
}
}
}
}
let all_nodes: Vec<_> = graph.nodes().map(|(id, _)| id).collect();
for node_id in all_nodes {
if !reachable.contains(&node_id) {
let _ = graph.remove_node(node_id);
nodes_removed += 1;
}
}
Ok(DeadCodeEliminationResult {
nodes_removed,
execution_time: start_time.elapsed(),
})
}
}
pub struct LoopOptimizer;
impl LoopOptimizer {
pub fn new() -> Self {
Self
}
pub fn optimize_loops(
&mut self,
graph: &mut ComputationGraph,
) -> JitResult<LoopOptimizationResult> {
let mut loops_optimized = 0;
let start_time = std::time::Instant::now();
let loops = self.detect_loops(graph)?;
for loop_info in loops {
if self.should_unroll(&loop_info) {
self.unroll_loop(graph, &loop_info)?;
loops_optimized += 1;
}
}
Ok(LoopOptimizationResult {
loops_optimized,
execution_time: start_time.elapsed(),
})
}
fn detect_loops(&self, graph: &ComputationGraph) -> JitResult<Vec<LoopInfo>> {
Ok(Vec::new()) }
fn should_unroll(&self, loop_info: &LoopInfo) -> bool {
loop_info.iteration_count.is_some()
&& loop_info
.iteration_count
.expect("iteration count should be Some based on check")
<= 8
}
fn unroll_loop(&mut self, graph: &mut ComputationGraph, loop_info: &LoopInfo) -> JitResult<()> {
Ok(()) }
}
pub struct SymbolicExecutor;
impl SymbolicExecutor {
pub fn new() -> Self {
Self
}
pub fn execute(&mut self, graph: &ComputationGraph) -> JitResult<SymbolicExecutionInfo> {
let start_time = std::time::Instant::now();
let mut info = SymbolicExecutionInfo {
constant_values: HashMap::new(),
shape_information: HashMap::new(),
type_information: HashMap::new(),
execution_time: std::time::Duration::from_millis(0),
};
for (node_id, node) in graph.nodes() {
if let Some(shape) = self.infer_symbolic_shape(node) {
info.shape_information.insert(node_id, shape);
}
if let Some(dtype) = self.infer_symbolic_type(node) {
info.type_information.insert(node_id, dtype);
}
}
info.execution_time = start_time.elapsed();
Ok(info)
}
fn infer_symbolic_shape(&self, node: &crate::graph::Node) -> Option<SymbolicShape> {
None }
fn infer_symbolic_type(&self, node: &crate::graph::Node) -> Option<DType> {
Some(node.dtype)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct InstructionId(usize);
#[derive(Debug, Clone)]
pub struct DependencyGraph {
instructions: HashMap<InstructionId, crate::ir::Instruction>,
dependencies: HashMap<InstructionId, Vec<InstructionId>>,
users: HashMap<InstructionId, Vec<InstructionId>>,
}
impl DependencyGraph {
pub fn new() -> Self {
Self {
instructions: HashMap::new(),
dependencies: HashMap::new(),
users: HashMap::new(),
}
}
pub fn add_instruction(&mut self, id: InstructionId, instruction: crate::ir::Instruction) {
self.instructions.insert(id, instruction);
self.dependencies.insert(id, Vec::new());
self.users.insert(id, Vec::new());
}
pub fn add_dependency(&mut self, user: InstructionId, dep: InstructionId) {
self.dependencies.entry(user).or_default().push(dep);
self.users.entry(dep).or_default().push(user);
}
pub fn instructions(&self) -> impl Iterator<Item = (&InstructionId, &crate::ir::Instruction)> {
self.instructions.iter()
}
pub fn dependencies(&self, id: &InstructionId) -> &[InstructionId] {
self.dependencies
.get(id)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn users(&self, id: &InstructionId) -> &[InstructionId] {
self.users.get(id).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn get_instruction(&self, id: &InstructionId) -> &crate::ir::Instruction {
&self.instructions[id]
}
}
#[derive(Debug)]
pub struct DataFlowInfo {
pub reaching_definitions: HashMap<InstructionId, HashSet<InstructionId>>,
pub live_variables: HashMap<InstructionId, HashSet<InstructionId>>,
}
impl DataFlowInfo {
pub fn new() -> Self {
Self {
reaching_definitions: HashMap::new(),
live_variables: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum ConstantValue {
Float32(f32),
Float64(f64),
Int32(i32),
Int64(i64),
Boolean(bool),
}
#[derive(Debug)]
pub struct SymbolicExecutionInfo {
pub constant_values: HashMap<NodeId, ConstantValue>,
pub shape_information: HashMap<NodeId, SymbolicShape>,
pub type_information: HashMap<NodeId, DType>,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct SymbolicShape {
pub dimensions: Vec<SymbolicDimension>,
}
#[derive(Debug)]
pub enum SymbolicDimension {
Constant(usize),
Variable(String),
Expression(String),
}
#[derive(Debug)]
pub struct SpecializationParameters {
pub constant_params: HashMap<String, ConstantValue>,
pub shape_params: HashMap<String, Shape>,
pub type_params: HashMap<String, DType>,
}
#[derive(Debug)]
pub struct SpecializedFunction {
pub original_name: String,
pub specialized_name: String,
pub parameters: SpecializationParameters,
pub estimated_speedup: f64,
}
#[derive(Debug)]
pub struct LoopInfo {
pub header_node: NodeId,
pub back_edges: Vec<(NodeId, NodeId)>,
pub iteration_count: Option<usize>,
pub induction_variables: Vec<NodeId>,
}
#[derive(Debug)]
pub struct OptimizedGraph {
pub graph: ComputationGraph,
pub statistics: EvaluationStatistics,
pub optimizations_applied: Vec<OptimizationType>,
}
#[derive(Debug)]
pub struct OptimizedIrModule {
pub module: IrModule,
pub statistics: IrEvaluationStatistics,
}
#[derive(Debug, Default)]
pub struct EvaluationStatistics {
pub constants_folded: usize,
pub functions_specialized: usize,
pub dead_nodes_removed: usize,
pub loops_optimized: usize,
pub constant_folding_time: std::time::Duration,
pub specialization_time: std::time::Duration,
pub dead_code_elimination_time: std::time::Duration,
pub loop_optimization_time: std::time::Duration,
pub symbolic_execution_time: std::time::Duration,
}
impl EvaluationStatistics {
pub fn new() -> Self {
Self::default()
}
pub fn merge(&mut self, other: Self) {
self.constants_folded += other.constants_folded;
self.functions_specialized += other.functions_specialized;
self.dead_nodes_removed += other.dead_nodes_removed;
self.loops_optimized += other.loops_optimized;
self.constant_folding_time += other.constant_folding_time;
self.specialization_time += other.specialization_time;
self.dead_code_elimination_time += other.dead_code_elimination_time;
self.loop_optimization_time += other.loop_optimization_time;
self.symbolic_execution_time += other.symbolic_execution_time;
}
}
#[derive(Debug, Default)]
pub struct IrEvaluationStatistics {
pub constants_propagated: usize,
pub dead_instructions_removed: usize,
pub strength_reductions: usize,
}
impl IrEvaluationStatistics {
pub fn new() -> Self {
Self::default()
}
pub fn merge(&mut self, other: Self) {
self.constants_propagated += other.constants_propagated;
self.dead_instructions_removed += other.dead_instructions_removed;
self.strength_reductions += other.strength_reductions;
}
}
#[derive(Debug)]
pub struct ConstantFoldingResult {
pub constants_folded: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct SpecializationResult {
pub functions_specialized: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct DeadCodeEliminationResult {
pub nodes_removed: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct LoopOptimizationResult {
pub loops_optimized: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct ConstantPropagationResult {
pub constants_propagated: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct DeadInstructionResult {
pub instructions_removed: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug)]
pub struct StrengthReductionResult {
pub reductions_applied: usize,
pub execution_time: std::time::Duration,
}
#[derive(Debug, Clone)]
pub enum OptimizationType {
ConstantFolding,
FunctionSpecialization,
DeadCodeElimination,
LoopOptimization,
ConstantPropagation,
StrengthReduction,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_eval_config() {
let config = PartialEvalConfig::default();
assert!(config.enable_constant_folding);
assert!(config.enable_specialization);
assert!(config.enable_dead_code_elimination);
assert!(config.enable_loop_optimization);
}
#[test]
fn test_constant_value_operations() {
let evaluator = PartialEvaluator::new(PartialEvalConfig::default());
let a = ConstantValue::Float32(2.0);
let b = ConstantValue::Float32(3.0);
let result = evaluator.add_constants(&a, &b).unwrap();
if let ConstantValue::Float32(val) = result {
assert_eq!(val, 5.0);
} else {
panic!("Expected Float32 result");
}
}
#[test]
fn test_dependency_graph() {
let mut deps = DependencyGraph::new();
let inst1 = InstructionId(0);
let inst2 = InstructionId(1);
use crate::ir::{Instruction, IrOpcode, IrValue};
use std::collections::HashMap;
let inst1_instruction = Instruction {
result: Some(IrValue(0)),
opcode: IrOpcode::Const,
operands: vec![],
attrs: HashMap::new(),
};
let inst2_instruction = Instruction {
result: Some(IrValue(1)),
opcode: IrOpcode::Const,
operands: vec![],
attrs: HashMap::new(),
};
deps.add_instruction(inst1, inst1_instruction);
deps.add_instruction(inst2, inst2_instruction);
deps.add_dependency(inst2, inst1);
assert_eq!(deps.dependencies(&inst2), &[inst1]);
assert_eq!(deps.users(&inst1), &[inst2]);
}
#[test]
fn test_partial_evaluator_creation() {
let config = PartialEvalConfig::default();
let evaluator = PartialEvaluator::new(config);
assert_eq!(evaluator.get_applied_optimizations().len(), 4);
}
}