phprs 0.1.7

A PHP interpreter with build/package manager written in Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
//! Function Call Optimizations and Inlining
//!
//! Advanced function call optimizations to outperform PHP 8

use crate::engine::types::PhpResult;
use crate::vm::execute_data::{ExecResult, ExecuteData};
use crate::vm::opcodes::{Op, OpArray, Opcode};
use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};

/// Function call optimization level
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationLevel {
    None = 0,
    Basic = 1,      // Basic call optimizations
    Aggressive = 2, // Aggressive inlining
}

/// Function metadata for optimization
#[derive(Debug, Clone)]
pub struct FunctionMetadata {
    pub name: String,
    pub param_count: usize,
    pub op_count: usize,
    pub complexity: Complexity,
    pub call_frequency: u64,
    pub inline_candidates: Vec<String>,
    pub optimization_level: OptimizationLevel,
}

/// Function complexity analysis
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Complexity {
    Trivial,     // Simple arithmetic/logic
    Simple,      // Basic operations
    Moderate,    // Some loops/conditions
    Complex,     // Complex control flow
    VeryComplex, // Highly complex
}

/// Inlined function representation
#[derive(Debug, Clone)]
pub struct InlinedFunction {
    pub metadata: FunctionMetadata,
    pub optimized_ops: Vec<Op>,
    pub inline_threshold: f32,
}

/// Function call optimizer
#[derive(Debug)]
pub struct FunctionOptimizer {
    function_cache: HashMap<String, FunctionMetadata>,
    inline_cache: HashMap<String, InlinedFunction>,
    call_stats: HashMap<String, CallStats>,
    optimization_stats: OptimizationStats,
}

#[derive(Debug, Default)]
pub struct CallStats {
    pub call_count: u64,
    pub total_time_ns: u64,
    pub average_time_ns: u64,
}

#[derive(Debug, Default)]
pub struct OptimizationStats {
    pub functions_optimized: u64,
    pub functions_inlined: u64,
    pub calls_saved: u64,
    pub total_time_saved_ns: u64,
}

impl FunctionOptimizer {
    pub fn new() -> Self {
        Self {
            function_cache: HashMap::new(),
            inline_cache: HashMap::new(),
            call_stats: HashMap::new(),
            optimization_stats: OptimizationStats::default(),
        }
    }

    /// Analyze function for optimization opportunities
    pub fn analyze_function(&mut self, name: &str, op_array: &OpArray) -> FunctionMetadata {
        let metadata = if let Some(cached) = self.function_cache.get(name) {
            cached.clone()
        } else {
            let metadata = self.perform_analysis(name, op_array);
            self.function_cache
                .insert(name.to_string(), metadata.clone());
            metadata
        };

        // Update optimization level based on call frequency
        let updated_metadata = self.update_optimization_level(metadata);
        self.function_cache
            .insert(name.to_string(), updated_metadata.clone());
        updated_metadata
    }

    /// Perform detailed function analysis
    fn perform_analysis(&self, name: &str, op_array: &OpArray) -> FunctionMetadata {
        let param_count = op_array.vars.len();
        let op_count = op_array.ops.len();
        let complexity = self.analyze_complexity(&op_array.ops);
        let inline_candidates = self.find_inline_candidates(&op_array.ops);

        FunctionMetadata {
            name: name.to_string(),
            param_count,
            op_count,
            complexity,
            call_frequency: 0,
            inline_candidates,
            optimization_level: OptimizationLevel::None,
        }
    }

    /// Analyze function complexity
    fn analyze_complexity(&self, ops: &[Op]) -> Complexity {
        let op_count = ops.len();
        let mut loop_count = 0;
        let mut _branch_count = 0;
        let mut call_count = 0;
        let mut _complex_ops = 0;

        for op in ops {
            match op.opcode {
                Opcode::Jmp => {
                    if op.extended_value < ops.len() as u32 {
                        loop_count += 1;
                    } else {
                        _branch_count += 1;
                    }
                }
                Opcode::JmpZ | Opcode::JmpNZ => _branch_count += 1,
                Opcode::InitFCall | Opcode::DoFCall => call_count += 1,
                Opcode::Include | Opcode::NewObj | Opcode::DoMethodCall => _complex_ops += 1,
                _ => {}
            }
        }

        // Determine complexity based on metrics
        if op_count <= 5 && loop_count == 0 && call_count == 0 {
            Complexity::Trivial
        } else if op_count <= 20 && loop_count <= 1 && call_count <= 2 {
            Complexity::Simple
        } else if op_count <= 50 && loop_count <= 3 && call_count <= 5 {
            Complexity::Moderate
        } else if op_count <= 100 && loop_count <= 5 && call_count <= 10 {
            Complexity::Complex
        } else {
            Complexity::VeryComplex
        }
    }

    /// Find functions that can be inlined
    fn find_inline_candidates(&self, ops: &[Op]) -> Vec<String> {
        let mut candidates = Vec::new();

        for i in 0..ops.len() {
            if ops[i].opcode == Opcode::InitFCall {
                // Look for function name in subsequent operations
                if i + 1 < ops.len() && ops[i + 1].opcode == Opcode::DoFCall {
                    if let crate::engine::types::PhpValue::String(ref func_name) =
                        ops[i + 1].op1.value
                    {
                        candidates.push(func_name.as_str().to_string());
                    }
                }
            }
        }

        candidates
    }

    /// Update optimization level based on call frequency
    fn update_optimization_level(&self, mut metadata: FunctionMetadata) -> FunctionMetadata {
        let call_stats = self.call_stats.get(&metadata.name);
        let call_frequency = call_stats.map(|s| s.call_count).unwrap_or(0);
        metadata.call_frequency = call_frequency;

        // Determine optimization level
        metadata.optimization_level = match (call_frequency, metadata.complexity, metadata.op_count)
        {
            (freq, _, _) if freq > 1000 => OptimizationLevel::Aggressive,
            (freq, Complexity::Trivial, _) if freq > 100 => OptimizationLevel::Aggressive,
            (freq, Complexity::Simple, _) if freq > 500 => OptimizationLevel::Aggressive,
            (freq, Complexity::Simple, _) if freq > 50 => OptimizationLevel::Basic,
            (freq, Complexity::Moderate, _) if freq > 200 => OptimizationLevel::Basic,
            (_, _, _) => OptimizationLevel::None,
        };

        metadata
    }

    /// Check if function should be inlined
    pub fn should_inline(&mut self, name: &str) -> bool {
        if let Some(metadata) = self.function_cache.get(name) {
            match metadata.optimization_level {
                OptimizationLevel::Aggressive => {
                    matches!(
                        metadata.complexity,
                        Complexity::Trivial | Complexity::Simple
                    )
                }
                OptimizationLevel::Basic => metadata.complexity == Complexity::Trivial,
                OptimizationLevel::None => false,
            }
        } else {
            false
        }
    }

    /// Generate inlined version of function
    pub fn generate_inlined_function(
        &mut self,
        name: &str,
        op_array: &OpArray,
    ) -> Result<InlinedFunction, String> {
        let metadata = self.analyze_function(name, op_array);

        if !self.should_inline(name) {
            return Err(format!("Function {} is not a candidate for inlining", name));
        }

        let optimized_ops = self.optimize_for_inlining(&op_array.ops, &metadata)?;
        let inline_threshold = self.calculate_inline_threshold(&metadata);

        let inlined = InlinedFunction {
            metadata,
            optimized_ops,
            inline_threshold,
        };

        self.inline_cache.insert(name.to_string(), inlined.clone());
        self.optimization_stats.functions_inlined += 1;

        Ok(inlined)
    }

    /// Optimize opcodes for inlining
    fn optimize_for_inlining(
        &mut self,
        ops: &[Op],
        _metadata: &FunctionMetadata,
    ) -> Result<Vec<Op>, String> {
        let mut optimized = Vec::with_capacity(ops.len());

        for op in ops {
            match op.opcode {
                Opcode::InitFCall | Opcode::DoFCall => {
                    // Try to inline nested function calls
                    if let crate::engine::types::PhpValue::String(ref func_name) = op.op1.value {
                        if self.should_inline(func_name.as_str()) {
                            // Skip the function call - it will be inlined
                            continue;
                        }
                    }
                    // Keep the call if it can't be inlined
                    optimized.push(op.clone());
                }
                Opcode::Return => {
                    // Convert return to assignment to result variable
                    let return_op = Op::new(
                        Opcode::Assign,
                        op.op1.clone(),
                        op.op2.clone(),
                        op.result.clone(),
                        op.extended_value,
                    );
                    optimized.push(return_op);
                }
                _ => {
                    optimized.push(op.clone());
                }
            }
        }

        Ok(optimized)
    }

    /// Calculate inline threshold for function
    fn calculate_inline_threshold(&self, metadata: &FunctionMetadata) -> f32 {
        match metadata.complexity {
            Complexity::Trivial => 0.1,
            Complexity::Simple => 0.5,
            Complexity::Moderate => 2.0,
            Complexity::Complex => 5.0,
            Complexity::VeryComplex => 10.0,
        }
    }

    /// Execute function with optimization
    pub fn execute_optimized(
        &mut self,
        name: &str,
        execute_data: &mut ExecuteData,
        op_array: &OpArray,
    ) -> Result<PhpResult, String> {
        let start_time = std::time::Instant::now();

        // Check if we have an inlined version
        let inlined = self.inline_cache.get(name).cloned();

        // Check if we should inline this function
        let should_inline = self.should_inline(name) && inlined.is_none();

        let result = if should_inline {
            // Generate inlined version
            match self.generate_inlined_function(name, op_array) {
                Ok(inlined) => {
                    let result = self.execute_inlined_function(&inlined, execute_data)?;
                    self.optimization_stats.calls_saved += 1;
                    result
                }
                Err(e) => return Err(e),
            }
        } else if let Some(inlined) = inlined {
            // Use existing inlined version
            self.execute_inlined_function(&inlined, execute_data)?
        } else {
            // Execute normally
            self.execute_normal_function(execute_data, op_array)?
        };

        // Update call statistics
        let stats = self.call_stats.entry(name.to_string()).or_default();
        stats.call_count += 1;

        // Update statistics
        let elapsed = start_time.elapsed().as_nanos() as u64;
        stats.total_time_ns += elapsed;
        stats.average_time_ns = stats.total_time_ns / stats.call_count;

        Ok(result)
    }

    /// Execute inlined function
    fn execute_inlined_function(
        &mut self,
        inlined: &InlinedFunction,
        execute_data: &mut ExecuteData,
    ) -> Result<PhpResult, String> {
        // Execute optimized opcodes directly
        for op in &inlined.optimized_ops {
            match crate::vm::dispatch_handlers::dispatch_opcode(op, execute_data)? {
                ExecResult::Continue => continue,
                ExecResult::Jump(_) => {
                    // Handle jumps in inlined code
                    continue;
                }
                ExecResult::Return(_) => {
                    return Ok(PhpResult::Success);
                }
            }
        }

        Ok(PhpResult::Success)
    }

    /// Execute normal function
    fn execute_normal_function(
        &self,
        execute_data: &mut ExecuteData,
        op_array: &OpArray,
    ) -> Result<PhpResult, String> {
        // Use normal execution path
        Ok(crate::vm::execute::execute_ex(execute_data, op_array))
    }

    /// Get optimization statistics
    pub fn get_stats(&self) -> (u64, u64, u64, u64) {
        (
            self.optimization_stats.functions_optimized,
            self.optimization_stats.functions_inlined,
            self.optimization_stats.calls_saved,
            self.optimization_stats.total_time_saved_ns,
        )
    }

    /// Get call statistics for a function
    pub fn get_call_stats(&self, name: &str) -> Option<&CallStats> {
        self.call_stats.get(name)
    }

    /// Clear all caches
    pub fn clear_caches(&mut self) {
        self.function_cache.clear();
        self.inline_cache.clear();
        self.call_stats.clear();
    }
}

impl Default for FunctionOptimizer {
    fn default() -> Self {
        Self::new()
    }
}

/// Global function optimizer instance
static FUNCTION_OPTIMIZER: OnceLock<RwLock<FunctionOptimizer>> = OnceLock::new();

/// Get the global function optimizer instance
pub fn get_function_optimizer() -> &'static RwLock<FunctionOptimizer> {
    FUNCTION_OPTIMIZER.get_or_init(|| RwLock::new(FunctionOptimizer::new()))
}

/// Execute function with optimizations
pub fn execute_function_with_optimization(
    name: &str,
    execute_data: &mut ExecuteData,
    op_array: &OpArray,
) -> Result<PhpResult, String> {
    let optimizer = get_function_optimizer();
    let mut optimizer = optimizer.write().unwrap();
    optimizer.execute_optimized(name, execute_data, op_array)
}

/// Analyze function for optimization opportunities
pub fn analyze_function(name: &str, op_array: &OpArray) -> FunctionMetadata {
    let optimizer = get_function_optimizer();
    let mut optimizer = optimizer.write().unwrap();
    optimizer.analyze_function(name, op_array)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::engine::types::{PhpType, PhpValue, Val};

    #[test]
    fn test_function_analysis() {
        let mut optimizer = FunctionOptimizer::new();

        let mut op_array = OpArray::new("test.php".to_string());
        op_array.ops.push(Op::new(
            Opcode::Add,
            Val::new(PhpValue::Long(1), PhpType::Long),
            Val::new(PhpValue::Long(2), PhpType::Long),
            Val::new(PhpValue::Long(0), PhpType::Long),
            0,
        ));

        let metadata = optimizer.analyze_function("test_function", &op_array);
        assert_eq!(metadata.name, "test_function");
        assert_eq!(metadata.complexity, Complexity::Trivial);
    }

    #[test]
    fn test_inline_generation() {
        let mut optimizer = FunctionOptimizer::new();

        let mut op_array = OpArray::new("test.php".to_string());
        op_array.ops.push(Op::new(
            Opcode::Add,
            Val::new(PhpValue::Long(1), PhpType::Long),
            Val::new(PhpValue::Long(2), PhpType::Long),
            Val::new(PhpValue::Long(0), PhpType::Long),
            0,
        ));

        // Simulate high call frequency
        optimizer.call_stats.insert(
            "test_function".to_string(),
            CallStats {
                call_count: 1000,
                total_time_ns: 1000000,
                average_time_ns: 1000,
            },
        );

        let _metadata = optimizer.analyze_function("test_function", &op_array);
        assert!(optimizer.should_inline("test_function"));

        if let Ok(inlined) = optimizer.generate_inlined_function("test_function", &op_array) {
            assert_eq!(inlined.optimized_ops.len(), 1);
        }
    }

    #[test]
    fn test_complexity_analysis() {
        let optimizer = FunctionOptimizer::new();

        // Test trivial function
        let trivial_ops = vec![Op::new(
            Opcode::Add,
            Val::new(PhpValue::Long(1), PhpType::Long),
            Val::new(PhpValue::Long(2), PhpType::Long),
            Val::new(PhpValue::Long(0), PhpType::Long),
            0,
        )];
        assert_eq!(
            optimizer.analyze_complexity(&trivial_ops),
            Complexity::Trivial
        );

        // Test complex function
        let mut complex_ops = Vec::new();
        for i in 0..150 {
            complex_ops.push(Op::new(
                Opcode::Add,
                Val::new(PhpValue::Long(i as i64), PhpType::Long),
                Val::new(PhpValue::Long((i + 1) as i64), PhpType::Long),
                Val::new(PhpValue::Long(0), PhpType::Long),
                0,
            ));
        }
        assert_eq!(
            optimizer.analyze_complexity(&complex_ops),
            Complexity::VeryComplex
        );
    }
}