gbf_core/decompiler/
function_decompiler.rs#![deny(missing_docs)]
use crate::basic_block::{BasicBlockId, BasicBlockType};
use crate::cfg_dot::{CfgDot, CfgDotConfig, DotRenderableGraph, NodeResolver};
use crate::decompiler::region::{Region, RegionId, RegionType};
use crate::function::{Function, FunctionError};
use crate::opcode::Opcode;
use crate::utils::GBF_BLUE;
use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;
use thiserror::Error;
use super::ast::ast_vec::AstVec;
use super::ast::expr::ExprKind;
use super::ast::function::FunctionNode;
use super::ast::visitors::emit_context::EmitContext;
use super::ast::visitors::emitter::Gs2Emitter;
use super::ast::{AstKind, AstVisitable};
use super::execution_frame::ExecutionFrame;
use super::function_decompiler_context::FunctionDecompilerContext;
#[derive(Debug, Error)]
pub enum FunctionDecompilerError {
#[error("Cannot pop a node from the empty stack at BasicBlockId: {0}")]
CannotPopNode(BasicBlockId),
#[error("Encountered FunctionError while decompiling: {0}")]
FunctionError(#[from] FunctionError),
#[error("Encountered an error while processing the operand: {0}")]
OperandError(#[from] crate::operand::OperandError),
#[error("The instruction associated with opcode {0:?} must have an operand")]
InstructionMustHaveOperand(Opcode),
#[error("Invalid AstNode type on stack for BasicBlockId {0}. Expected {1}, found {2}")]
InvalidNodeType(BasicBlockId, String, String),
#[error("Encountered AstNodeError while decompiling: {0}")]
AstNodeError(#[from] super::ast::AstNodeError),
#[error("Unimplemented Opcode {0:?} in BasicBlockId {1}")]
UnimplementedOpcode(Opcode, BasicBlockId),
#[error("Execution stack is empty")]
ExecutionStackEmpty,
#[error("Unexpected execution state. Expected {0}, but found {1}")]
UnexpectedExecutionState(ExecutionFrame, ExecutionFrame),
}
pub struct FunctionDecompiler {
function: Function,
regions: Vec<Region>,
block_to_region: HashMap<BasicBlockId, RegionId>,
region_graph: DiGraph<(), ()>,
graph_node_to_region: HashMap<NodeIndex, RegionId>,
region_to_graph_node: HashMap<RegionId, NodeIndex>,
context: FunctionDecompilerContext,
function_parameters: AstVec<ExprKind>,
}
impl FunctionDecompiler {
pub fn new(function: Function) -> Self {
FunctionDecompiler {
function,
regions: Vec::new(),
block_to_region: HashMap::new(),
region_graph: DiGraph::new(),
graph_node_to_region: HashMap::new(),
region_to_graph_node: HashMap::new(),
context: FunctionDecompilerContext::new(),
function_parameters: Vec::<ExprKind>::new().into(),
}
}
}
impl FunctionDecompiler {
pub fn decompile(
&mut self,
emit_context: EmitContext,
) -> Result<String, FunctionDecompilerError> {
self.process_regions()?;
let entry_block_id = self.function.get_entry_basic_block().id;
let entry_region_id = self.block_to_region.get(&entry_block_id).unwrap();
let entry_region = self.regions.get(entry_region_id.index).unwrap();
let entry_region_nodes = entry_region
.iter_statements()
.cloned()
.collect::<AstVec<_>>();
let func = AstKind::Function(FunctionNode::new(
self.function.id.name.clone(),
self.function_parameters.clone(),
entry_region_nodes,
));
let mut output = String::new();
let mut emitter = Gs2Emitter::new(emit_context);
func.accept(&mut emitter);
output.push_str(emitter.output());
output.push('\n');
Ok(output)
}
pub fn add_node_to_current_region(&mut self, node: AstKind) {
let current_region_id = self.context.current_region_id.unwrap();
let current_region = self.regions.get_mut(current_region_id.index).unwrap();
current_region.push_node(node);
}
fn generate_regions(&mut self) {
for block in self.function.iter() {
let region_type = if block.id.block_type == BasicBlockType::ModuleEnd {
RegionType::Tail
} else {
RegionType::Linear
};
let new_region_id: RegionId = RegionId::new(self.regions.len(), region_type);
self.block_to_region.insert(block.id, new_region_id);
let node_id = self.region_graph.add_node(());
self.graph_node_to_region.insert(node_id, new_region_id);
self.region_to_graph_node.insert(new_region_id, node_id);
self.regions.push(Region::new(new_region_id));
}
}
fn process_regions(&mut self) -> Result<(), FunctionDecompilerError> {
self.generate_regions();
let reverse_post_order = self
.function
.get_reverse_post_order(self.function.get_entry_basic_block().id)
.map_err(FunctionDecompilerError::FunctionError)?;
for block_id in reverse_post_order {
let region_id = *self
.block_to_region
.get(&block_id)
.expect("We just made the regions, so not sure why it doesn't exist.");
self.context.start_block_processing(block_id, region_id)?;
self.connect_predecessor_regions(block_id, region_id)?;
let instructions: Vec<_> = {
let block = self.function.get_basic_block_by_id(block_id)?;
block.iter().cloned().collect()
};
for instr in instructions {
let processed = self.context.process_instruction(&instr)?;
if let Some(node) = processed.node_to_push {
self.add_node_to_current_region(node);
}
if let Some(params) = processed.function_parameters {
self.function_parameters = params;
}
}
}
Ok(())
}
fn connect_predecessor_regions(
&mut self,
block_id: BasicBlockId,
region_id: RegionId,
) -> Result<(), FunctionDecompilerError> {
let predecessors = self
.function
.get_predecessors(block_id)
.map_err(FunctionDecompilerError::FunctionError)?;
let predecessor_regions: Vec<RegionId> = predecessors
.iter()
.map(|pred_id| *self.block_to_region.get(pred_id).unwrap())
.collect();
for pred_region_id in predecessor_regions {
let pred_node_id = self.region_to_graph_node.get(&pred_region_id).unwrap();
let current_node_id = self.region_to_graph_node.get(®ion_id).unwrap();
self.region_graph
.add_edge(*pred_node_id, *current_node_id, ());
}
Ok(())
}
}
impl DotRenderableGraph for FunctionDecompiler {
fn render_dot(&self, config: CfgDotConfig) -> String {
let dot = CfgDot { config };
dot.render(&self.region_graph, self)
}
}
impl NodeResolver for FunctionDecompiler {
type NodeData = Region;
fn resolve(&self, node_index: NodeIndex) -> Option<&Self::NodeData> {
self.graph_node_to_region
.get(&node_index)
.and_then(|region_id| self.regions.get(region_id.index))
}
fn resolve_edge_color(&self, _: NodeIndex, _: NodeIndex) -> String {
GBF_BLUE.to_string()
}
}