mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 06:54:31 -04:00
[decompiler] clean up offline test program, reorganize decomp order (#895)
* make a new offline test * finish up offline test and fix crash bugs
This commit is contained in:
@@ -24,6 +24,7 @@ add_library(common
|
||||
util/dgo_util.cpp
|
||||
util/DgoReader.cpp
|
||||
util/DgoWriter.cpp
|
||||
util/diff.cpp
|
||||
util/FileUtil.cpp
|
||||
util/json_util.cpp
|
||||
util/Timer.cpp
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
enum EditType { kMatch, kAdd, kRemove, kReplace };
|
||||
std::vector<EditType> CalculateOptimalEdits(const std::vector<size_t>& left,
|
||||
const std::vector<size_t>& right) {
|
||||
std::vector<std::vector<double> > costs(left.size() + 1, std::vector<double>(right.size() + 1));
|
||||
std::vector<std::vector<EditType> > best_move(left.size() + 1,
|
||||
std::vector<EditType>(right.size() + 1));
|
||||
|
||||
// Populate for empty right.
|
||||
for (size_t l_i = 0; l_i < costs.size(); ++l_i) {
|
||||
costs[l_i][0] = static_cast<double>(l_i);
|
||||
best_move[l_i][0] = kRemove;
|
||||
}
|
||||
// Populate for empty left.
|
||||
for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) {
|
||||
costs[0][r_i] = static_cast<double>(r_i);
|
||||
best_move[0][r_i] = kAdd;
|
||||
}
|
||||
|
||||
for (size_t l_i = 0; l_i < left.size(); ++l_i) {
|
||||
for (size_t r_i = 0; r_i < right.size(); ++r_i) {
|
||||
if (left[l_i] == right[r_i]) {
|
||||
// Found a match. Consume it.
|
||||
costs[l_i + 1][r_i + 1] = costs[l_i][r_i];
|
||||
best_move[l_i + 1][r_i + 1] = kMatch;
|
||||
continue;
|
||||
}
|
||||
|
||||
const double add = costs[l_i + 1][r_i];
|
||||
const double remove = costs[l_i][r_i + 1];
|
||||
const double replace = costs[l_i][r_i];
|
||||
if (add < remove && add < replace) {
|
||||
costs[l_i + 1][r_i + 1] = add + 1;
|
||||
best_move[l_i + 1][r_i + 1] = kAdd;
|
||||
} else if (remove < add && remove < replace) {
|
||||
costs[l_i + 1][r_i + 1] = remove + 1;
|
||||
best_move[l_i + 1][r_i + 1] = kRemove;
|
||||
} else {
|
||||
// We make replace a little more expensive than add/remove to lower
|
||||
// their priority.
|
||||
costs[l_i + 1][r_i + 1] = replace + 1.00001;
|
||||
best_move[l_i + 1][r_i + 1] = kReplace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct the best path. We do it in reverse order.
|
||||
std::vector<EditType> best_path;
|
||||
for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) {
|
||||
EditType move = best_move[l_i][r_i];
|
||||
best_path.push_back(move);
|
||||
l_i -= move != kAdd;
|
||||
r_i -= move != kRemove;
|
||||
}
|
||||
std::reverse(best_path.begin(), best_path.end());
|
||||
return best_path;
|
||||
}
|
||||
|
||||
// Helper class to convert string into ids with deduplication.
|
||||
class InternalStrings {
|
||||
public:
|
||||
size_t GetId(const std::string& str) {
|
||||
IdMap::iterator it = ids_.find(str);
|
||||
if (it != ids_.end())
|
||||
return it->second;
|
||||
size_t id = ids_.size();
|
||||
return ids_[str] = id;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, size_t> IdMap;
|
||||
IdMap ids_;
|
||||
};
|
||||
|
||||
std::vector<EditType> CalculateOptimalEdits(const std::vector<std::string>& left,
|
||||
const std::vector<std::string>& right) {
|
||||
std::vector<size_t> left_ids, right_ids;
|
||||
{
|
||||
InternalStrings intern_table;
|
||||
for (size_t i = 0; i < left.size(); ++i) {
|
||||
left_ids.push_back(intern_table.GetId(left[i]));
|
||||
}
|
||||
for (size_t i = 0; i < right.size(); ++i) {
|
||||
right_ids.push_back(intern_table.GetId(right[i]));
|
||||
}
|
||||
}
|
||||
return CalculateOptimalEdits(left_ids, right_ids);
|
||||
}
|
||||
|
||||
constexpr char RESET_COLOR[] = "\x1B[0m";
|
||||
constexpr char RED_COLOR[] = "\x1B[31m";
|
||||
constexpr char GREEN_COLOR[] = "\x1B[32m";
|
||||
|
||||
// Helper class that holds the state for one hunk and prints it out to the
|
||||
// stream.
|
||||
// It reorders adds/removes when possible to group all removes before all
|
||||
// adds. It also adds the hunk header before printint into the stream.
|
||||
class Hunk {
|
||||
public:
|
||||
Hunk(size_t left_start, size_t right_start)
|
||||
: left_start_(left_start), right_start_(right_start), adds_(), removes_(), common_() {}
|
||||
|
||||
void PushLine(char edit, const char* line) {
|
||||
switch (edit) {
|
||||
case ' ':
|
||||
++common_;
|
||||
FlushEdits();
|
||||
hunk_.push_back(std::make_pair(' ', line));
|
||||
break;
|
||||
case '-':
|
||||
++removes_;
|
||||
hunk_removes_.push_back(std::make_pair('-', line));
|
||||
break;
|
||||
case '+':
|
||||
++adds_;
|
||||
hunk_adds_.push_back(std::make_pair('+', line));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintTo(std::ostream* os) {
|
||||
PrintHeader(os);
|
||||
FlushEdits();
|
||||
for (std::list<std::pair<char, const char*> >::const_iterator it = hunk_.begin();
|
||||
it != hunk_.end(); ++it) {
|
||||
// NOTE: this part is modified from gtest to give us pretty colored diffs.
|
||||
switch (it->first) {
|
||||
case '+':
|
||||
*os << GREEN_COLOR << it->first << it->second << RESET_COLOR << "\n";
|
||||
break;
|
||||
case '-':
|
||||
*os << RED_COLOR << it->first << it->second << RESET_COLOR << "\n";
|
||||
break;
|
||||
default:
|
||||
*os << it->first << it->second << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool has_edits() const { return adds_ || removes_; }
|
||||
|
||||
private:
|
||||
void FlushEdits() {
|
||||
hunk_.splice(hunk_.end(), hunk_removes_);
|
||||
hunk_.splice(hunk_.end(), hunk_adds_);
|
||||
}
|
||||
|
||||
// Print a unified diff header for one hunk.
|
||||
// The format is
|
||||
// "@@ -<left_start>,<left_length> +<right_start>,<right_length> @@"
|
||||
// where the left/right parts are omitted if unnecessary.
|
||||
void PrintHeader(std::ostream* ss) const {
|
||||
*ss << "@@ ";
|
||||
if (removes_) {
|
||||
*ss << "-" << left_start_ << "," << (removes_ + common_);
|
||||
}
|
||||
if (removes_ && adds_) {
|
||||
*ss << " ";
|
||||
}
|
||||
if (adds_) {
|
||||
*ss << "+" << right_start_ << "," << (adds_ + common_);
|
||||
}
|
||||
*ss << " @@\n";
|
||||
}
|
||||
|
||||
size_t left_start_, right_start_;
|
||||
size_t adds_, removes_, common_;
|
||||
std::list<std::pair<char, const char*> > hunk_, hunk_adds_, hunk_removes_;
|
||||
};
|
||||
|
||||
// Create a list of diff hunks in Unified diff format.
|
||||
// Each hunk has a header generated by PrintHeader above plus a body with
|
||||
// lines prefixed with ' ' for no change, '-' for deletion and '+' for
|
||||
// addition.
|
||||
// 'context' represents the desired unchanged prefix/suffix around the diff.
|
||||
// If two hunks are close enough that their contexts overlap, then they are
|
||||
// joined into one hunk.
|
||||
std::string CreateUnifiedDiff(const std::vector<std::string>& left,
|
||||
const std::vector<std::string>& right,
|
||||
size_t context) {
|
||||
const std::vector<EditType> edits = CalculateOptimalEdits(left, right);
|
||||
|
||||
size_t l_i = 0, r_i = 0, edit_i = 0;
|
||||
std::stringstream ss;
|
||||
while (edit_i < edits.size()) {
|
||||
// Find first edit.
|
||||
while (edit_i < edits.size() && edits[edit_i] == kMatch) {
|
||||
++l_i;
|
||||
++r_i;
|
||||
++edit_i;
|
||||
}
|
||||
|
||||
// Find the first line to include in the hunk.
|
||||
const size_t prefix_context = std::min(l_i, context);
|
||||
Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1);
|
||||
for (size_t i = prefix_context; i > 0; --i) {
|
||||
hunk.PushLine(' ', left[l_i - i].c_str());
|
||||
}
|
||||
|
||||
// Iterate the edits until we found enough suffix for the hunk or the input
|
||||
// is over.
|
||||
size_t n_suffix = 0;
|
||||
for (; edit_i < edits.size(); ++edit_i) {
|
||||
if (n_suffix >= context) {
|
||||
// Continue only if the next hunk is very close.
|
||||
auto it = edits.begin() + static_cast<int>(edit_i);
|
||||
while (it != edits.end() && *it == kMatch)
|
||||
++it;
|
||||
if (it == edits.end() || static_cast<size_t>(it - edits.begin()) - edit_i >= context) {
|
||||
// There is no next edit or it is too far away.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EditType edit = edits[edit_i];
|
||||
// Reset count when a non match is found.
|
||||
n_suffix = edit == kMatch ? n_suffix + 1 : 0;
|
||||
|
||||
if (edit == kMatch || edit == kRemove || edit == kReplace) {
|
||||
hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str());
|
||||
}
|
||||
if (edit == kAdd || edit == kReplace) {
|
||||
hunk.PushLine('+', right[r_i].c_str());
|
||||
}
|
||||
|
||||
// Advance indices, depending on edit type.
|
||||
l_i += edit != kAdd;
|
||||
r_i += edit != kRemove;
|
||||
}
|
||||
|
||||
if (!hunk.has_edits()) {
|
||||
// We are done. We don't want this hunk.
|
||||
break;
|
||||
}
|
||||
|
||||
hunk.PrintTo(&ss);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitString(const ::std::string& str, char delimiter = '\n') {
|
||||
::std::vector< ::std::string> parsed;
|
||||
::std::string::size_type pos = 0;
|
||||
while (true) {
|
||||
const ::std::string::size_type colon = str.find(delimiter, pos);
|
||||
if (colon == ::std::string::npos) {
|
||||
parsed.push_back(str.substr(pos));
|
||||
break;
|
||||
} else {
|
||||
parsed.push_back(str.substr(pos, colon - pos));
|
||||
pos = colon + 1;
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
std::string diff_strings(const std::string& lhs, const std::string& rhs) {
|
||||
if (!lhs.empty() && !rhs.empty()) {
|
||||
const std::vector<std::string> lhs_lines = SplitString(lhs);
|
||||
const std::vector<std::string> rhs_lines = SplitString(rhs);
|
||||
if (lhs_lines.size() > 1 || rhs_lines.size() > 1) {
|
||||
return CreateUnifiedDiff(lhs_lines, rhs_lines, 2);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/*!
|
||||
* Diff two strings. This uses the code from gtest's diff implementation.
|
||||
*/
|
||||
std::string diff_strings(const std::string& lhs, const std::string& rhs);
|
||||
@@ -70,6 +70,12 @@ add_library(
|
||||
|
||||
config.cpp)
|
||||
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
|
||||
elseif (WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2")
|
||||
endif ()
|
||||
|
||||
target_link_libraries(decomp
|
||||
lzokay
|
||||
common
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "LinkedObjectFile.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "common/common_types.h"
|
||||
#include "decompiler/analysis/symbol_def_map.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
@@ -39,6 +40,9 @@ struct ObjectFileData {
|
||||
std::string name_from_map;
|
||||
std::string to_unique_name() const;
|
||||
uint32_t reference_count = 0; // number of times its used.
|
||||
|
||||
std::string full_output;
|
||||
std::string output_with_skips;
|
||||
};
|
||||
|
||||
class ObjectFileDB {
|
||||
@@ -65,26 +69,26 @@ class ObjectFileDB {
|
||||
void analyze_functions_ir1(const Config& config);
|
||||
void analyze_functions_ir2(const std::string& output_dir,
|
||||
const Config& config,
|
||||
bool skip_debug_output = false);
|
||||
const std::unordered_set<std::string>& skip_functions);
|
||||
void ir2_top_level_pass(const Config& config);
|
||||
void ir2_stack_spill_slot_pass(int seg);
|
||||
void ir2_basic_block_pass(int seg, const Config& config);
|
||||
void ir2_atomic_op_pass(int seg, const Config& config);
|
||||
void ir2_type_analysis_pass(int seg, const Config& config);
|
||||
void ir2_register_usage_pass(int seg);
|
||||
void ir2_variable_pass(int seg);
|
||||
void ir2_cfg_build_pass(int seg);
|
||||
void ir2_stack_spill_slot_pass(int seg, ObjectFileData& data);
|
||||
void ir2_basic_block_pass(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_atomic_op_pass(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_type_analysis_pass(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_register_usage_pass(int seg, ObjectFileData& data);
|
||||
void ir2_variable_pass(int seg, ObjectFileData& data);
|
||||
void ir2_cfg_build_pass(int seg, ObjectFileData& data);
|
||||
void ir2_store_current_forms(int seg);
|
||||
void ir2_build_expressions(int seg, const Config& config);
|
||||
void ir2_insert_lets(int seg);
|
||||
void ir2_rewrite_inline_asm_instructions(int seg);
|
||||
void ir2_insert_anonymous_functions(int seg);
|
||||
void ir2_symbol_definition_map(const std::string& output_dir);
|
||||
void ir2_write_results(const std::string& output_dir, const Config& config);
|
||||
void ir2_do_segment_analysis_phase1(int seg, const Config& config);
|
||||
void ir2_do_segment_analysis_phase2(int seg, const Config& config);
|
||||
void ir2_setup_labels(const Config& config);
|
||||
void ir2_run_mips2c(const Config& config);
|
||||
void ir2_build_expressions(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_insert_lets(int seg, ObjectFileData& data);
|
||||
void ir2_rewrite_inline_asm_instructions(int seg, ObjectFileData& data);
|
||||
void ir2_insert_anonymous_functions(int seg, ObjectFileData& data);
|
||||
void ir2_symbol_definition_map(ObjectFileData& data);
|
||||
void ir2_write_results(const std::string& output_dir, const Config& config, ObjectFileData& data);
|
||||
void ir2_do_segment_analysis_phase1(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_do_segment_analysis_phase2(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_setup_labels(const Config& config, ObjectFileData& data);
|
||||
void ir2_run_mips2c(const Config& config, ObjectFileData& data);
|
||||
std::string ir2_to_file(ObjectFileData& data, const Config& config);
|
||||
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
|
||||
std::string ir2_final_out(ObjectFileData& data,
|
||||
@@ -157,6 +161,17 @@ class ObjectFileDB {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void for_each_function_def_order_in_obj(ObjectFileData& data, Func f) {
|
||||
for (int i = 0; i < int(data.linked_data.segments); i++) {
|
||||
int fn = 0;
|
||||
for (size_t j = data.linked_data.functions_by_seg.at(i).size(); j-- > 0;) {
|
||||
f(data.linked_data.functions_by_seg.at(i).at(j), i);
|
||||
fn++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void for_each_function_in_seg(int seg, Func f) {
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
@@ -170,6 +185,17 @@ class ObjectFileDB {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void for_each_function_in_seg_in_obj(int seg, ObjectFileData& data, Func f) {
|
||||
int fn = 0;
|
||||
if (data.linked_data.segments == 3) {
|
||||
for (size_t j = data.linked_data.functions_by_seg.at(seg).size(); j-- > 0;) {
|
||||
f(data.linked_data.functions_by_seg.at(seg).at(j));
|
||||
fn++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Danger: after adding all object files, we assume that the vector never reallocates.
|
||||
std::unordered_map<std::string, std::vector<ObjectFileData>> obj_files_by_name;
|
||||
std::unordered_map<std::string, std::vector<ObjectFileRecord>> obj_files_by_dgo;
|
||||
@@ -177,6 +203,8 @@ class ObjectFileDB {
|
||||
std::vector<std::string> obj_file_order;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> dgo_obj_name_map;
|
||||
|
||||
SymbolMapBuilder map_builder;
|
||||
|
||||
struct {
|
||||
uint32_t total_dgo_bytes = 0;
|
||||
uint32_t total_obj_files = 0;
|
||||
|
||||
@@ -35,105 +35,108 @@ namespace decompiler {
|
||||
*/
|
||||
void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir,
|
||||
const Config& config,
|
||||
bool skip_debug_output) {
|
||||
(void)skip_debug_output;
|
||||
const std::unordered_set<std::string>& skip_functions) {
|
||||
// First, do basic analysis on the top level:
|
||||
lg::info("Using IR2 analysis...");
|
||||
|
||||
lg::info("Processing top-level functions...");
|
||||
ir2_top_level_pass(config);
|
||||
|
||||
ir2_do_segment_analysis_phase1(TOP_LEVEL_SEGMENT, config);
|
||||
ir2_do_segment_analysis_phase1(DEBUG_SEGMENT, config);
|
||||
ir2_do_segment_analysis_phase1(MAIN_SEGMENT, config);
|
||||
|
||||
ir2_setup_labels(config);
|
||||
|
||||
ir2_do_segment_analysis_phase2(TOP_LEVEL_SEGMENT, config);
|
||||
|
||||
int total_file_count = 0;
|
||||
for (auto& f : obj_files_by_name) {
|
||||
total_file_count += f.second.size();
|
||||
}
|
||||
int file_idx = 1;
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
Timer file_timer;
|
||||
fmt::print("[{:3d}/{}]------ {}\n", file_idx++, total_file_count, data.to_unique_name());
|
||||
ir2_do_segment_analysis_phase1(TOP_LEVEL_SEGMENT, config, data);
|
||||
ir2_do_segment_analysis_phase1(DEBUG_SEGMENT, config, data);
|
||||
ir2_do_segment_analysis_phase1(MAIN_SEGMENT, config, data);
|
||||
ir2_setup_labels(config, data);
|
||||
ir2_do_segment_analysis_phase2(TOP_LEVEL_SEGMENT, config, data);
|
||||
try {
|
||||
run_defstate(data.linked_data.functions_by_seg.at(2).front());
|
||||
if (data.linked_data.functions_by_seg.size() == 3) {
|
||||
run_defstate(data.linked_data.functions_by_seg.at(2).front());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
lg::error("Failed to find defstates: {}", e.what());
|
||||
}
|
||||
});
|
||||
ir2_do_segment_analysis_phase2(DEBUG_SEGMENT, config, data);
|
||||
ir2_do_segment_analysis_phase2(MAIN_SEGMENT, config, data);
|
||||
|
||||
ir2_do_segment_analysis_phase2(DEBUG_SEGMENT, config);
|
||||
ir2_do_segment_analysis_phase2(MAIN_SEGMENT, config);
|
||||
ir2_insert_anonymous_functions(DEBUG_SEGMENT, data);
|
||||
ir2_insert_anonymous_functions(MAIN_SEGMENT, data);
|
||||
ir2_insert_anonymous_functions(TOP_LEVEL_SEGMENT, data);
|
||||
|
||||
ir2_run_mips2c(config, data);
|
||||
|
||||
ir2_symbol_definition_map(data);
|
||||
|
||||
if (!output_dir.empty()) {
|
||||
ir2_write_results(output_dir, config, data);
|
||||
} else {
|
||||
if (!skip_functions.empty()) {
|
||||
data.output_with_skips = ir2_final_out(data, skip_functions);
|
||||
}
|
||||
data.full_output = ir2_final_out(data);
|
||||
}
|
||||
|
||||
for_each_function_def_order_in_obj(data, [&](Function& f, int) { f.ir2 = {}; });
|
||||
|
||||
fmt::print("Done in {:.2f}ms\n", file_timer.getMs());
|
||||
});
|
||||
|
||||
if (config.generate_symbol_definition_map) {
|
||||
lg::info("Generating symbol definition map...");
|
||||
ir2_symbol_definition_map(output_dir);
|
||||
}
|
||||
|
||||
lg::info("Inserting anonymous function definitions...");
|
||||
|
||||
ir2_insert_anonymous_functions(DEBUG_SEGMENT);
|
||||
ir2_insert_anonymous_functions(MAIN_SEGMENT);
|
||||
ir2_insert_anonymous_functions(TOP_LEVEL_SEGMENT);
|
||||
|
||||
// doesn't really matter where we do this.
|
||||
ir2_run_mips2c(config);
|
||||
|
||||
if (!output_dir.empty()) {
|
||||
lg::info("Writing results...");
|
||||
ir2_write_results(output_dir, config);
|
||||
map_builder.build_map();
|
||||
std::string result = map_builder.convert_to_json();
|
||||
auto file_name = file_util::combine_path(output_dir, "symbol_map.json");
|
||||
file_util::write_text_file(file_name, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_do_segment_analysis_phase1(int seg, const Config& config) {
|
||||
lg::info("ASM analysis for {} segment", SEGMENT_NAMES[seg]);
|
||||
|
||||
lg::info("Processing basic blocks and control flow graph...");
|
||||
ir2_basic_block_pass(seg, config);
|
||||
lg::info("Finding stack spills...");
|
||||
ir2_stack_spill_slot_pass(seg);
|
||||
lg::info("Converting to atomic ops...");
|
||||
ir2_atomic_op_pass(seg, config);
|
||||
void ObjectFileDB::ir2_do_segment_analysis_phase1(int seg,
|
||||
const Config& config,
|
||||
ObjectFileData& data) {
|
||||
ir2_basic_block_pass(seg, config, data);
|
||||
ir2_stack_spill_slot_pass(seg, data);
|
||||
ir2_atomic_op_pass(seg, config, data);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_do_segment_analysis_phase2(int seg, const Config& config) {
|
||||
lg::info("GOAL analysis for {} segment", SEGMENT_NAMES[seg]);
|
||||
void ObjectFileDB::ir2_do_segment_analysis_phase2(int seg,
|
||||
const Config& config,
|
||||
ObjectFileData& data) {
|
||||
ir2_type_analysis_pass(seg, config, data);
|
||||
ir2_register_usage_pass(seg, data);
|
||||
ir2_variable_pass(seg, data);
|
||||
ir2_cfg_build_pass(seg, data);
|
||||
|
||||
lg::info("Running type analysis...");
|
||||
ir2_type_analysis_pass(seg, config);
|
||||
lg::info("Register usage analysis...");
|
||||
ir2_register_usage_pass(seg);
|
||||
lg::info("Variable analysis...");
|
||||
ir2_variable_pass(seg);
|
||||
lg::info("Initial structuring...");
|
||||
ir2_cfg_build_pass(seg);
|
||||
ir2_build_expressions(seg, config, data);
|
||||
ir2_rewrite_inline_asm_instructions(seg, data);
|
||||
|
||||
lg::info("Expression building...");
|
||||
ir2_build_expressions(seg, config);
|
||||
lg::info("Re-writing inline asm instructions...");
|
||||
ir2_rewrite_inline_asm_instructions(seg);
|
||||
|
||||
lg::info("Inserting lets...");
|
||||
ir2_insert_lets(seg);
|
||||
ir2_insert_lets(seg, data);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_setup_labels(const Config& config) {
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
if (data.linked_data.segments == 3) {
|
||||
std::unordered_map<std::string, LabelConfigInfo> config_labels;
|
||||
auto config_it = config.label_types.find(data.to_unique_name());
|
||||
if (config_it != config.label_types.end()) {
|
||||
config_labels = config_it->second;
|
||||
}
|
||||
try {
|
||||
data.linked_data.label_db =
|
||||
std::make_unique<LabelDB>(config_labels, data.linked_data.labels, dts);
|
||||
analyze_labels(data.linked_data.label_db.get(), &data.linked_data);
|
||||
} catch (const std::exception& e) {
|
||||
lg::die("Error parsing labels for {}: {}\n", data.to_unique_name(), e.what());
|
||||
}
|
||||
void ObjectFileDB::ir2_setup_labels(const Config& config, ObjectFileData& data) {
|
||||
if (data.linked_data.segments == 3) {
|
||||
std::unordered_map<std::string, LabelConfigInfo> config_labels;
|
||||
auto config_it = config.label_types.find(data.to_unique_name());
|
||||
if (config_it != config.label_types.end()) {
|
||||
config_labels = config_it->second;
|
||||
}
|
||||
});
|
||||
try {
|
||||
data.linked_data.label_db =
|
||||
std::make_unique<LabelDB>(config_labels, data.linked_data.labels, dts);
|
||||
analyze_labels(data.linked_data.label_db.get(), &data.linked_data);
|
||||
} catch (const std::exception& e) {
|
||||
lg::die("Error parsing labels for {}: {}\n", data.to_unique_name(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_run_mips2c(const Config& config) {
|
||||
for_each_function_def_order([&](Function& func, int, ObjectFileData&) {
|
||||
void ObjectFileDB::ir2_run_mips2c(const Config& config, ObjectFileData& data) {
|
||||
for_each_function_def_order_in_obj(data, [&](Function& func, int) {
|
||||
if (config.hacks.mips2c_functions_by_name.count(func.name())) {
|
||||
lg::info("MIPS2C on {}", func.name());
|
||||
run_mips2c(&func);
|
||||
@@ -255,28 +258,14 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
|
||||
* - Analyze prologue and epilogue
|
||||
* - Build control flow graph
|
||||
*/
|
||||
void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config) {
|
||||
Timer timer;
|
||||
// Main Pass over each function...
|
||||
int total_basic_blocks = 0;
|
||||
int total_functions = 0;
|
||||
int functions_with_one_block = 0;
|
||||
int inspect_methods = 0;
|
||||
int suspected_asm = 0;
|
||||
int failed_to_build_cfg = 0;
|
||||
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
total_functions++;
|
||||
void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
func.ir2.env.file = &data.linked_data;
|
||||
func.ir2.env.dts = &dts;
|
||||
func.ir2.env.func = &func;
|
||||
|
||||
// first, find basic blocks.
|
||||
auto blocks = find_blocks_in_function(data.linked_data, seg, func);
|
||||
total_basic_blocks += blocks.size();
|
||||
if (blocks.size() == 1) {
|
||||
functions_with_one_block++;
|
||||
}
|
||||
func.basic_blocks = blocks;
|
||||
|
||||
if (!func.suspected_asm) {
|
||||
@@ -309,7 +298,6 @@ void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config) {
|
||||
if (!func.cfg->is_fully_resolved()) {
|
||||
lg::warn("Function {} from {} failed to build control flow graph!", func.name(),
|
||||
data.to_unique_name());
|
||||
failed_to_build_cfg++;
|
||||
} else {
|
||||
func.cfg_ok = true;
|
||||
}
|
||||
@@ -317,63 +305,35 @@ void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config) {
|
||||
|
||||
if (func.suspected_asm) {
|
||||
func.warnings.info("Assembly Function");
|
||||
suspected_asm++;
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Found {} basic blocks in {} functions in {:.2f} ms:", total_basic_blocks,
|
||||
total_functions, timer.getMs());
|
||||
lg::info(" {} functions ({:.2f}%) failed to build control flow graph", failed_to_build_cfg,
|
||||
100.f * failed_to_build_cfg / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) had exactly one basic block", functions_with_one_block,
|
||||
100.f * functions_with_one_block / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) were ignored as assembly", suspected_asm,
|
||||
100.f * suspected_asm / total_functions);
|
||||
lg::info(" {} functions ({:.2f}%) were inspect methods\n", inspect_methods,
|
||||
100.f * inspect_methods / total_functions);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_stack_spill_slot_pass(int seg) {
|
||||
Timer timer;
|
||||
int functions_with_spills = 0;
|
||||
int total_slots = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData&) {
|
||||
void ObjectFileDB::ir2_stack_spill_slot_pass(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
if (!func.cfg_ok) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto spill_map = build_spill_map(func.instructions, {func.prologue_end, func.epilogue_start});
|
||||
auto map_size = spill_map.size();
|
||||
if (map_size) {
|
||||
functions_with_spills++;
|
||||
total_slots += map_size;
|
||||
}
|
||||
func.ir2.env.set_stack_spills(spill_map);
|
||||
} catch (std::exception& e) {
|
||||
func.warnings.general_warning("stack spill failed: {}", e.what());
|
||||
}
|
||||
});
|
||||
lg::info("Analyzed stack spills: found {} functions with spills (total {} vars), took {:.2f} ms",
|
||||
functions_with_spills, total_slots, timer.getMs());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Conversion of MIPS instructions into AtomicOps. The AtomicOps represent what we
|
||||
* think are IR of the original GOAL compiler.
|
||||
*/
|
||||
void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config) {
|
||||
Timer timer;
|
||||
int total_functions = 0;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
if (!func.cfg_ok) {
|
||||
return;
|
||||
}
|
||||
total_functions++;
|
||||
if (!func.suspected_asm) {
|
||||
func.ir2.atomic_ops_attempted = true;
|
||||
attempted++;
|
||||
try {
|
||||
bool inline_asm = config.hacks.hint_inline_assembly_functions.find(func.name()) !=
|
||||
config.hacks.hint_inline_assembly_functions.end();
|
||||
@@ -391,7 +351,6 @@ void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config) {
|
||||
func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
||||
func.ir2.atomic_ops_succeeded = true;
|
||||
func.ir2.env.set_end_var(func.ir2.atomic_ops->end_op().return_var());
|
||||
successful++;
|
||||
} catch (std::exception& e) {
|
||||
lg::warn("Function {} from {} could not be converted to atomic ops: {}", func.name(),
|
||||
data.to_unique_name(), e.what());
|
||||
@@ -399,23 +358,10 @@ void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{} (successful/attempted/total) functions converted to Atomic Ops in {:.2f} ms",
|
||||
successful, attempted, total_functions, timer.getMs());
|
||||
lg::info("{:.2f}% were attempted, {:.2f}% of attempted succeeded\n",
|
||||
100.f * attempted / total_functions, 100.f * successful / attempted);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_symbol_definition_map(const std::string& output_dir) {
|
||||
Timer timer;
|
||||
SymbolMapBuilder map_builder;
|
||||
for_each_obj([&](ObjectFileData& data) { map_builder.add_object(data); });
|
||||
map_builder.build_map();
|
||||
std::string result = map_builder.convert_to_json();
|
||||
auto file_name = file_util::combine_path(output_dir, "symbol_map.json");
|
||||
file_util::write_text_file(file_name, result);
|
||||
|
||||
lg::info("Built symbol map in {:.2f} ms", timer.getMs());
|
||||
void ObjectFileDB::ir2_symbol_definition_map(ObjectFileData& data) {
|
||||
map_builder.add_object(data);
|
||||
}
|
||||
|
||||
template <typename Key, typename Value>
|
||||
@@ -434,22 +380,13 @@ Value try_lookup(const std::unordered_map<Key, Value>& map, const Key& key) {
|
||||
* - Propagate types.
|
||||
* - NOTE: this will update register info usage more accurately for functions.
|
||||
*/
|
||||
void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config) {
|
||||
Timer timer;
|
||||
int total_functions = 0;
|
||||
int non_asm_functions = 0;
|
||||
int attempted_functions = 0;
|
||||
int successful_functions = 0;
|
||||
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
total_functions++;
|
||||
void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
if (!func.suspected_asm) {
|
||||
non_asm_functions++;
|
||||
TypeSpec ts;
|
||||
if (lookup_function_type(func.guessed_name, data.to_unique_name(), config, &ts) &&
|
||||
func.ir2.atomic_ops_succeeded) {
|
||||
func.type = ts;
|
||||
attempted_functions++;
|
||||
// try type analysis here.
|
||||
auto func_name = func.name();
|
||||
auto register_casts =
|
||||
@@ -470,7 +407,6 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config) {
|
||||
func.ir2.env.set_stack_structure_hints(
|
||||
try_lookup(config.stack_structure_hints_by_function, func_name));
|
||||
if (run_type_analysis_ir2(ts, dts, func)) {
|
||||
successful_functions++;
|
||||
func.ir2.env.types_succeeded = true;
|
||||
} else {
|
||||
func.warnings.type_prop_warning("Type analysis failed");
|
||||
@@ -481,20 +417,11 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{}/{} (success/attempted/non-asm/total) in {:.2f} ms\n", successful_functions,
|
||||
attempted_functions, non_asm_functions, total_functions, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_register_usage_pass(int seg) {
|
||||
Timer timer;
|
||||
|
||||
int total_funcs = 0, analyzed_funcs = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
(void)data;
|
||||
total_funcs++;
|
||||
void ObjectFileDB::ir2_register_usage_pass(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded) {
|
||||
analyzed_funcs++;
|
||||
func.ir2.env.set_reg_use(analyze_ir2_register_usage(func));
|
||||
|
||||
auto block_0_start = func.ir2.env.reg_use().block.at(0).input;
|
||||
@@ -536,24 +463,16 @@ void ObjectFileDB::ir2_register_usage_pass(int seg) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{} functions had register usage analyzed in {:.2f} ms\n", analyzed_funcs,
|
||||
total_funcs, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_variable_pass(int seg) {
|
||||
Timer timer;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
void ObjectFileDB::ir2_variable_pass(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded && func.ir2.env.has_type_analysis()) {
|
||||
try {
|
||||
attempted++;
|
||||
auto result =
|
||||
run_variable_renaming(func, func.ir2.env.reg_use(), *func.ir2.atomic_ops, dts);
|
||||
if (result.has_value()) {
|
||||
successful++;
|
||||
func.ir2.env.set_local_vars(*result);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
@@ -561,16 +480,14 @@ void ObjectFileDB::ir2_variable_pass(int seg) {
|
||||
}
|
||||
}
|
||||
});
|
||||
lg::info("{}/{} functions out of attempted passed variable pass in {:.2f} ms\n", successful,
|
||||
attempted, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_cfg_build_pass(int seg) {
|
||||
void ObjectFileDB::ir2_cfg_build_pass(int seg, ObjectFileData& data) {
|
||||
Timer timer;
|
||||
int total = 0;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
total++;
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded && func.cfg->is_fully_resolved()) {
|
||||
@@ -587,38 +504,30 @@ void ObjectFileDB::ir2_cfg_build_pass(int seg) {
|
||||
successful++;
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{} cfg build in {:.2f} ms\n", successful, attempted, total, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_store_current_forms(int seg) {
|
||||
Timer timer;
|
||||
int total = 0;
|
||||
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
// void ObjectFileDB::ir2_store_current_forms(int seg) {
|
||||
// Timer timer;
|
||||
// int total = 0;
|
||||
//
|
||||
// for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
// (void)data;
|
||||
//
|
||||
// if (func.ir2.top_form) {
|
||||
// total++;
|
||||
// func.ir2.debug_form_string =
|
||||
// pretty_print::to_string(func.ir2.top_form->to_form(func.ir2.env));
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// lg::info("Stored debug forms for {} functions in {:.2f} ms\n", total, timer.getMs());
|
||||
//}
|
||||
//
|
||||
void ObjectFileDB::ir2_build_expressions(int seg, const Config& config, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
|
||||
if (func.ir2.top_form) {
|
||||
total++;
|
||||
func.ir2.debug_form_string =
|
||||
pretty_print::to_string(func.ir2.top_form->to_form(func.ir2.env));
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Stored debug forms for {} functions in {:.2f} ms\n", total, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_build_expressions(int seg, const Config& config) {
|
||||
Timer timer;
|
||||
int total = 0;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
(void)data;
|
||||
total++;
|
||||
if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars() &&
|
||||
func.ir2.env.types_succeeded) {
|
||||
attempted++;
|
||||
auto name = func.name();
|
||||
auto arg_config = config.function_arg_names.find(name);
|
||||
auto var_config = config.function_var_overrides.find(name);
|
||||
@@ -630,26 +539,18 @@ void ObjectFileDB::ir2_build_expressions(int seg, const Config& config) {
|
||||
? var_config->second
|
||||
: std::unordered_map<std::string, LocalVarOverride>{},
|
||||
dts)) {
|
||||
successful++;
|
||||
func.ir2.print_debug_forms = true;
|
||||
func.ir2.expressions_succeeded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{} expression build in {:.2f} ms\n", successful, attempted, total, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_insert_lets(int seg) {
|
||||
Timer timer;
|
||||
LetStats combined_stats;
|
||||
int attempted = 0;
|
||||
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData&) {
|
||||
void ObjectFileDB::ir2_insert_lets(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
if (func.ir2.expressions_succeeded) {
|
||||
attempted++;
|
||||
try {
|
||||
combined_stats += insert_lets(func, func.ir2.env, *func.ir2.form_pool, func.ir2.top_form);
|
||||
insert_lets(func, func.ir2.env, *func.ir2.form_pool, func.ir2.top_form);
|
||||
} catch (const std::exception& e) {
|
||||
func.warnings.general_warning(
|
||||
fmt::format("Error while inserting lets: {}. Make sure that the return type is not "
|
||||
@@ -658,71 +559,47 @@ void ObjectFileDB::ir2_insert_lets(int seg) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Let pass on {} functions ({}/{} vars in lets) in {:.2f} ms\n", attempted,
|
||||
combined_stats.vars_in_lets, combined_stats.total_vars, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_rewrite_inline_asm_instructions(int seg) {
|
||||
Timer timer;
|
||||
int total = 0;
|
||||
int attempted = 0;
|
||||
int successful = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
void ObjectFileDB::ir2_rewrite_inline_asm_instructions(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
total++;
|
||||
if (func.ir2.top_form && func.ir2.env.has_type_analysis()) {
|
||||
attempted++;
|
||||
if (rewrite_inline_asm_instructions(func.ir2.top_form, *func.ir2.form_pool, func, dts)) {
|
||||
successful++;
|
||||
func.ir2.print_debug_forms = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{}/{} rewrote inline-asm instructions in {:.2f} ms\n", successful, attempted, total,
|
||||
timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_insert_anonymous_functions(int seg) {
|
||||
Timer timer;
|
||||
int total = 0;
|
||||
for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
void ObjectFileDB::ir2_insert_anonymous_functions(int seg, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
if (func.ir2.top_form && func.ir2.env.has_type_analysis()) {
|
||||
try {
|
||||
total += insert_static_refs(func.ir2.top_form, *func.ir2.form_pool, func, dts);
|
||||
insert_static_refs(func.ir2.top_form, *func.ir2.form_pool, func, dts);
|
||||
} catch (std::exception& e) {
|
||||
func.warnings.general_warning("Failed static ref finding: {}\n", e.what());
|
||||
lg::error("Function {} failed static ref: {}\n", func.name(), e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("Inserted {} anonymous functions in {:.2f} ms\n", total, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_write_results(const std::string& output_dir, const Config& config) {
|
||||
Timer timer;
|
||||
lg::info("Writing IR2 results to file...");
|
||||
int total_files = 0;
|
||||
int total_bytes = 0;
|
||||
for_each_obj([&](ObjectFileData& obj) {
|
||||
if (obj.linked_data.has_any_functions()) {
|
||||
// todo
|
||||
total_files++;
|
||||
auto file_text = ir2_to_file(obj, config);
|
||||
total_bytes += file_text.length();
|
||||
auto file_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_ir2.asm");
|
||||
file_util::write_text_file(file_name, file_text);
|
||||
void ObjectFileDB::ir2_write_results(const std::string& output_dir,
|
||||
const Config& config,
|
||||
ObjectFileData& obj) {
|
||||
if (obj.linked_data.has_any_functions()) {
|
||||
// todo
|
||||
|
||||
auto final = ir2_final_out(obj);
|
||||
auto final_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_disasm.gc");
|
||||
file_util::write_text_file(final_name, final);
|
||||
}
|
||||
});
|
||||
lg::info("Wrote {} files ({:.2f} MB) in {:.2f} ms\n", total_files, total_bytes / float(1 << 20),
|
||||
timer.getMs());
|
||||
auto file_text = ir2_to_file(obj, config);
|
||||
auto file_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_ir2.asm");
|
||||
file_util::write_text_file(file_name, file_text);
|
||||
|
||||
auto final = ir2_final_out(obj);
|
||||
auto final_name = file_util::combine_path(output_dir, obj.to_unique_name() + "_disasm.gc");
|
||||
file_util::write_text_file(final_name, final);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::ir2_to_file(ObjectFileData& data, const Config& config) {
|
||||
|
||||
@@ -35,7 +35,7 @@ bool try_convert_lambda(const Function& parent_function,
|
||||
"ignored and is no longer required.",
|
||||
lab.name);
|
||||
}
|
||||
if (!other_func->ir2.env.has_local_vars()) {
|
||||
if (!other_func->ir2.env.has_local_vars() || !other_func->ir2.top_form) {
|
||||
// don't bother if we don't even have vars.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,7 @@
|
||||
// if you want to filter to only some object names.
|
||||
// it will make the decompiler much faster.
|
||||
"allowed_objects": [],
|
||||
"banned_objects": [
|
||||
"crates",
|
||||
"puffer",
|
||||
"helix-water",
|
||||
"green-eco-lurker",
|
||||
"seagull",
|
||||
"sunken-pipegame",
|
||||
"snow-ram",
|
||||
"snow-ram-boss",
|
||||
"rolling-race-ring",
|
||||
"part-tester"
|
||||
],
|
||||
"banned_objects": [],
|
||||
|
||||
////////////////////////////
|
||||
// CODE ANALYSIS OPTIONS
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
// main decompile.
|
||||
if (config.decompile_code) {
|
||||
db.analyze_functions_ir2(out_folder, config);
|
||||
db.analyze_functions_ir2(out_folder, config, {});
|
||||
}
|
||||
|
||||
// write out all symbols
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
add_executable(offline-test
|
||||
${CMAKE_CURRENT_LIST_DIR}/offline_test_main.cpp)
|
||||
|
||||
target_link_libraries(offline-test common gtest decomp compiler)
|
||||
target_link_libraries(offline-test common gtest decomp compiler)
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"dgos": ["CGO/KERNEL.CGO", "CGO/ENGINE.CGO", "CGO/GAME.CGO", "DGO/BEA.DGO",
|
||||
"DGO/INT.DGO", "DGO/VI1.DGO", "DGO/VI2.DGO", "DGO/VI3.DGO",
|
||||
"DGO/CIT.DGO", "DGO/MIS.DGO", "DGO/JUB.DGO", "DGO/SUN.DGO",
|
||||
"DGO/DEM.DGO", "DGO/FIN.DGO", "DGO/JUN.DGO", "DGO/FIC.DGO",
|
||||
"DGO/SNO.DGO", "DGO/SWA.DGO", "DGO/MAI.DGO", "DGO/ROB.DGO",
|
||||
"DGO/LAV.DGO", "DGO/OGR.DGO", "DGO/TRA.DGO", "DGO/ROL.DGO"],
|
||||
|
||||
"skip_compile_files": [
|
||||
"timer", // accessing timer regs
|
||||
"display", // interrupt handlers
|
||||
"target-snowball" // screwed up labels, likely cut content
|
||||
],
|
||||
|
||||
"skip_compile_functions": [
|
||||
/// GCOMMON
|
||||
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
|
||||
"abs", "ash", "min", "max", "lognor",
|
||||
// weird PS2 specific debug registers:
|
||||
"breakpoint-range-set!",
|
||||
// inline assembly
|
||||
"valid?",
|
||||
|
||||
/// GKERNEL
|
||||
// asm
|
||||
"(method 10 process)", "(method 14 dead-pool)",
|
||||
|
||||
/// GSTATE
|
||||
"enter-state", // stack pointer asm
|
||||
|
||||
/// MATH
|
||||
"rand-vu-init", "rand-vu",
|
||||
"rand-vu-nostep", // random hardware
|
||||
|
||||
// trig
|
||||
"sin-rad", // fpu acc
|
||||
"cos-rad", // fpu acc
|
||||
"atan-series-rad", // fpu acc
|
||||
|
||||
/// VECTOR-H
|
||||
"(method 3 vector)", // this function appears twice, which confuses the compiler.
|
||||
"vector4-dot", // fpu acc
|
||||
|
||||
"(method 3 profile-frame)", // double definition.
|
||||
|
||||
// dma-disasm
|
||||
"disasm-dma-list", // missing a single cast :(
|
||||
|
||||
// math camera
|
||||
"transform-point-vector!", "transform-point-qword!", "transform-point-vector-scale!",
|
||||
|
||||
// display-h
|
||||
"put-draw-env",
|
||||
|
||||
// geometry
|
||||
"calculate-basis-functions-vector!", // asm requiring manual rewrite
|
||||
"curve-evaluate!", // asm requiring manual rewrite
|
||||
"point-in-triangle-cross", // logior on floats manual fixup
|
||||
|
||||
// texture
|
||||
"(method 9 texture-page-dir)", // multiplication on pointers
|
||||
"adgif-shader<-texture-with-update!", // misrecognized bitfield stuff.
|
||||
|
||||
// asm
|
||||
"invalidate-cache-line",
|
||||
|
||||
// stats-h
|
||||
"(method 11 perf-stat)", "(method 12 perf-stat)",
|
||||
|
||||
// sprite-distorter
|
||||
"sprite-draw-distorters", // uses clipping flag.
|
||||
|
||||
// sync-info
|
||||
"(method 15 sync-info)", // needs display stuff first
|
||||
"(method 15 sync-info-eased)", // needs display stuff first
|
||||
"(method 15 sync-info-paused)", // needs display stuff first
|
||||
|
||||
// sparticle
|
||||
"lookup-part-group-pointer-by-name", // address of element in array issue
|
||||
|
||||
// ripple - calls an asm function
|
||||
"ripple-execute",
|
||||
|
||||
"get-task-status",
|
||||
|
||||
// aligner - return-from-thread, currently not supported
|
||||
"(method 9 align-control)",
|
||||
|
||||
// stat collection
|
||||
"start-perf-stat-collection", "end-perf-stat-collection",
|
||||
|
||||
// float to int
|
||||
"(method 10 bsp-header)",
|
||||
|
||||
// multiply defined.
|
||||
"(method 3 sprite-aux-list)",
|
||||
|
||||
// camera
|
||||
"slave-set-rotation!", "v-slrp2!", "v-slrp3!", // vector-dot involving the stack
|
||||
|
||||
// function returning float with a weird cast.
|
||||
"debug-menu-item-var-make-float",
|
||||
|
||||
// decompiler BUG
|
||||
"level-hint-task-process",
|
||||
|
||||
// anim-tester
|
||||
"(method 3 anim-tester)",
|
||||
"anim-tester-save-object-seqs" // anim-tester -- new basic on the stack
|
||||
],
|
||||
|
||||
"skip_compile_states": {
|
||||
}
|
||||
}
|
||||
+304
-521
@@ -1,214 +1,95 @@
|
||||
#include <common/link_types.h>
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "third-party/fmt/format.h"
|
||||
#include "common/log/log.h"
|
||||
#include "decompiler/Disasm/OpcodeInfo.h"
|
||||
#include "decompiler/config.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/json_util.h"
|
||||
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
||||
#include "common/util/diff.h"
|
||||
#include "goalc/compiler/Compiler.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include <common/util/json_util.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
// list of object files to ignore during reference checks
|
||||
const std::unordered_set<std::string> g_files_to_skip_compiling = {
|
||||
"timer", // accessing timer regs
|
||||
"display", // interrupt handlers
|
||||
"target-snowball", // screwed up labels, likely cut content
|
||||
// command line arguments
|
||||
struct OfflineTestArgs {
|
||||
bool dump_current_output = false;
|
||||
std::string iso_data_path;
|
||||
s32 max_files = INT32_MAX;
|
||||
};
|
||||
|
||||
// the functions we expect the decompiler to skip
|
||||
const std::unordered_set<std::string> g_functions_expected_to_reject = {
|
||||
// gcommon
|
||||
"quad-copy!", // asm mempcy
|
||||
// gkernel
|
||||
"set-to-run-bootstrap", // kernel context switch
|
||||
"throw", // manually sets fp/t9.
|
||||
"throw-dispatch", // restore context
|
||||
"(method 0 catch-frame)", // save context
|
||||
"(method 11 cpu-thread)", // kernel -> user context switch
|
||||
"(method 10 cpu-thread)", // user -> kernel context switch
|
||||
"reset-and-call", // kernel -> user
|
||||
"return-from-thread-dead", // kernel -> user
|
||||
"return-from-thread", // kernel -> user
|
||||
"return-from-exception", // ps2 exception -> ps2 user
|
||||
"run-function-in-process", // temp while stack vars aren't supported.
|
||||
// pskernel
|
||||
"kernel-check-hardwired-addresses", // ps2 ee kernel debug hook
|
||||
"kernel-read-function", // ps2 ee kernel debug hook
|
||||
"kernel-write-function", // ps2 ee kernel debug hook
|
||||
"kernel-copy-function", // ps2 ee kernel debug hook
|
||||
// math
|
||||
"rand-uint31-gen", // weird and terrible random generator
|
||||
// bounding-box
|
||||
"(method 9 bounding-box)", // handwritten asm loop
|
||||
"(method 14 bounding-box)", // handwritten asm loop
|
||||
// trig
|
||||
"exp", "atan0", "sincos!", "sincos-rad!",
|
||||
// matrix
|
||||
"(method 9 matrix)", // handwritten asm loop
|
||||
"matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!",
|
||||
// geometry
|
||||
"circle-circle-xz-intersect", // unused not bothering
|
||||
// dma-h
|
||||
"dma-count-until-done", // dma asm loop
|
||||
"dma-sync-with-count", "dma-send-no-scratch", "dma-sync-fast",
|
||||
// dma
|
||||
"symlink2", "symlink3",
|
||||
"dma-sync-hang", // handwritten asm
|
||||
"vector=", // asm branching
|
||||
// display
|
||||
"vblank-handler", // asm
|
||||
"vif1-handler", "vif1-handler-debug",
|
||||
// texture
|
||||
"adgif-shader<-texture-with-update!", // mips2c
|
||||
// sparticle
|
||||
"sp-launch-particles-var", "particle-adgif", "sp-init-fields!", "memcpy", "sp-process-block-2d",
|
||||
"sp-process-block-3d",
|
||||
// ripple - asm
|
||||
"ripple-execute-init", "ripple-create-wave-table", "ripple-apply-wave-table",
|
||||
"ripple-matrix-scale",
|
||||
/*!
|
||||
* Parse command line arguments.
|
||||
*/
|
||||
OfflineTestArgs parse_args(int argc, char* argv[]) {
|
||||
OfflineTestArgs result;
|
||||
|
||||
// collide-mesh-h
|
||||
"(method 11 collide-mesh-cache)", // asm
|
||||
for (int i = 1; i < argc; i++) {
|
||||
auto arg = std::string(argv[i]);
|
||||
if (arg == "--dump-mode") {
|
||||
result.dump_current_output = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// mood
|
||||
"update-mood-lava", // asm
|
||||
"update-mood-lightning", // asm
|
||||
if (arg == "--max-files") {
|
||||
i++;
|
||||
if (i >= argc) {
|
||||
fmt::print("--max-files must be followed by an integer\n");
|
||||
exit(1);
|
||||
}
|
||||
result.max_files = atoi(argv[i]);
|
||||
fmt::print("Limiting to {} files\n", result.max_files);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ambient
|
||||
"ambient-inspect", // asm, weird
|
||||
result.iso_data_path = arg;
|
||||
fmt::print("Using {} for ISO data\n", result.iso_data_path);
|
||||
}
|
||||
|
||||
// background
|
||||
"background-upload-vu0", "draw-node-cull",
|
||||
return result;
|
||||
}
|
||||
|
||||
// anim-tester
|
||||
"(method 3 anim-tester)",
|
||||
"anim-tester-save-object-seqs" // anim-tester -- new basic on the stack
|
||||
// json config file data (previously was in source of offline_test_main.cpp)
|
||||
struct OfflineTestConfig {
|
||||
std::vector<std::string> dgos;
|
||||
std::unordered_set<std::string> skip_compile_files;
|
||||
std::unordered_set<std::string> skip_compile_functions;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> skip_compile_states;
|
||||
};
|
||||
|
||||
const std::unordered_set<std::string> g_functions_to_skip_compiling = {
|
||||
/// GCOMMON
|
||||
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
|
||||
"abs", "ash", "min", "max", "lognor",
|
||||
// weird PS2 specific debug registers:
|
||||
"breakpoint-range-set!",
|
||||
// inline assembly
|
||||
"valid?",
|
||||
/*!
|
||||
* Read and parse the json config file, config.json, located in test/offline
|
||||
*/
|
||||
OfflineTestConfig parse_config() {
|
||||
auto json_file_path = file_util::get_file_path({"test", "offline", "config.json"});
|
||||
auto json = parse_commented_json(file_util::read_text_file(json_file_path), json_file_path);
|
||||
OfflineTestConfig result;
|
||||
result.dgos = json["dgos"].get<std::vector<std::string>>();
|
||||
result.skip_compile_files = json["skip_compile_files"].get<std::unordered_set<std::string>>();
|
||||
result.skip_compile_functions =
|
||||
json["skip_compile_functions"].get<std::unordered_set<std::string>>();
|
||||
result.skip_compile_states =
|
||||
json["skip_compile_states"]
|
||||
.get<std::unordered_map<std::string, std::unordered_set<std::string>>>();
|
||||
|
||||
/// GKERNEL
|
||||
// asm
|
||||
"(method 10 process)", "(method 14 dead-pool)",
|
||||
if (!result.skip_compile_states.empty()) {
|
||||
fmt::print("skip_compile_states wasn't emtpy. It's not implemented in the decompiler yet\n");
|
||||
exit(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// GSTATE
|
||||
"enter-state", // stack pointer asm
|
||||
|
||||
/// MATH
|
||||
"rand-vu-init", "rand-vu",
|
||||
"rand-vu-nostep", // random hardware
|
||||
|
||||
// trig
|
||||
"sin-rad", // fpu acc
|
||||
"cos-rad", // fpu acc
|
||||
"atan-series-rad", // fpu acc
|
||||
|
||||
/// VECTOR-H
|
||||
"(method 3 vector)", // this function appears twice, which confuses the compiler.
|
||||
"vector4-dot", // fpu acc
|
||||
|
||||
"(method 3 profile-frame)", // double definition.
|
||||
|
||||
// dma-disasm
|
||||
"disasm-dma-list", // missing a single cast :(
|
||||
|
||||
// math camera
|
||||
"transform-point-vector!", "transform-point-qword!", "transform-point-vector-scale!",
|
||||
|
||||
// display-h
|
||||
"put-draw-env",
|
||||
|
||||
// geometry
|
||||
"calculate-basis-functions-vector!", // asm requiring manual rewrite
|
||||
"curve-evaluate!", // asm requiring manual rewrite
|
||||
"point-in-triangle-cross", // logior on floats manual fixup
|
||||
|
||||
// texture
|
||||
"(method 9 texture-page-dir)", // multiplication on pointers
|
||||
"adgif-shader<-texture-with-update!", // misrecognized bitfield stuff.
|
||||
|
||||
// asm
|
||||
"invalidate-cache-line",
|
||||
|
||||
// stats-h
|
||||
"(method 11 perf-stat)", "(method 12 perf-stat)",
|
||||
|
||||
// sprite-distorter
|
||||
"sprite-draw-distorters", // uses clipping flag.
|
||||
|
||||
// sync-info
|
||||
"(method 15 sync-info)", // needs display stuff first
|
||||
"(method 15 sync-info-eased)", // needs display stuff first
|
||||
"(method 15 sync-info-paused)", // needs display stuff first
|
||||
|
||||
// sparticle
|
||||
"lookup-part-group-pointer-by-name", // address of element in array issue
|
||||
|
||||
// ripple - calls an asm function
|
||||
"ripple-execute",
|
||||
|
||||
"get-task-status",
|
||||
|
||||
// aligner - return-from-thread, currently not supported
|
||||
"(method 9 align-control)",
|
||||
|
||||
// stat collection
|
||||
"start-perf-stat-collection", "end-perf-stat-collection",
|
||||
|
||||
// float to int
|
||||
"(method 10 bsp-header)",
|
||||
|
||||
// multiply defined.
|
||||
"(method 3 sprite-aux-list)",
|
||||
|
||||
// camera
|
||||
"slave-set-rotation!", "v-slrp2!", "v-slrp3!", // vector-dot involving the stack
|
||||
|
||||
// function returning float with a weird cast.
|
||||
"debug-menu-item-var-make-float",
|
||||
|
||||
// decompiler BUG
|
||||
"level-hint-task-process",
|
||||
|
||||
// anim-tester
|
||||
"(method 3 anim-tester)",
|
||||
"anim-tester-save-object-seqs" // anim-tester -- new basic on the stack
|
||||
struct DecompilerFile {
|
||||
std::filesystem::path path;
|
||||
std::string name_in_dgo;
|
||||
std::string unique_name;
|
||||
std::string reference;
|
||||
};
|
||||
|
||||
// default location for the data. It can be changed with a command line argument.
|
||||
std::string g_iso_data_path = "";
|
||||
|
||||
bool g_dump_mode = false;
|
||||
|
||||
struct decomp_meta {
|
||||
std::string fileName;
|
||||
std::string fileNameOverride;
|
||||
fs::path filePath;
|
||||
};
|
||||
|
||||
std::vector<decomp_meta> g_object_files_to_decompile_or_ref_check;
|
||||
|
||||
std::vector<std::string> dgos = {"CGO/KERNEL.CGO", "CGO/ENGINE.CGO", "CGO/GAME.CGO", "DGO/BEA.DGO",
|
||||
"DGO/INT.DGO", "DGO/VI1.DGO", "DGO/VI2.DGO", "DGO/VI3.DGO",
|
||||
"DGO/CIT.DGO", "DGO/MIS.DGO", "DGO/JUB.DGO", "DGO/SUN.DGO",
|
||||
"DGO/DEM.DGO", "DGO/FIN.DGO", "DGO/JUN.DGO", "DGO/FIC.DGO",
|
||||
"DGO/SNO.DGO", "DGO/SWA.DGO", "DGO/MAI.DGO", "DGO/ROB.DGO",
|
||||
"DGO/LAV.DGO", "DGO/OGR.DGO", "DGO/TRA.DGO", "DGO/ROL.DGO"};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string replaceFirstOccurrence(std::string& s,
|
||||
const std::string& toReplace,
|
||||
const std::string& replaceWith) {
|
||||
@@ -218,13 +99,10 @@ std::string replaceFirstOccurrence(std::string& s,
|
||||
return s.replace(pos, toReplace.length(), replaceWith);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
lg::initialize();
|
||||
std::vector<DecompilerFile> find_files(const std::vector<std::string>& dgos) {
|
||||
std::vector<DecompilerFile> result;
|
||||
|
||||
// Determine the files to decompile and reference check by scanning the reference directory
|
||||
// All relevant files are assumed to end with `_REF.g[c|d]`
|
||||
// First rough order them
|
||||
std::vector<decomp_meta> reference_files_rough_order;
|
||||
std::unordered_map<std::string, fs::path> files_with_ref;
|
||||
for (auto& p : fs::recursive_directory_iterator(
|
||||
file_util::get_file_path({"test", "decompiler", "reference"}))) {
|
||||
if (p.is_regular_file()) {
|
||||
@@ -233,339 +111,156 @@ int main(int argc, char** argv) {
|
||||
continue;
|
||||
}
|
||||
std::string object_name = replaceFirstOccurrence(file_name, "_REF", "");
|
||||
reference_files_rough_order.push_back({object_name, "", p.path()});
|
||||
files_with_ref.insert({object_name, p.path()});
|
||||
}
|
||||
}
|
||||
|
||||
fmt::print(" Found {} reference files\n", files_with_ref.size());
|
||||
|
||||
// use the all_objs.json file to place them in the correct build order
|
||||
auto j = parse_commented_json(
|
||||
file_util::read_text_file(file_util::get_file_path({"goal_src", "build", "all_objs.json"})),
|
||||
"all_objs.json");
|
||||
|
||||
std::unordered_set<std::string> matched_files;
|
||||
for (auto& x : j) {
|
||||
auto mapped_name = x[0].get<std::string>();
|
||||
auto unique_name = x[0].get<std::string>();
|
||||
|
||||
std::vector<std::string> dgoList = x[3].get<std::vector<std::string>>();
|
||||
for (auto& p : reference_files_rough_order) {
|
||||
if (p.fileName == mapped_name) {
|
||||
// Check to see if we've included atleast one of the DGO/CGOs in our hardcoded list
|
||||
// If not BLOW UP
|
||||
bool dgoValidated = false;
|
||||
for (int i = 0; i < (int)dgoList.size(); i++) {
|
||||
std::string& dgo = dgoList.at(i);
|
||||
// can either be in the DGO or CGO folder, and can either end with .CGO or .DGO
|
||||
if (std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.DGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.CGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.DGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.CGO", dgo)) != dgos.end()) {
|
||||
dgoValidated = true;
|
||||
}
|
||||
// for (auto& p : reference_files_rough_order) {
|
||||
auto it = files_with_ref.find(unique_name);
|
||||
if (it != files_with_ref.end()) {
|
||||
// Check to see if we've included atleast one of the DGO/CGOs in our hardcoded list
|
||||
// If not BLOW UP
|
||||
bool dgoValidated = false;
|
||||
for (int i = 0; i < (int)dgoList.size(); i++) {
|
||||
std::string& dgo = dgoList.at(i);
|
||||
// can either be in the DGO or CGO folder, and can either end with .CGO or .DGO
|
||||
if (std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.DGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.CGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.DGO", dgo)) != dgos.end() ||
|
||||
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.CGO", dgo)) != dgos.end()) {
|
||||
dgoValidated = true;
|
||||
}
|
||||
if (!dgoValidated) {
|
||||
fmt::print(
|
||||
"File [{}] is in the following DGOs [{}], and not one of these is in our list! Add "
|
||||
"it!\n",
|
||||
mapped_name, fmt::join(dgoList, ", "));
|
||||
return 1;
|
||||
}
|
||||
// Hack for working around multi-DGO files
|
||||
if (mapped_name != x[1]) {
|
||||
p.fileNameOverride = x[1];
|
||||
}
|
||||
g_object_files_to_decompile_or_ref_check.push_back(p);
|
||||
break;
|
||||
}
|
||||
if (!dgoValidated) {
|
||||
fmt::print(
|
||||
"File [{}] is in the following DGOs [{}], and not one of these is in our list! Add "
|
||||
"it!\n",
|
||||
unique_name, fmt::join(dgoList, ", "));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
DecompilerFile file;
|
||||
file.path = it->second;
|
||||
file.unique_name = it->first;
|
||||
file.name_in_dgo = x[1];
|
||||
result.push_back(file);
|
||||
matched_files.insert(unique_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_files.size() != files_with_ref.size()) {
|
||||
fmt::print("Error: some REF files were not matched to files in all_objs.json:\n");
|
||||
for (auto& f : files_with_ref) {
|
||||
if (matched_files.count(f.first) == 0) {
|
||||
fmt::print(" {}\n", f.first);
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// look for an argument that's not a gtest option
|
||||
bool got_arg = false;
|
||||
int max_files = -1;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
auto arg = std::string(argv[i]);
|
||||
if (arg == "--dump-mode") {
|
||||
g_dump_mode = true;
|
||||
continue;
|
||||
}
|
||||
if (arg == "--max-files") {
|
||||
i++;
|
||||
assert(i < argc);
|
||||
max_files = atoi(argv[i]);
|
||||
printf("Limiting to %d files\n", max_files);
|
||||
}
|
||||
if (arg.length() > 2 && arg[0] == '-' && arg[1] == '-') {
|
||||
continue;
|
||||
}
|
||||
if (got_arg) {
|
||||
printf("You can only specify a single path for ISO data\n");
|
||||
return 1;
|
||||
}
|
||||
g_iso_data_path = arg;
|
||||
lg::warn("Using path {} for iso_data", g_iso_data_path);
|
||||
got_arg = true;
|
||||
}
|
||||
|
||||
if (max_files >= 0) {
|
||||
if ((int)g_object_files_to_decompile_or_ref_check.size() > max_files) {
|
||||
g_object_files_to_decompile_or_ref_check.erase(
|
||||
g_object_files_to_decompile_or_ref_check.begin() + max_files,
|
||||
g_object_files_to_decompile_or_ref_check.end());
|
||||
}
|
||||
}
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
return result;
|
||||
}
|
||||
|
||||
class OfflineDecompilation : public ::testing::Test {
|
||||
protected:
|
||||
static std::unique_ptr<decompiler::ObjectFileDB> db;
|
||||
static std::unique_ptr<decompiler::Config> config;
|
||||
|
||||
static std::unique_ptr<std::unordered_map<std::string, std::string>> final_output_cache;
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// global setup
|
||||
file_util::init_crc();
|
||||
decompiler::init_opcode_info();
|
||||
config = std::make_unique<decompiler::Config>(decompiler::read_config_file(
|
||||
file_util::get_file_path({"decompiler", "config", "jak1_ntsc_black_label.jsonc"})));
|
||||
|
||||
std::unordered_set<std::string> object_files;
|
||||
for (auto& p : g_object_files_to_decompile_or_ref_check) {
|
||||
std::string fileName = p.fileNameOverride == "" ? p.fileName : p.fileNameOverride;
|
||||
object_files.insert(fileName);
|
||||
}
|
||||
config->allowed_objects = object_files;
|
||||
// don't try to do this because we can't write the file
|
||||
config->generate_symbol_definition_map = false;
|
||||
|
||||
std::vector<std::string> dgo_paths;
|
||||
if (g_iso_data_path.empty()) {
|
||||
for (auto& x : dgos) {
|
||||
dgo_paths.push_back(file_util::get_file_path({"iso_data", x}));
|
||||
}
|
||||
} else {
|
||||
for (auto& x : dgos) {
|
||||
dgo_paths.push_back(file_util::combine_path(g_iso_data_path, x));
|
||||
}
|
||||
}
|
||||
|
||||
db = std::make_unique<decompiler::ObjectFileDB>(dgo_paths, config->obj_file_name_map_file,
|
||||
std::vector<std::string>{},
|
||||
std::vector<std::string>{}, *config);
|
||||
|
||||
// basic processing to find functions/data/disassembly
|
||||
db->process_link_data(*config);
|
||||
db->find_code(*config);
|
||||
db->process_labels();
|
||||
|
||||
// fancy decompilation.
|
||||
db->analyze_functions_ir2({}, *config, true);
|
||||
|
||||
final_output_cache = std::make_unique<std::unordered_map<std::string, std::string>>();
|
||||
}
|
||||
|
||||
static void TearDownTestCase() {
|
||||
db.reset();
|
||||
config.reset();
|
||||
final_output_cache.reset();
|
||||
}
|
||||
struct Decompiler {
|
||||
std::unique_ptr<decompiler::ObjectFileDB> db;
|
||||
std::unique_ptr<decompiler::Config> config;
|
||||
};
|
||||
|
||||
std::unique_ptr<decompiler::ObjectFileDB> OfflineDecompilation::db;
|
||||
std::unique_ptr<decompiler::Config> OfflineDecompilation::config;
|
||||
std::unique_ptr<std::unordered_map<std::string, std::string>>
|
||||
OfflineDecompilation::final_output_cache;
|
||||
Decompiler setup_decompiler(const std::vector<DecompilerFile>& files,
|
||||
const OfflineTestArgs& args,
|
||||
const OfflineTestConfig& offline_config) {
|
||||
Decompiler dc;
|
||||
file_util::init_crc();
|
||||
decompiler::init_opcode_info();
|
||||
dc.config = std::make_unique<decompiler::Config>(decompiler::read_config_file(
|
||||
file_util::get_file_path({"decompiler", "config", "jak1_ntsc_black_label.jsonc"})));
|
||||
|
||||
/*!
|
||||
* Check that the most basic disassembly into files/functions/instructions has succeeded.
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, CheckBasicDecode) {
|
||||
int obj_count = 0;
|
||||
db->for_each_obj([&](decompiler::ObjectFileData& obj) {
|
||||
obj_count++;
|
||||
auto& stats = obj.linked_data.stats;
|
||||
// make sure we decoded all instructions
|
||||
EXPECT_EQ(stats.code_bytes / 4, stats.decoded_ops);
|
||||
// make sure all FP uses are properly recognized
|
||||
EXPECT_EQ(stats.n_fp_reg_use, stats.n_fp_reg_use_resolved);
|
||||
});
|
||||
|
||||
EXPECT_EQ(obj_count, config->allowed_objects.size());
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, AsmFunction) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (func.suspected_asm) {
|
||||
if (g_functions_expected_to_reject.find(func.name()) ==
|
||||
g_functions_expected_to_reject.end()) {
|
||||
lg::error("Function {} was marked as asm, but wasn't expected.", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass CFG build stage.
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, CfgBuild) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.cfg || !func.cfg->is_fully_resolved()) {
|
||||
lg::error("Function {} failed cfg", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass the atomic op construction stage
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, AtomicOp) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.atomic_ops || !func.ir2.atomic_ops_succeeded) {
|
||||
lg::error("Function {} failed atomic ops", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass the type analysis stage
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, TypeAnalysis) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_type_analysis() || !func.ir2.env.types_succeeded) {
|
||||
lg::error("Function {} failed types", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, RegisterUse) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_reg_use()) {
|
||||
lg::error("Function {} failed reg use", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, VariableSSA) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_local_vars()) {
|
||||
lg::error("Function {} failed ssa", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, Structuring) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.top_form) {
|
||||
lg::error("Function {} failed structuring", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, Expressions) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.expressions_succeeded) {
|
||||
lg::error("Function {} failed expressions", func.name());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void strip_trailing_newlines(std::string& in) {
|
||||
while (!in.empty() && in.back() == '\n') {
|
||||
in.pop_back();
|
||||
// modify the config
|
||||
std::unordered_set<std::string> object_files;
|
||||
for (auto& file : files) {
|
||||
object_files.insert(file.name_in_dgo); // todo, make this work with unique_name
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(OfflineDecompilation, Reference) {
|
||||
for (decomp_meta& file : g_object_files_to_decompile_or_ref_check) {
|
||||
std::string fileName = file.fileNameOverride == "" ? file.fileName : file.fileNameOverride;
|
||||
auto& obj_l = db->obj_files_by_name.at(fileName);
|
||||
ASSERT_EQ(obj_l.size(), 1);
|
||||
dc.config->allowed_objects = object_files;
|
||||
// don't try to do this because we can't write the file
|
||||
dc.config->generate_symbol_definition_map = false;
|
||||
|
||||
std::string src = db->ir2_final_out(obj_l.at(0));
|
||||
|
||||
lg::info("Comparing {}...", fileName);
|
||||
|
||||
// NOTE - currently only handles .gc files!
|
||||
auto reference = file_util::read_text_file(file.filePath.string());
|
||||
|
||||
bool can_cache = true;
|
||||
for (auto& func_list : obj_l.at(0).linked_data.functions_by_seg) {
|
||||
for (auto& func : func_list) {
|
||||
if (g_functions_to_skip_compiling.find(func.name()) !=
|
||||
g_functions_to_skip_compiling.end()) {
|
||||
can_cache = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::vector<std::string> dgo_paths;
|
||||
if (args.iso_data_path.empty()) {
|
||||
for (auto& x : offline_config.dgos) {
|
||||
dgo_paths.push_back(file_util::get_file_path({"iso_data", x}));
|
||||
}
|
||||
|
||||
if (can_cache) {
|
||||
EXPECT_EQ(final_output_cache->count(fileName), 0);
|
||||
final_output_cache->insert({file.fileName, src});
|
||||
}
|
||||
|
||||
strip_trailing_newlines(reference);
|
||||
strip_trailing_newlines(src);
|
||||
|
||||
if (g_dump_mode) {
|
||||
if (reference != src) {
|
||||
file_util::create_dir_if_needed("./failures");
|
||||
file_util::write_text_file("./failures/" + file.fileName + "_REF.gc", src);
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(reference, src);
|
||||
} else {
|
||||
for (auto& x : offline_config.dgos) {
|
||||
dgo_paths.push_back(file_util::combine_path(args.iso_data_path, x));
|
||||
}
|
||||
}
|
||||
|
||||
dc.db = std::make_unique<decompiler::ObjectFileDB>(dgo_paths, dc.config->obj_file_name_map_file,
|
||||
std::vector<std::string>{},
|
||||
std::vector<std::string>{}, *dc.config);
|
||||
|
||||
std::unordered_set<std::string> db_files;
|
||||
for (auto& files_by_name : dc.db->obj_files_by_name) {
|
||||
for (auto& f : files_by_name.second) {
|
||||
db_files.insert(f.to_unique_name());
|
||||
}
|
||||
}
|
||||
|
||||
if (db_files.size() != files.size()) {
|
||||
fmt::print("DB file error.\n");
|
||||
for (auto& f : files) {
|
||||
if (!db_files.count(f.unique_name)) {
|
||||
fmt::print("didn't find {}\n", f.unique_name);
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return dc;
|
||||
}
|
||||
|
||||
void disassemble(Decompiler& dc) {
|
||||
dc.db->process_link_data(*dc.config);
|
||||
dc.db->find_code(*dc.config);
|
||||
dc.db->process_labels();
|
||||
}
|
||||
|
||||
void decompile(Decompiler& dc, const OfflineTestConfig& config) {
|
||||
dc.db->analyze_functions_ir2({}, *dc.config, config.skip_compile_functions);
|
||||
}
|
||||
|
||||
std::string strip_trailing_newlines(const std::string& in) {
|
||||
std::string out = in;
|
||||
while (!out.empty() && out.back() == '\n') {
|
||||
out.pop_back();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
decompiler::ObjectFileData& get_data(Decompiler& dc,
|
||||
const std::string& unique_name,
|
||||
const std::string& name_in_dgo) {
|
||||
auto& files = dc.db->obj_files_by_name.at(name_in_dgo);
|
||||
auto it = std::find_if(files.begin(), files.end(), [&](const decompiler::ObjectFileData& data) {
|
||||
return data.to_unique_name() == unique_name;
|
||||
});
|
||||
assert(it != files.end());
|
||||
return *it;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int line_count(const std::string& str) {
|
||||
int result = 0;
|
||||
for (auto& c : str) {
|
||||
@@ -575,9 +270,47 @@ int line_count(const std::string& str) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(OfflineDecompilation, Compile) {
|
||||
struct CompareResult {
|
||||
std::vector<std::string> failing_files;
|
||||
int total_files = 0;
|
||||
int ok_files = 0;
|
||||
int total_lines = 0;
|
||||
|
||||
bool total_pass = true;
|
||||
};
|
||||
|
||||
CompareResult compare(Decompiler& dc, const std::vector<DecompilerFile>& refs, bool dump_mode) {
|
||||
CompareResult compare_result;
|
||||
|
||||
for (const auto& file : refs) {
|
||||
auto& data = get_data(dc, file.unique_name, file.name_in_dgo);
|
||||
std::string result = strip_trailing_newlines(data.full_output);
|
||||
std::string ref = strip_trailing_newlines(file_util::read_text_file(file.path.string()));
|
||||
compare_result.total_files++;
|
||||
compare_result.total_lines += line_count(result);
|
||||
if (result != ref) {
|
||||
compare_result.failing_files.push_back(file.unique_name);
|
||||
compare_result.total_pass = false;
|
||||
fmt::print("Reference test failure on {}:\n", file.unique_name);
|
||||
fmt::print("{}\n", diff_strings(result, ref));
|
||||
|
||||
if (dump_mode) {
|
||||
file_util::create_dir_if_needed("./failures");
|
||||
file_util::write_text_file("./failures/" + file.unique_name + "_REF.gc", result);
|
||||
}
|
||||
} else {
|
||||
compare_result.ok_files++;
|
||||
}
|
||||
}
|
||||
|
||||
return compare_result;
|
||||
}
|
||||
|
||||
bool compile(Decompiler& dc,
|
||||
const std::vector<DecompilerFile>& refs,
|
||||
const OfflineTestConfig& config) {
|
||||
fmt::print("Setting up compiler...\n");
|
||||
Compiler compiler;
|
||||
|
||||
compiler.run_front_end_on_file({"decompiler", "config", "all-types.gc"});
|
||||
@@ -585,29 +318,79 @@ TEST_F(OfflineDecompilation, Compile) {
|
||||
|
||||
Timer timer;
|
||||
int total_lines = 0;
|
||||
for (decomp_meta& file : g_object_files_to_decompile_or_ref_check) {
|
||||
std::string fileName = file.fileNameOverride == "" ? file.fileName : file.fileNameOverride;
|
||||
if (g_files_to_skip_compiling.find(fileName) != g_files_to_skip_compiling.end()) {
|
||||
for (const auto& file : refs) {
|
||||
if (config.skip_compile_files.count(file.name_in_dgo)) {
|
||||
fmt::print("Skipping {}\n", file.name_in_dgo);
|
||||
continue;
|
||||
}
|
||||
|
||||
lg::info("Compiling {}...", fileName);
|
||||
fmt::print("Compiling {}...\n", file.unique_name);
|
||||
|
||||
auto& obj_l = db->obj_files_by_name.at(fileName);
|
||||
ASSERT_EQ(obj_l.size(), 1);
|
||||
auto& data = get_data(dc, file.unique_name, file.name_in_dgo);
|
||||
|
||||
const auto& cache = final_output_cache->find(fileName);
|
||||
if (cache != final_output_cache->end()) {
|
||||
const auto& src = cache->second;
|
||||
total_lines += line_count(src);
|
||||
compiler.run_full_compiler_on_string_no_save(src);
|
||||
} else {
|
||||
auto src = db->ir2_final_out(obj_l.at(0), g_functions_to_skip_compiling);
|
||||
try {
|
||||
const auto& src = data.output_with_skips;
|
||||
total_lines += line_count(src);
|
||||
compiler.run_full_compiler_on_string_no_save(src);
|
||||
} catch (const std::exception& e) {
|
||||
fmt::print("Compiler exception: {}\n", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto time = timer.getSeconds();
|
||||
lg::info("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
|
||||
(float)total_lines / time);
|
||||
fmt::print("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
|
||||
(float)total_lines / time);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
fmt::print("Offline Decompiler Test 2\n");
|
||||
lg::initialize();
|
||||
|
||||
fmt::print("Reading config...\n");
|
||||
auto args = parse_args(argc, argv);
|
||||
auto config = parse_config();
|
||||
|
||||
fmt::print("Finding files...\n");
|
||||
auto files = find_files(config.dgos);
|
||||
if (args.max_files < (int)files.size()) {
|
||||
files.erase(files.begin() + args.max_files, files.end());
|
||||
}
|
||||
|
||||
fmt::print("Setting up decompiler and loading files...\n");
|
||||
auto decompiler = setup_decompiler(files, args, config);
|
||||
|
||||
fmt::print("Disassembling files...\n");
|
||||
disassemble(decompiler);
|
||||
|
||||
fmt::print("Decompiling...\n");
|
||||
decompile(decompiler, config);
|
||||
|
||||
fmt::print("Comparing...\n");
|
||||
auto compare_result = compare(decompiler, files, args.dump_current_output);
|
||||
fmt::print("Compared {} lines. {}/{} files passed.\n", compare_result.total_lines,
|
||||
compare_result.ok_files, compare_result.total_files);
|
||||
|
||||
if (!compare_result.failing_files.empty()) {
|
||||
fmt::print("Failing files:\n");
|
||||
for (auto& f : compare_result.failing_files) {
|
||||
fmt::print(" {}\n", f);
|
||||
}
|
||||
}
|
||||
|
||||
bool compile_result = compile(decompiler, files, config);
|
||||
|
||||
if (compare_result.total_pass && compile_result) {
|
||||
fmt::print("Pass!\n");
|
||||
return 0;
|
||||
} else {
|
||||
if (!compile_result) {
|
||||
fmt::print("Compilation failed.\n");
|
||||
}
|
||||
if (!compare_result.total_pass) {
|
||||
fmt::print("Comparison failed.\n");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user