Skip to content
Open
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
66 changes: 64 additions & 2 deletions gbf_core/src/decompiler/function_decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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, new_array};
use super::ast::{AstKind, AstVisitable, new_array, new_assignment, new_id_with_version};
use super::execution_frame::ExecutionFrame;
use super::function_decompiler_context::FunctionDecompilerContext;
use super::structure_analysis::region::{RegionId, RegionType};
Expand Down Expand Up @@ -284,7 +284,14 @@ impl FunctionDecompiler<'_> {
&mut self,
emit_context: EmitContext,
) -> Result<String, FunctionDecompilerError> {
self.process_regions()?;
{
self.process_regions()?;
}

// Create SSA variables from leftover stack nodes
{
self.create_ssa_vars_from_stack()?;
}

let entry_block_id = self.function.get_entry_basic_block().id;
let entry_region_id = self.block_to_region.get(&entry_block_id).unwrap();
Expand Down Expand Up @@ -335,6 +342,61 @@ impl FunctionDecompiler<'_> {
.cloned()
}

/// Create unresolved SSA variables from leftover stack nodes
fn create_ssa_vars_from_stack(&mut self) -> Result<(), FunctionDecompilerError> {
for (block_id, region_id) in &self.block_to_region {
// Grab the execution_frame from context
let execution_frame = {
let ctx = self.context.as_ref().unwrap();
ctx.block_ast_node_stack
.get(block_id)
.expect("[Bug] The block should have an AST node stack.")
// TODO: Cloning here is not ideal, but it's necessary to avoid borrowing issues.
.clone()
};

let region = self
.struct_analysis
.get_region_mut(*region_id)
.expect("[Bug] The region should exist.");

// Get the leftover nodes from the execution frame
for frame in execution_frame.iter() {
let node = match frame {
// TODO: This should be handled differently. Technically, StandaloneNodes could be part of a
// BuildingArray in a predecessor block. E.g. arr = {one, two == 0 ? three : four, five};
// In the above case, one will appear as a BuildingArray since it's the first node in the array.
// However, two will appear as a StandaloneNode since it's part of a ternary expression.
// Lastly, five will appear as a standalone node, but this is handled in a previous step that assumes
// EndArray is the last node in the array.
ExecutionFrame::BuildingArray(arr) => new_array::<ExprKind>(arr.clone()).into(),
ExecutionFrame::StandaloneNode(node) => {
if let AstKind::Expression(expr) = node {
expr.clone()
} else {
let ctx = self.context.as_ref().unwrap();
return Err(FunctionDecompilerError::UnexpectedNodeType {
expected: "Expression".to_string(),
context: ctx.get_error_context(),
backtrace: Backtrace::capture(),
});
}
}
};

// Create a new SSA variable for the leftover node
let ssa_context = &mut self.context.as_mut().unwrap().ssa_context;
let var = ssa_context.new_ssa_version_for("leftover");
let ssa_id = new_id_with_version("leftover", var);
let stmt = new_assignment(ssa_id.clone(), node);

region.push_node(stmt.into());
region.push_unresolved_node(ssa_id.into());
}
}
Ok(())
}

fn generate_regions(&mut self) -> Result<(), FunctionDecompilerError> {
for block in self.function.iter() {
// If the block is the end of the module, it is a tail region
Expand Down
18 changes: 18 additions & 0 deletions gbf_core/src/decompiler/structure_analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,24 @@ impl NodeResolver for StructureAnalysis {
}
}

impl<'a> IntoIterator for &'a StructureAnalysis {
type Item = &'a Region;
type IntoIter = std::slice::Iter<'a, Region>;

fn into_iter(self) -> Self::IntoIter {
self.regions.iter()
}
}

impl<'a> IntoIterator for &'a mut StructureAnalysis {
type Item = &'a mut Region;
type IntoIter = std::slice::IterMut<'a, Region>;

fn into_iter(self) -> Self::IntoIter {
self.regions.iter_mut()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
40 changes: 13 additions & 27 deletions gbf_core/tests/test_ast_construction.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
use common::{load_bytecode, load_expected_output};
use gbf_core::decompiler::{
ast::visitors::emit_context::EmitContext, function_decompiler::FunctionDecompilerBuilder,
};
use common::{get_all_bytecode_files, load_bytecode};
use gbf_core::decompiler::ast::visitors::emit_context::EmitContext;
pub mod common;

#[test]
fn load_simple_gs2() {
let reader = load_bytecode("simple.gs2bc").unwrap();
let _expected = load_expected_output("simple.gs2")
.unwrap()
.trim()
.to_string();
fn test_all_decompile() {
for fname in get_all_bytecode_files().unwrap().iter() {
let fname = fname.to_string();
let reader = load_bytecode(&fname).unwrap();
let module = gbf_core::module::ModuleBuilder::new()
.name(fname.clone())
.reader(Box::new(reader))
.build()
.unwrap();

let module = gbf_core::module::ModuleBuilder::new()
.name("simple.gs2".to_string())
.reader(Box::new(reader))
.build()
.unwrap();

// Get the entry function
let entry_function = module.get_entry_function();

// Decompile the entry function
let mut decompiler = FunctionDecompilerBuilder::new(entry_function).build();
let decompiled = decompiler.decompile(EmitContext::default());

// For now, assert that the decompiler did not fail
// TODO: We need to update the test to compare the decompiled output with the expected output
// once the decompiler is more stable.
assert!(decompiled.is_ok());
assert!(module.decompile(EmitContext::default()).is_ok());
}
}