Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [
"gcrecomp-cli",
"gcrecomp-ui",
"gcrecomp-runtime",
"gcrecomp-lua",
"gcrecomp-web",
"game",
]
resolver = "2"
Expand All @@ -13,6 +15,7 @@ opt-level = 3
lto = true
codegen-units = 1
strip = true
panic = "abort"

[workspace.package]
version = "0.0.1-alpha"
Expand Down Expand Up @@ -54,3 +57,15 @@ minifb = "0.25"
smallvec = "1.13"
bitvec = "1.0"

# Lua scripting
mlua = { version = "0.10", features = ["lua54", "vendored", "serialize", "send"] }

# Verification
crc32fast = "1.4"
sha2 = "0.10"

# Web server
axum = { version = "0.7", features = ["multipart"] }
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.5", features = ["fs", "cors"] }

1 change: 1 addition & 0 deletions game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ path = "src/main.rs"
[dependencies]
gcrecomp-runtime = { path = "../gcrecomp-runtime" }
gcrecomp-ui = { path = "../gcrecomp-ui" }
gcrecomp-lua = { path = "../gcrecomp-lua" }
log = { workspace = true }

18 changes: 18 additions & 0 deletions game/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
// Game entry point
fn main() {
println!("Game entry point - recompiled code will be integrated here");

// Initialize Lua scripting engine
match gcrecomp_lua::engine::LuaEngine::new() {
Ok(engine) => {
println!("Lua scripting engine initialized");

// Load game initialization scripts
let init_script = std::path::Path::new("lua/game/init.lua");
if init_script.exists() {
if let Err(e) = engine.execute_file(init_script) {
eprintln!("Failed to load game scripts: {}", e);
}
}
}
Err(e) => {
eprintln!("Failed to initialize Lua engine: {}", e);
}
}
}
223 changes: 141 additions & 82 deletions gcrecomp-core/src/recompiler/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
//! # Optimization Passes
//! - **Constant Folding**: Evaluate constant expressions at compile time
//! - **Dead Code Elimination**: Remove unused instructions
//! - **Register Allocation**: Optimize register usage (placeholder for future implementation)
//!
//! # Memory Optimizations
//! - Pre-allocates result vectors with estimated capacity
//! - Uses efficient data structures for analysis
//! - **Constant Propagation**: Track li/addi constant loads through register chains
//! - **Function-level DCE**: Remove unreachable functions using call graph analysis

use crate::recompiler::decoder::DecodedInstruction;
use std::collections::HashMap;
use crate::recompiler::decoder::{DecodedInstruction, InstructionType, Operand};
use std::collections::{HashMap, HashSet};

/// Optimizer for PowerPC instructions.
///
Expand All @@ -23,122 +20,184 @@ pub struct Optimizer {
constant_folding: bool,
/// Enable dead code elimination
dead_code_elimination: bool,
/// Enable register allocation (placeholder)
register_allocation: bool,
}

impl Optimizer {
/// Create a new optimizer with all optimizations enabled.
///
/// # Returns
/// `Optimizer` - New optimizer instance
///
/// # Examples
/// ```rust
/// let optimizer = Optimizer::new();
/// ```
#[inline] // Constructor - simple, may be inlined
pub fn new() -> Self {
Self {
constant_folding: true,
dead_code_elimination: true,
register_allocation: true,
}
}

/// Optimize a sequence of instructions.
///
/// # Algorithm
/// Applies optimization passes in order:
/// 1. Constant folding
/// 2. Dead code elimination
///
/// # Arguments
/// * `instructions` - Sequence of decoded PowerPC instructions
///
/// # Returns
/// `Vec<DecodedInstruction>` - Optimized instruction sequence
///
/// # Examples
/// ```rust
/// let optimized = optimizer.optimize(&instructions);
/// ```
#[inline] // May be called frequently
pub fn optimize(&self, instructions: &[DecodedInstruction]) -> Vec<DecodedInstruction> {
let mut optimized: Vec<DecodedInstruction> = instructions.to_vec();

if self.constant_folding {
optimized = self.fold_constants(&optimized);
}

if self.dead_code_elimination {
optimized = self.eliminate_dead_code(&optimized);
}

optimized
}

/// Constant folding optimization pass.
///
/// # Algorithm
/// Tracks constant values in registers and evaluates constant expressions
/// at compile time, replacing them with immediate values.
/// Constant folding and propagation pass.
///
/// # Arguments
/// * `instructions` - Instruction sequence to optimize
///
/// # Returns
/// `Vec<DecodedInstruction>` - Optimized instruction sequence
#[inline] // Optimization pass - may be inlined
/// Tracks constant values loaded by `li` (opcode 14 with rA=0) and `addi`
/// patterns, propagating them through register chains.
fn fold_constants(&self, instructions: &[DecodedInstruction]) -> Vec<DecodedInstruction> {
let mut result: Vec<DecodedInstruction> = Vec::with_capacity(instructions.len());
let mut constants: HashMap<u8, Option<u32>> = HashMap::new();
let mut constants: HashMap<u8, u32> = HashMap::new();

for inst in instructions.iter() {
// Track constant values in registers
// If we can determine a register always holds a constant, we can fold it
let optimized: DecodedInstruction = inst.clone(); // For now, just pass through
result.push(optimized);
// Track li/addi constant loads (opcode 14 = addi; li is addi rD,0,imm)
if inst.instruction.opcode == 14 && inst.instruction.operands.len() >= 3 {
if let (Operand::Register(rd), Operand::Register(ra), Operand::Immediate(imm)) = (
&inst.instruction.operands[0],
&inst.instruction.operands[1],
&inst.instruction.operands[2],
) {
if *ra == 0 {
// li rD, imm — rD = sign_extend(imm)
constants.insert(*rd, *imm as i32 as u32);
} else if let Some(&base) = constants.get(ra) {
// addi rD, rA, imm where rA is known constant
constants.insert(*rd, base.wrapping_add(*imm as i32 as u32));
} else {
constants.remove(rd);
}
}
}
// Track lis (opcode 15 = addis; lis is addis rD,0,imm)
else if inst.instruction.opcode == 15 && inst.instruction.operands.len() >= 3 {
if let (Operand::Register(rd), Operand::Register(ra), Operand::Immediate(imm)) = (
&inst.instruction.operands[0],
&inst.instruction.operands[1],
&inst.instruction.operands[2],
) {
if *ra == 0 {
constants.insert(*rd, (*imm as u32) << 16);
} else {
constants.remove(rd);
}
}
}
// Invalidate register on any other write
else if let Some(Operand::Register(rd)) = inst.instruction.operands.first() {
if matches!(
inst.instruction.instruction_type,
InstructionType::Arithmetic
| InstructionType::Load
| InstructionType::Move
| InstructionType::Shift
| InstructionType::Rotate
) {
constants.remove(rd);
}
}
// Branches invalidate all tracked constants (control flow merge)
if matches!(inst.instruction.instruction_type, InstructionType::Branch) {
constants.clear();
}

result.push(inst.clone());
}

result
}

/// Dead code elimination optimization pass.
///
/// # Algorithm
/// Removes instructions that write to registers that are never read.
/// Uses reverse pass to identify unused register definitions.
/// Dead code elimination pass.
///
/// # Arguments
/// * `instructions` - Instruction sequence to optimize
///
/// # Returns
/// `Vec<DecodedInstruction>` - Optimized instruction sequence
#[inline] // Optimization pass - may be inlined
/// Removes instructions that write to registers never subsequently read.
fn eliminate_dead_code(&self, instructions: &[DecodedInstruction]) -> Vec<DecodedInstruction> {
// Simple dead code elimination: remove writes to registers that are never read
let mut result: Vec<DecodedInstruction> = Vec::with_capacity(instructions.len());
let mut used_registers: std::collections::HashSet<u8> = std::collections::HashSet::new();

// First pass: find all used registers (reverse pass)
for inst in instructions.iter().rev() {
// Check if this instruction uses any registers
// If a register is written but never read after, it's dead
// (Simplified implementation - would use proper def-use analysis)

// Collect used registers in a backward pass
let mut used_after: HashSet<u8> = HashSet::new();
// Assume all registers may be live at function exit
for r in 0..32u8 {
used_after.insert(r);
}

// Second pass: keep only instructions that produce used values
for inst in instructions.iter() {
result.push(inst.clone());

let mut keep = vec![true; instructions.len()];

for (i, inst) in instructions.iter().enumerate().rev() {
// Branch/system/store instructions always kept (side effects)
if matches!(
inst.instruction.instruction_type,
InstructionType::Branch
| InstructionType::System
| InstructionType::Store
| InstructionType::Compare
) {
// Mark all source registers as used
for op in &inst.instruction.operands {
if let Operand::Register(r) = op {
used_after.insert(*r);
}
}
continue;
}

// For instructions that write a register: check if it's read later
if let Some(Operand::Register(rd)) = inst.instruction.operands.first() {
if !used_after.contains(rd) {
keep[i] = false;
continue;
}
// Remove destination from used set, add sources
used_after.remove(rd);
}

for op in inst.instruction.operands.iter().skip(1) {
if let Operand::Register(r) = op {
used_after.insert(*r);
}
}
}


for (i, inst) in instructions.iter().enumerate() {
if keep[i] {
result.push(inst.clone());
}
}

result
}

/// Function-level dead code elimination using call graph.
///
/// Given a set of function addresses and their call targets, returns the set
/// of reachable function addresses from the given entry points.
pub fn reachable_functions(
entry_points: &[u32],
call_graph: &HashMap<u32, Vec<u32>>,
) -> HashSet<u32> {
let mut reachable = HashSet::new();
let mut worklist: Vec<u32> = entry_points.to_vec();

while let Some(addr) = worklist.pop() {
if reachable.insert(addr) {
if let Some(callees) = call_graph.get(&addr) {
for &callee in callees {
if !reachable.contains(&callee) {
worklist.push(callee);
}
}
}
}
}

reachable
}
}

impl Default for Optimizer {
#[inline] // Simple default implementation
fn default() -> Self {
Self::new()
}
Expand Down
Loading
Loading