From eb4d15b40eac4bd4ff566f2311703fc8b984bf77 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:03:55 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Optimize=20?= =?UTF-8?q?Gazle=20Pass=20Change=20Tracking=20and=20Fold=20Scalar=20Logari?= =?UTF-8?q?thms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/src/codegen/arch/aarch64.rs | 2 +- .../src/modules/gazle/constant_propagation.rs | 5 +- .../src/modules/gazle/eliminate_dead_loops.rs | 6 +- .../modules/gazle/eliminate_dead_stores.rs | 8 ++- .../gazle/eliminate_redundant_loads.rs | 8 ++- rust/src/modules/gazle/fold_constants.rs | 9 ++- rust/src/modules/gazle/fold_conversions.rs | 55 ++++++++++++++++++- rust/src/modules/gazle/jump_threading.rs | 6 +- rust/src/modules/gazle/optimize_bytecode.rs | 19 ++++++- .../modules/gazle/specialized_instructions.rs | 5 +- rust/src/modules/gazle/strength_reduction.rs | 13 ++++- rust/src/modules/gazle/utils/run_pass.rs | 14 +++-- 12 files changed, 128 insertions(+), 22 deletions(-) diff --git a/rust/src/codegen/arch/aarch64.rs b/rust/src/codegen/arch/aarch64.rs index 12d00dfe..3798c010 100644 --- a/rust/src/codegen/arch/aarch64.rs +++ b/rust/src/codegen/arch/aarch64.rs @@ -19,7 +19,7 @@ use crate::types::instructions::Instructions; use ahash::AHashMap; use smol_str::SmolStr; pub fn compile_aarch64(mut instructions: Vec) -> Result { - specialized_instructions(&mut instructions); + let _ = specialized_instructions(&mut instructions); let empty_imports: AHashMap = AHashMap::new(); let (var_count, _symbol_table) = resolve_symbols(&mut instructions, &empty_imports); let mut builder = AArch64Builder::new() diff --git a/rust/src/modules/gazle/constant_propagation.rs b/rust/src/modules/gazle/constant_propagation.rs index d6dfe407..d0761fcb 100644 --- a/rust/src/modules/gazle/constant_propagation.rs +++ b/rust/src/modules/gazle/constant_propagation.rs @@ -29,7 +29,7 @@ fn extract_push_value(instr: &Instructions) -> Option { _ => None, } } -pub fn constant_propagation(bytecode: &mut [Instructions]) { +pub fn constant_propagation(bytecode: &mut [Instructions]) -> bool { const MAX_INLINE_USES: usize = 8; let mut get_counts: AHashMap = AHashMap::new(); for instr in bytecode.iter() { @@ -38,11 +38,13 @@ pub fn constant_propagation(bytecode: &mut [Instructions]) { } } let mut const_map: AHashMap> = AHashMap::new(); + let mut changed = false; for i in 0..bytecode.len() { match &bytecode[i] { Instructions::Get(name) => { if let Some(Some(val)) = const_map.get(name) { bytecode[i] = Instructions::Push(val.clone()); + changed = true; } } _ => { @@ -72,4 +74,5 @@ pub fn constant_propagation(bytecode: &mut [Instructions]) { } } } + changed } diff --git a/rust/src/modules/gazle/eliminate_dead_loops.rs b/rust/src/modules/gazle/eliminate_dead_loops.rs index bae91d46..f4fa6257 100644 --- a/rust/src/modules/gazle/eliminate_dead_loops.rs +++ b/rust/src/modules/gazle/eliminate_dead_loops.rs @@ -12,8 +12,9 @@ use crate::modules::gazle::utils::is_pure_loop::is_pure_loop; use crate::types::instructions::Instructions; use ahash::AHashMap; #[inline(always)] -pub fn eliminate_dead_loops(bytecode: Vec) -> Vec { +pub fn eliminate_dead_loops(bytecode: Vec) -> (Vec, bool) { let mut out: Vec = Vec::new(); + let mut changed = false; let mut index_map: AHashMap = AHashMap::new(); for (i, inst) in bytecode.iter().enumerate() { let mut is_eliminated = false; @@ -26,6 +27,7 @@ pub fn eliminate_dead_loops(bytecode: Vec) -> Vec { { out.truncate(out_start_index); is_eliminated = true; + changed = true; } } _ => {} @@ -36,5 +38,5 @@ pub fn eliminate_dead_loops(bytecode: Vec) -> Vec { index_map.insert(i, out.len()); out.push(inst.clone()); } - out + (out, changed) } diff --git a/rust/src/modules/gazle/eliminate_dead_stores.rs b/rust/src/modules/gazle/eliminate_dead_stores.rs index 7a355914..7b7e8379 100644 --- a/rust/src/modules/gazle/eliminate_dead_stores.rs +++ b/rust/src/modules/gazle/eliminate_dead_stores.rs @@ -16,9 +16,11 @@ enum Demand { Drop, } #[inline] -pub fn eliminate_dead_stores(bytecode: &mut [Instructions], usage: &Usage) { +pub fn eliminate_dead_stores(bytecode: &mut [Instructions], usage: &Usage) -> bool { let mut stack_demands: Vec = Vec::new(); + let mut changed = false; for i in (0..bytecode.len()).rev() { + let previous = bytecode[i].clone(); let inst = &mut bytecode[i]; match inst { Instructions::Push(_) @@ -49,6 +51,7 @@ pub fn eliminate_dead_stores(bytecode: &mut [Instructions], usage: &Usage) { Instructions::Val(arg) => { if !usage.read.contains(arg.as_str()) { *inst = Instructions::Nop; + changed = true; continue; } } @@ -59,6 +62,7 @@ pub fn eliminate_dead_stores(bytecode: &mut [Instructions], usage: &Usage) { if !usage.read.contains(arg.as_str()) { stack_demands.push(Demand::Drop); *inst = Instructions::Nop; + changed = true; continue; } stack_demands.push(Demand::Keep); @@ -224,5 +228,7 @@ pub fn eliminate_dead_stores(bytecode: &mut [Instructions], usage: &Usage) { Instructions::Jump(_) | Instructions::Stop => {} _ => {} } + changed |= bytecode[i] != previous; } + changed } diff --git a/rust/src/modules/gazle/eliminate_redundant_loads.rs b/rust/src/modules/gazle/eliminate_redundant_loads.rs index ccb41c2f..391da081 100644 --- a/rust/src/modules/gazle/eliminate_redundant_loads.rs +++ b/rust/src/modules/gazle/eliminate_redundant_loads.rs @@ -10,12 +10,13 @@ use crate::types::instructions::Instructions; #[inline(always)] -pub fn eliminate_redundant_loads(bytecode: Vec) -> Vec { +pub fn eliminate_redundant_loads(bytecode: Vec) -> (Vec, bool) { if bytecode.is_empty() { - return bytecode; + return (bytecode, false); } let mut optimized = Vec::with_capacity(bytecode.len()); let mut last_origin: Option = None; + let mut changed = false; for instr in bytecode { let is_redundant = if let Some(last) = optimized.last() { let effective_last = if matches!(last, Instructions::Dup) { @@ -36,10 +37,11 @@ pub fn eliminate_redundant_loads(bytecode: Vec) -> Vec bool { let mut i = 0; + let mut changed = false; while i < bytecode.len() { if let Some(Instructions::MakeArray(count)) = bytecode.get(i) { let count = *count as usize; @@ -65,6 +66,7 @@ pub fn fold_constants(bytecode: &mut [Instructions]) { } } if all_const { + changed = true; bytecode[i - count] = Instructions::PushArray(Arc::new(vals)); for instr in bytecode.iter_mut().take(i + 1).skip(i - count + 1) { *instr = Instructions::Nop; @@ -90,6 +92,7 @@ pub fn fold_constants(bytecode: &mut [Instructions]) { .iter() .all(|pair| matches!(&pair[0], Value::String(_))); if operands.len() == operand_count && valid_keys { + changed = true; let mut object = AHashMap::with_capacity(*count as usize); for pair in operands.as_chunks::<2>().0.iter().rev() { let Value::String(key) = &pair[0] else { @@ -154,6 +157,7 @@ pub fn fold_constants(bytecode: &mut [Instructions]) { _ => None, }; if let Some(res_val) = result { + changed = true; bytecode[i] = value_to_instruction(res_val); bytecode[i + 1] = Instructions::Nop; bytecode[i + 2] = Instructions::Nop; @@ -206,6 +210,7 @@ pub fn fold_constants(bytecode: &mut [Instructions]) { _ => None, }; if let Some(res_val) = result { + changed = true; bytecode[i] = value_to_instruction(res_val); bytecode[i + 1] = Instructions::Nop; bytecode[i + 2] = Instructions::Nop; @@ -215,8 +220,10 @@ pub fn fold_constants(bytecode: &mut [Instructions]) { } i += 1; } + changed } #[cfg(test)] +#[allow(unused_must_use)] mod tests { use super::*; use crate::types::{primitive_types::PrimitiveTypes, value::Value}; diff --git a/rust/src/modules/gazle/fold_conversions.rs b/rust/src/modules/gazle/fold_conversions.rs index c4cc37fc..4b48e44a 100644 --- a/rust/src/modules/gazle/fold_conversions.rs +++ b/rust/src/modules/gazle/fold_conversions.rs @@ -20,7 +20,7 @@ use crate::instructions::{ cos_func::cos_values, neg_func::neg_values, sin_func::sin_values, tan_func::tan_values, }, exp_func::exp_values, - logarithm::ln_func::ln_values, + logarithm::{ln_func::ln_values, log2_func::log2_values, log10_func::log10_values}, root::{cbrt_func::cbrt_values, sqrt_func::sqrt_values}, trigonometry::{ hyperbolic::{ @@ -62,8 +62,9 @@ use crate::modules::gazle::utils::{ }; use crate::types::instructions::Instructions; #[inline(always)] -pub fn fold_conversions(bytecode: &mut [Instructions]) { +pub fn fold_conversions(bytecode: &mut [Instructions]) -> bool { let mut i = 0; + let mut changed = false; while i < bytecode.len().saturating_sub(1) { let instr1 = &bytecode[i]; let instr2 = &bytecode[i + 1]; @@ -114,11 +115,14 @@ pub fn fold_conversions(bytecode: &mut [Instructions]) { Instructions::Lnv(t) => lnv_values(val, *t, i).ok(), Instructions::Exp(t) => exp_values(val, *t, i).ok(), Instructions::Expv(t) => expv_values(val, *t, i).ok(), + Instructions::Log2(t) => log2_values(val, *t, i).ok(), + Instructions::Log10(t) => log10_values(val, *t, i).ok(), Instructions::Log2v(t) => log2v_values(val, *t, i).ok(), Instructions::Log10v(t) => log10v_values(val, *t, i).ok(), _ => None, }; if let Some(res_val) = folded { + changed = true; bytecode[i] = value_to_instruction(res_val); bytecode[i + 1] = Instructions::Nop; i += 2; @@ -127,13 +131,60 @@ pub fn fold_conversions(bytecode: &mut [Instructions]) { } i += 1; } + changed } #[cfg(test)] +#[allow(unused_must_use)] mod tests { use super::*; use crate::types::{primitive_types::PrimitiveTypes, value::Value}; use std::sync::Arc; #[test] + fn folds_scalar_logarithms() { + let mut log2 = vec![ + Instructions::PushFloat32(8.0), + Instructions::Log2(PrimitiveTypes::Flt), + ]; + assert!(fold_conversions(&mut log2)); + assert_eq!( + log2, + vec![Instructions::PushFloat32(3.0), Instructions::Nop] + ); + let mut log10 = vec![ + Instructions::PushFloat32(100.0), + Instructions::Log10(PrimitiveTypes::Flt), + ]; + assert!(fold_conversions(&mut log10)); + assert_eq!( + log10, + vec![Instructions::PushFloat32(2.0), Instructions::Nop] + ); + } + #[test] + fn leaves_invalid_scalar_logarithms_for_runtime_error() { + for operation in [ + Instructions::Log2(PrimitiveTypes::Flt), + Instructions::Log10(PrimitiveTypes::Flt), + ] { + let mut bytecode = vec![ + Instructions::PushString("invalid".into()), + operation, + Instructions::Stop, + ]; + let expected = bytecode.clone(); + assert!(!fold_conversions(&mut bytecode)); + assert_eq!(bytecode, expected); + assert!(matches!( + crate::vm::execute::execute(bytecode, &mut None, None), + Err(crate::modules::vmerror::VMError::TypeMismatch { + ip: 1, + expected: "Float", + found: "String" + }) + )); + } + } + #[test] fn folds_valid_unary_float_vectors_and_retains_invalid_ones() { let operations = [ Instructions::Lnv(PrimitiveTypes::Flt), diff --git a/rust/src/modules/gazle/jump_threading.rs b/rust/src/modules/gazle/jump_threading.rs index ecc93b9c..9e008524 100644 --- a/rust/src/modules/gazle/jump_threading.rs +++ b/rust/src/modules/gazle/jump_threading.rs @@ -10,8 +10,9 @@ use crate::types::instructions::Instructions; #[inline(always)] -pub fn jump_threading(bytecode: &mut [Instructions]) { +pub fn jump_threading(bytecode: &mut [Instructions]) -> bool { let mut changed = true; + let mut pass_changed = false; while changed { changed = false; for i in 0..bytecode.len() { @@ -21,6 +22,7 @@ pub fn jump_threading(bytecode: &mut [Instructions]) { { bytecode[i] = Instructions::Jump(final_target); changed = true; + pass_changed = true; } } else if let Instructions::IfFalse(target) = bytecode[i] && let Some(final_target) = find_final_target(bytecode, target) @@ -28,9 +30,11 @@ pub fn jump_threading(bytecode: &mut [Instructions]) { { bytecode[i] = Instructions::IfFalse(final_target); changed = true; + pass_changed = true; } } } + pass_changed } fn find_final_target(bytecode: &[Instructions], mut target: usize) -> Option { let mut visited = std::collections::HashSet::new(); diff --git a/rust/src/modules/gazle/optimize_bytecode.rs b/rust/src/modules/gazle/optimize_bytecode.rs index 15499428..dcef10f5 100644 --- a/rust/src/modules/gazle/optimize_bytecode.rs +++ b/rust/src/modules/gazle/optimize_bytecode.rs @@ -32,10 +32,9 @@ pub fn optimize_bytecode( break; } let len_before_pass = bytecode.len(); - let prev_bytes_debug = bytecode.clone(); - run_pass(pass_id, &mut bytecode); + let pass_changed = run_pass(pass_id, &mut bytecode); let len_after_pass = bytecode.len(); - if bytecode != prev_bytes_debug { + if pass_changed { let reduction = (len_before_pass as i32) - (len_after_pass as i32); let reward = if reduction > 0 { reduction * 2 } else { 1 }; pass_weights[pass_id] += reward; @@ -93,9 +92,23 @@ pub fn optimize_bytecode( #[cfg(test)] mod tests { use super::*; + use crate::modules::gazle::utils::run_pass::run_pass; use crate::types::{primitive_types::PrimitiveTypes, value::Value}; use smol_str::SmolStr; #[test] + fn terminates_when_no_pass_reports_a_mutation() { + let bytecode = vec![Instructions::Stop]; + for pass_id in 0..9 { + let mut pass_bytecode = bytecode.clone(); + assert!(!run_pass(pass_id, &mut pass_bytecode)); + assert_eq!(pass_bytecode, bytecode); + } + assert_eq!( + optimize_bytecode(bytecode.clone(), TimeBudgetType::Cheap), + bytecode + ); + } + #[test] fn folds_constant_add_and_concat_to_correct_result() { let bytecode = vec![ Instructions::Val(SmolStr::new("x")), diff --git a/rust/src/modules/gazle/specialized_instructions.rs b/rust/src/modules/gazle/specialized_instructions.rs index 400ac94b..34d990dd 100644 --- a/rust/src/modules/gazle/specialized_instructions.rs +++ b/rust/src/modules/gazle/specialized_instructions.rs @@ -9,7 +9,8 @@ */ use crate::types::{instructions::Instructions, value::Value}; -pub fn specialized_instructions(bytecode: &mut [Instructions]) { +pub fn specialized_instructions(bytecode: &mut [Instructions]) -> bool { + let mut changed = false; for instr in bytecode.iter_mut() { if let Instructions::Push(val) = instr { let replacement = match val { @@ -31,7 +32,9 @@ pub fn specialized_instructions(bytecode: &mut [Instructions]) { }; if let Some(new_instr) = replacement { *instr = new_instr; + changed = true; } } } + changed } diff --git a/rust/src/modules/gazle/strength_reduction.rs b/rust/src/modules/gazle/strength_reduction.rs index 4293e0b3..d49317fd 100644 --- a/rust/src/modules/gazle/strength_reduction.rs +++ b/rust/src/modules/gazle/strength_reduction.rs @@ -10,17 +10,20 @@ use crate::types::{instructions::Instructions, value::Value}; #[inline(always)] -pub fn strength_reduction(bytecode: &mut Vec) { +pub fn strength_reduction(bytecode: &mut Vec) -> bool { let mut i = 0; + let mut changed = false; while i < bytecode.len().saturating_sub(1) { let pair = (bytecode[i].clone(), bytecode[i + 1].clone()); match pair { (Instructions::Push(v), Instructions::Mul(_)) if is_zero(&v) => { + changed = true; bytecode[i] = Instructions::Push(v); bytecode[i + 1] = Instructions::Nop; i += 2; } (Instructions::Push(v), Instructions::Mul(_)) if is_one(&v) => { + changed = true; bytecode[i] = Instructions::Nop; bytecode[i + 1] = Instructions::Nop; i += 2; @@ -28,6 +31,7 @@ pub fn strength_reduction(bytecode: &mut Vec) { (Instructions::Push(Value::Int32(n)), Instructions::Mul(t)) if n > 0 && (n & (n - 1)) == 0 => { + changed = true; bytecode[i] = Instructions::Push(Value::Int32(n.trailing_zeros() as i32)); bytecode[i + 1] = Instructions::Shl(t); i += 2; @@ -35,16 +39,19 @@ pub fn strength_reduction(bytecode: &mut Vec) { (Instructions::Push(Value::Int64(n)), Instructions::Mul(t)) if n > 0 && (n & (n - 1)) == 0 => { + changed = true; bytecode[i] = Instructions::Push(Value::Int64(n.trailing_zeros() as i64)); bytecode[i + 1] = Instructions::Shl(t); i += 2; } (Instructions::Push(v), Instructions::Div(_)) if is_one(&v) => { + changed = true; bytecode[i] = Instructions::Nop; bytecode[i + 1] = Instructions::Nop; i += 2; } (Instructions::Push(v), Instructions::Mod(_)) if is_one(&v) => { + changed = true; bytecode[i] = Instructions::Push(make_zero_like(&v)); bytecode[i + 1] = Instructions::Nop; i += 2; @@ -52,6 +59,7 @@ pub fn strength_reduction(bytecode: &mut Vec) { (Instructions::Push(Value::Int32(n)), Instructions::Mod(_)) if n > 0 && (n & (n - 1)) == 0 => { + changed = true; bytecode[i] = Instructions::Push(Value::Int32(n - 1)); bytecode[i + 1] = Instructions::And; i += 2; @@ -59,6 +67,7 @@ pub fn strength_reduction(bytecode: &mut Vec) { (Instructions::Push(Value::Int64(n)), Instructions::Mod(_)) if n > 0 && (n & (n - 1)) == 0 => { + changed = true; bytecode[i] = Instructions::Push(Value::Int64(n - 1)); bytecode[i + 1] = Instructions::And; i += 2; @@ -67,6 +76,7 @@ pub fn strength_reduction(bytecode: &mut Vec) { | (Instructions::Push(v), Instructions::Sub(_)) if is_zero(&v) => { + changed = true; bytecode[i] = Instructions::Nop; bytecode[i + 1] = Instructions::Nop; i += 2; @@ -75,6 +85,7 @@ pub fn strength_reduction(bytecode: &mut Vec) { } } bytecode.retain(|instr| !matches!(instr, Instructions::Nop)); + changed } fn is_zero(v: &Value) -> bool { match v { diff --git a/rust/src/modules/gazle/utils/run_pass.rs b/rust/src/modules/gazle/utils/run_pass.rs index ec606a86..d1da22b4 100644 --- a/rust/src/modules/gazle/utils/run_pass.rs +++ b/rust/src/modules/gazle/utils/run_pass.rs @@ -17,7 +17,7 @@ use crate::modules::gazle::{ }; use crate::types::instructions::Instructions; #[inline(always)] -pub fn run_pass(pass_id: usize, bytecode: &mut Vec) { +pub fn run_pass(pass_id: usize, bytecode: &mut Vec) -> bool { match pass_id { 0 => specialized_instructions(bytecode), 1 => strength_reduction(bytecode), @@ -27,16 +27,20 @@ pub fn run_pass(pass_id: usize, bytecode: &mut Vec) { 5 => constant_propagation(bytecode), 6 => { let taken = std::mem::take(bytecode); - *bytecode = eliminate_dead_loops(taken); + let (optimized, changed) = eliminate_dead_loops(taken); + *bytecode = optimized; + changed } 7 => { let taken = std::mem::take(bytecode); - *bytecode = eliminate_redundant_loads(taken); + let (optimized, changed) = eliminate_redundant_loads(taken); + *bytecode = optimized; + changed } 8 => { let usage = analyze_usage(bytecode); - eliminate_dead_stores(bytecode, &usage); + eliminate_dead_stores(bytecode, &usage) } - _ => {} + _ => false, } }