2021-03-03 15:42:55 -05:00
|
|
|
#include <common/link_types.h>
|
|
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "common/log/log.h"
|
|
|
|
#include "decompiler/Disasm/OpcodeInfo.h"
|
|
|
|
#include "decompiler/config.h"
|
|
|
|
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
|
|
|
#include "goalc/compiler/Compiler.h"
|
2021-03-24 19:16:31 -04:00
|
|
|
#include "common/util/Timer.h"
|
2021-05-09 17:03:58 -04:00
|
|
|
#include <common/util/json_util.h>
|
|
|
|
|
|
|
|
namespace fs = std::filesystem;
|
2021-03-03 15:42:55 -05:00
|
|
|
|
|
|
|
namespace {
|
2021-05-09 17:03:58 -04:00
|
|
|
|
|
|
|
// list of object files to ignore during reference checks
|
2021-05-16 21:07:22 -04:00
|
|
|
const std::unordered_set<std::string> g_files_to_skip_compiling = {
|
2021-09-28 20:42:00 -04:00
|
|
|
"timer", // accessing timer regs
|
|
|
|
"display", // interrupt handlers
|
|
|
|
"target-snowball", // screwed up labels, likely cut content
|
2021-05-01 15:51:53 -04:00
|
|
|
};
|
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
// the functions we expect the decompiler to skip
|
2021-05-16 21:07:22 -04:00
|
|
|
const std::unordered_set<std::string> g_functions_expected_to_reject = {
|
2021-03-03 15:42:55 -05:00
|
|
|
// 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
|
2021-03-13 16:10:39 -05:00
|
|
|
"run-function-in-process", // temp while stack vars aren't supported.
|
2021-03-03 15:42:55 -05:00
|
|
|
// 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
|
2021-03-24 19:16:31 -04:00
|
|
|
"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
|
2021-05-15 10:38:14 -04:00
|
|
|
// trig
|
2021-07-04 18:25:08 -04:00
|
|
|
"exp", "atan0", "sincos!", "sincos-rad!",
|
2021-03-27 15:18:59 -04:00
|
|
|
// matrix
|
|
|
|
"(method 9 matrix)", // handwritten asm loop
|
2021-07-04 18:25:08 -04:00
|
|
|
"matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!",
|
2021-07-05 16:07:07 -04:00
|
|
|
// geometry
|
|
|
|
"circle-circle-xz-intersect", // unused not bothering
|
2021-04-11 16:07:01 -04:00
|
|
|
// dma-h
|
|
|
|
"dma-count-until-done", // dma asm loop
|
2021-07-04 18:25:08 -04:00
|
|
|
"dma-sync-with-count", "dma-send-no-scratch", "dma-sync-fast",
|
2021-04-11 16:07:01 -04:00
|
|
|
// dma
|
2021-07-04 18:25:08 -04:00
|
|
|
"symlink2", "symlink3",
|
2021-06-14 20:46:54 -04:00
|
|
|
"dma-sync-hang", // handwritten asm
|
|
|
|
"vector=", // asm branching
|
2021-05-05 17:38:16 -04:00
|
|
|
// display
|
2021-05-01 15:51:53 -04:00
|
|
|
"vblank-handler", // asm
|
2021-07-04 18:25:08 -04:00
|
|
|
"vif1-handler", "vif1-handler-debug",
|
2021-09-28 21:17:38 -04:00
|
|
|
// sparticle
|
|
|
|
"sp-launch-particles-var", "particle-adgif", "sp-init-fields!", "memcpy", "sp-process-block-2d",
|
|
|
|
"sp-process-block-3d",
|
2021-05-26 19:40:12 -04:00
|
|
|
// ripple - asm
|
2021-07-04 18:25:08 -04:00
|
|
|
"ripple-execute-init", "ripple-create-wave-table", "ripple-apply-wave-table",
|
2021-05-26 19:40:12 -04:00
|
|
|
"ripple-matrix-scale",
|
2021-07-30 22:18:35 -04:00
|
|
|
|
2021-06-05 23:55:36 -04:00
|
|
|
// collide-mesh-h
|
|
|
|
"(method 11 collide-mesh-cache)", // asm
|
2021-06-14 20:46:54 -04:00
|
|
|
|
2021-08-01 16:11:22 -04:00
|
|
|
// mood
|
|
|
|
"update-mood-lava", // asm
|
|
|
|
"update-mood-lightning", // asm
|
|
|
|
|
2021-09-21 18:40:38 -04:00
|
|
|
// ambient
|
2021-09-28 19:24:09 -04:00
|
|
|
"ambient-inspect", // asm, weird
|
|
|
|
|
|
|
|
// background
|
|
|
|
"background-upload-vu0", "draw-node-cull"};
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-05-16 21:07:22 -04:00
|
|
|
const std::unordered_set<std::string> g_functions_to_skip_compiling = {
|
2021-03-24 19:16:31 -04:00
|
|
|
/// GCOMMON
|
2021-03-03 15:42:55 -05:00
|
|
|
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
|
2021-07-11 16:19:03 -04:00
|
|
|
"abs", "ash", "min", "max", "lognor",
|
2021-03-06 20:16:48 -05:00
|
|
|
// weird PS2 specific debug registers:
|
|
|
|
"breakpoint-range-set!",
|
|
|
|
// inline assembly
|
2021-03-13 16:10:39 -05:00
|
|
|
"valid?",
|
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
/// GKERNEL
|
2021-03-14 16:11:42 -04:00
|
|
|
// asm
|
2021-08-22 20:12:47 -04:00
|
|
|
"(method 10 process)", "(method 14 dead-pool)",
|
2021-03-22 20:04:00 -04:00
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
/// GSTATE
|
2021-06-09 21:35:13 -04:00
|
|
|
"enter-state", // stack pointer asm
|
2021-03-14 16:11:42 -04:00
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
/// MATH
|
2021-07-11 16:19:03 -04:00
|
|
|
"rand-vu-init", "rand-vu",
|
2021-05-26 19:40:12 -04:00
|
|
|
"rand-vu-nostep", // random hardware
|
2021-03-24 19:16:31 -04:00
|
|
|
|
2021-05-15 10:38:14 -04:00
|
|
|
// trig
|
2021-07-25 15:30:37 -04:00
|
|
|
"sin-rad", // fpu acc
|
|
|
|
"cos-rad", // fpu acc
|
|
|
|
"atan-series-rad", // fpu acc
|
2021-05-15 10:38:14 -04:00
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
/// VECTOR-H
|
2021-03-25 16:02:48 -04:00
|
|
|
"(method 3 vector)", // this function appears twice, which confuses the compiler.
|
|
|
|
"vector4-dot", // fpu acc
|
2021-04-02 11:35:14 -04:00
|
|
|
|
2021-04-11 16:07:01 -04:00
|
|
|
"(method 3 profile-frame)", // double definition.
|
|
|
|
|
2021-04-25 14:48:54 -04:00
|
|
|
// dma-disasm
|
2021-04-26 21:40:08 -04:00
|
|
|
"disasm-dma-list", // missing a single cast :(
|
|
|
|
|
2021-05-15 20:08:08 -04:00
|
|
|
// math camera
|
2021-07-11 16:19:03 -04:00
|
|
|
"transform-point-vector!", "transform-point-qword!", "transform-point-vector-scale!",
|
2021-05-15 20:08:08 -04:00
|
|
|
|
2021-04-27 19:40:14 -04:00
|
|
|
// display-h
|
|
|
|
"put-draw-env",
|
|
|
|
|
2021-07-05 16:07:07 -04:00
|
|
|
// geometry
|
|
|
|
"calculate-basis-functions-vector!", // asm requiring manual rewrite
|
|
|
|
"curve-evaluate!", // asm requiring manual rewrite
|
|
|
|
"point-in-triangle-cross", // logior on floats manual fixup
|
|
|
|
|
2021-07-09 22:20:37 -04:00
|
|
|
// texture
|
|
|
|
"(method 9 texture-page-dir)", // multiplication on pointers
|
|
|
|
"adgif-shader<-texture-with-update!", // misrecognized bitfield stuff.
|
|
|
|
|
2021-05-16 21:07:22 -04:00
|
|
|
// asm
|
|
|
|
"invalidate-cache-line",
|
|
|
|
|
2021-07-19 20:49:33 -04:00
|
|
|
// stats-h
|
|
|
|
"(method 11 perf-stat)", "(method 12 perf-stat)",
|
|
|
|
|
2021-08-13 18:50:29 -04:00
|
|
|
// sprite-distorter
|
|
|
|
"sprite-draw-distorters", // uses clipping flag.
|
|
|
|
|
2021-04-10 21:17:36 -04:00
|
|
|
// 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
|
2021-05-26 19:40:12 -04:00
|
|
|
|
2021-09-28 21:17:38 -04:00
|
|
|
// sparticle
|
|
|
|
"lookup-part-group-pointer-by-name", // address of element in array issue
|
|
|
|
|
2021-05-26 19:40:12 -04:00
|
|
|
// ripple - calls an asm function
|
|
|
|
"ripple-execute",
|
2021-07-03 16:18:41 -04:00
|
|
|
|
|
|
|
"get-task-status",
|
2021-07-11 16:19:03 -04:00
|
|
|
|
|
|
|
// aligner - return-from-thread, currently not supported
|
2021-07-23 18:30:49 -04:00
|
|
|
"(method 9 align-control)",
|
|
|
|
|
2021-07-25 15:30:37 -04:00
|
|
|
// stat collection
|
|
|
|
"start-perf-stat-collection", "end-perf-stat-collection",
|
|
|
|
|
|
|
|
// float to int
|
|
|
|
"(method 10 bsp-header)",
|
|
|
|
|
2021-07-26 20:02:16 -04:00
|
|
|
// multiply defined.
|
|
|
|
"(method 3 sprite-aux-list)",
|
|
|
|
|
2021-07-30 22:18:35 -04:00
|
|
|
// camera
|
|
|
|
"slave-set-rotation!", "v-slrp2!", "v-slrp3!", // vector-dot involving the stack
|
|
|
|
|
2021-08-29 11:13:06 -04:00
|
|
|
// function returning float with a weird cast.
|
2021-09-21 18:40:38 -04:00
|
|
|
"debug-menu-item-var-make-float",
|
|
|
|
|
|
|
|
// decompiler BUG
|
|
|
|
"level-hint-task-process"};
|
2021-03-03 20:52:25 -05:00
|
|
|
|
|
|
|
// default location for the data. It can be changed with a command line argument.
|
|
|
|
std::string g_iso_data_path = "";
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-05-06 00:42:49 -04:00
|
|
|
bool g_dump_mode = false;
|
|
|
|
|
2021-09-06 21:10:19 -04:00
|
|
|
struct decomp_meta {
|
|
|
|
std::string fileName;
|
|
|
|
std::string fileNameOverride;
|
|
|
|
fs::path filePath;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<decomp_meta> g_object_files_to_decompile_or_ref_check;
|
|
|
|
|
2021-09-21 18:40:38 -04:00
|
|
|
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",
|
2021-09-28 20:42:00 -04:00
|
|
|
"DGO/DEM.DGO", "DGO/FIN.DGO", "DGO/JUN.DGO", "DGO/FIC.DGO", "DGO/SNO.DGO", "DGO/SWA.DGO",
|
2021-09-29 20:33:40 -04:00
|
|
|
"DGO/MAI.DGO", "DGO/ROB.DGO", "DGO/LAV.DGO", "DGO/OGR.DGO", "DGO/TRA.DGO"};
|
2021-05-09 17:03:58 -04:00
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
} // namespace
|
2021-05-09 17:03:58 -04:00
|
|
|
|
|
|
|
std::string replaceFirstOccurrence(std::string& s,
|
|
|
|
const std::string& toReplace,
|
|
|
|
const std::string& replaceWith) {
|
|
|
|
std::size_t pos = s.find(toReplace);
|
|
|
|
if (pos == std::string::npos)
|
|
|
|
return s;
|
|
|
|
return s.replace(pos, toReplace.length(), replaceWith);
|
|
|
|
}
|
|
|
|
|
2021-03-03 20:52:25 -05:00
|
|
|
int main(int argc, char** argv) {
|
|
|
|
lg::initialize();
|
|
|
|
|
2021-05-09 17:03:58 -04:00
|
|
|
// 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
|
2021-09-06 21:10:19 -04:00
|
|
|
std::vector<decomp_meta> reference_files_rough_order;
|
2021-05-09 17:03:58 -04:00
|
|
|
for (auto& p : fs::recursive_directory_iterator(
|
|
|
|
file_util::get_file_path({"test", "decompiler", "reference"}))) {
|
|
|
|
if (p.is_regular_file()) {
|
|
|
|
std::string file_name = fs::path(p.path()).replace_extension().filename().string();
|
|
|
|
if (file_name.find("_REF") == std::string::npos) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
std::string object_name = replaceFirstOccurrence(file_name, "_REF", "");
|
2021-09-06 21:10:19 -04:00
|
|
|
reference_files_rough_order.push_back({object_name, "", p.path()});
|
2021-05-09 17:03:58 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// use the all_objs.json file to place them in the correct build order
|
|
|
|
auto j = parse_commented_json(
|
2021-05-11 21:57:05 -04:00
|
|
|
file_util::read_text_file(file_util::get_file_path({"goal_src", "build", "all_objs.json"})),
|
|
|
|
"all_objs.json");
|
2021-05-09 17:03:58 -04:00
|
|
|
for (auto& x : j) {
|
|
|
|
auto mapped_name = x[0].get<std::string>();
|
2021-09-06 21:10:19 -04:00
|
|
|
std::vector<std::string> dgoList = x[3].get<std::vector<std::string>>();
|
2021-05-09 17:03:58 -04:00
|
|
|
for (auto& p : reference_files_rough_order) {
|
2021-09-06 21:10:19 -04:00
|
|
|
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;
|
2021-09-11 20:52:35 -04:00
|
|
|
for (int i = 0; i < (int)dgoList.size(); i++) {
|
2021-09-06 21:10:19 -04:00
|
|
|
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];
|
|
|
|
}
|
2021-05-09 17:03:58 -04:00
|
|
|
g_object_files_to_decompile_or_ref_check.push_back(p);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-03 20:52:25 -05:00
|
|
|
// look for an argument that's not a gtest option
|
|
|
|
bool got_arg = false;
|
2021-08-29 11:13:06 -04:00
|
|
|
int max_files = -1;
|
2021-03-03 20:52:25 -05:00
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
auto arg = std::string(argv[i]);
|
2021-05-06 00:42:49 -04:00
|
|
|
if (arg == "--dump-mode") {
|
|
|
|
g_dump_mode = true;
|
|
|
|
continue;
|
|
|
|
}
|
2021-08-29 11:13:06 -04:00
|
|
|
if (arg == "--max-files") {
|
|
|
|
i++;
|
|
|
|
assert(i < argc);
|
|
|
|
max_files = atoi(argv[i]);
|
|
|
|
printf("Limiting to %d files\n", max_files);
|
|
|
|
}
|
2021-03-03 20:52:25 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-29 11:13:06 -04:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-03 20:52:25 -05:00
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
|
|
|
|
class OfflineDecompilation : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
static std::unique_ptr<decompiler::ObjectFileDB> db;
|
2021-05-11 20:49:54 -04:00
|
|
|
static std::unique_ptr<decompiler::Config> config;
|
2021-07-11 16:35:25 -04:00
|
|
|
|
|
|
|
static std::unique_ptr<std::unordered_map<std::string, std::string>> final_output_cache;
|
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
static void SetUpTestCase() {
|
|
|
|
// global setup
|
|
|
|
file_util::init_crc();
|
|
|
|
decompiler::init_opcode_info();
|
2021-05-11 20:49:54 -04:00
|
|
|
config = std::make_unique<decompiler::Config>(decompiler::read_config_file(
|
|
|
|
file_util::get_file_path({"decompiler", "config", "jak1_ntsc_black_label.jsonc"})));
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-05-09 17:03:58 -04:00
|
|
|
std::unordered_set<std::string> object_files;
|
|
|
|
for (auto& p : g_object_files_to_decompile_or_ref_check) {
|
2021-09-06 21:10:19 -04:00
|
|
|
std::string fileName = p.fileNameOverride == "" ? p.fileName : p.fileNameOverride;
|
|
|
|
object_files.insert(fileName);
|
2021-05-09 17:03:58 -04:00
|
|
|
}
|
2021-05-11 20:49:54 -04:00
|
|
|
config->allowed_objects = object_files;
|
2021-05-31 18:14:18 -04:00
|
|
|
// don't try to do this because we can't write the file
|
|
|
|
config->generate_symbol_definition_map = false;
|
2021-03-03 20:52:25 -05:00
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-11 20:49:54 -04:00
|
|
|
db = std::make_unique<decompiler::ObjectFileDB>(dgo_paths, config->obj_file_name_map_file,
|
|
|
|
std::vector<std::string>{},
|
|
|
|
std::vector<std::string>{}, *config);
|
2021-03-03 15:42:55 -05:00
|
|
|
|
|
|
|
// basic processing to find functions/data/disassembly
|
2021-05-11 20:49:54 -04:00
|
|
|
db->process_link_data(*config);
|
|
|
|
db->find_code(*config);
|
2021-03-03 15:42:55 -05:00
|
|
|
db->process_labels();
|
|
|
|
|
|
|
|
// fancy decompilation.
|
2021-07-11 16:35:25 -04:00
|
|
|
db->analyze_functions_ir2({}, *config, true);
|
|
|
|
|
|
|
|
final_output_cache = std::make_unique<std::unordered_map<std::string, std::string>>();
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-05-11 20:49:54 -04:00
|
|
|
static void TearDownTestCase() {
|
|
|
|
db.reset();
|
|
|
|
config.reset();
|
2021-07-11 16:35:25 -04:00
|
|
|
final_output_cache.reset();
|
2021-05-11 20:49:54 -04:00
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
std::unique_ptr<decompiler::ObjectFileDB> OfflineDecompilation::db;
|
2021-05-11 20:49:54 -04:00
|
|
|
std::unique_ptr<decompiler::Config> OfflineDecompilation::config;
|
2021-07-11 16:35:25 -04:00
|
|
|
std::unique_ptr<std::unordered_map<std::string, std::string>>
|
|
|
|
OfflineDecompilation::final_output_cache;
|
2021-03-03 15:42:55 -05:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* 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);
|
|
|
|
});
|
|
|
|
|
2021-05-11 20:49:54 -04:00
|
|
|
EXPECT_EQ(obj_count, config->allowed_objects.size());
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(OfflineDecompilation, AsmFunction) {
|
|
|
|
int failed_count = 0;
|
|
|
|
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
|
|
|
if (func.suspected_asm) {
|
2021-09-11 20:52:35 -04:00
|
|
|
if (g_functions_expected_to_reject.find(func.name()) ==
|
2021-05-16 21:07:22 -04:00
|
|
|
g_functions_expected_to_reject.end()) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} was marked as asm, but wasn't expected.", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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()) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed cfg", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed atomic ops", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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) {
|
2021-03-24 19:16:31 -04:00
|
|
|
if (!func.ir2.env.has_type_analysis() || !func.ir2.env.types_succeeded) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed types", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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()) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed reg use", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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()) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed ssa", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed structuring", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
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) {
|
2021-09-11 20:52:35 -04:00
|
|
|
lg::error("Function {} failed expressions", func.name());
|
2021-03-03 15:42:55 -05:00
|
|
|
failed_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
EXPECT_EQ(failed_count, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void strip_trailing_newlines(std::string& in) {
|
|
|
|
while (!in.empty() && in.back() == '\n') {
|
|
|
|
in.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
TEST_F(OfflineDecompilation, Reference) {
|
2021-09-06 21:10:19 -04:00
|
|
|
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);
|
2021-03-03 15:42:55 -05:00
|
|
|
ASSERT_EQ(obj_l.size(), 1);
|
|
|
|
|
|
|
|
std::string src = db->ir2_final_out(obj_l.at(0));
|
|
|
|
|
2021-09-06 21:10:19 -04:00
|
|
|
lg::info("Comparing {}...", fileName);
|
2021-05-01 21:09:48 -04:00
|
|
|
|
2021-05-09 17:03:58 -04:00
|
|
|
// NOTE - currently only handles .gc files!
|
2021-09-06 21:10:19 -04:00
|
|
|
auto reference = file_util::read_text_file(file.filePath.string());
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-07-11 16:35:25 -04:00
|
|
|
bool can_cache = true;
|
|
|
|
for (auto& func_list : obj_l.at(0).linked_data.functions_by_seg) {
|
|
|
|
for (auto& func : func_list) {
|
2021-09-11 20:52:35 -04:00
|
|
|
if (g_functions_to_skip_compiling.find(func.name()) !=
|
2021-07-11 16:35:25 -04:00
|
|
|
g_functions_to_skip_compiling.end()) {
|
|
|
|
can_cache = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (can_cache) {
|
2021-09-06 21:10:19 -04:00
|
|
|
EXPECT_EQ(final_output_cache->count(fileName), 0);
|
|
|
|
final_output_cache->insert({file.fileName, src});
|
2021-07-11 16:35:25 -04:00
|
|
|
}
|
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
strip_trailing_newlines(reference);
|
|
|
|
strip_trailing_newlines(src);
|
|
|
|
|
2021-05-06 00:42:49 -04:00
|
|
|
if (g_dump_mode) {
|
|
|
|
if (reference != src) {
|
2021-05-12 19:46:17 -04:00
|
|
|
file_util::create_dir_if_needed("./failures");
|
2021-09-06 21:10:19 -04:00
|
|
|
file_util::write_text_file("./failures/" + file.fileName + "_REF.gc", src);
|
2021-05-06 00:42:49 -04:00
|
|
|
EXPECT_TRUE(false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
EXPECT_EQ(reference, src);
|
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
namespace {
|
|
|
|
int line_count(const std::string& str) {
|
|
|
|
int result = 0;
|
|
|
|
for (auto& c : str) {
|
|
|
|
if (c == '\n') {
|
|
|
|
result++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
TEST_F(OfflineDecompilation, Compile) {
|
|
|
|
Compiler compiler;
|
|
|
|
|
2021-06-19 15:50:52 -04:00
|
|
|
compiler.run_front_end_on_file({"decompiler", "config", "all-types.gc"});
|
|
|
|
compiler.run_front_end_on_file({"test", "decompiler", "reference", "decompiler-macros.gc"});
|
2021-03-05 18:48:01 -05:00
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
Timer timer;
|
|
|
|
int total_lines = 0;
|
2021-09-06 21:10:19 -04:00
|
|
|
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()) {
|
2021-05-01 15:51:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
2021-05-03 08:54:49 -04:00
|
|
|
|
2021-09-06 21:10:19 -04:00
|
|
|
lg::info("Compiling {}...", fileName);
|
2021-05-03 08:54:49 -04:00
|
|
|
|
2021-09-06 21:10:19 -04:00
|
|
|
auto& obj_l = db->obj_files_by_name.at(fileName);
|
2021-03-03 15:42:55 -05:00
|
|
|
ASSERT_EQ(obj_l.size(), 1);
|
|
|
|
|
2021-09-06 21:10:19 -04:00
|
|
|
const auto& cache = final_output_cache->find(fileName);
|
2021-07-11 16:35:25 -04:00
|
|
|
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);
|
|
|
|
total_lines += line_count(src);
|
|
|
|
compiler.run_full_compiler_on_string_no_save(src);
|
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
2021-03-24 19:16:31 -04:00
|
|
|
auto time = timer.getSeconds();
|
|
|
|
lg::info("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
|
|
|
|
(float)total_lines / time);
|
2021-04-10 21:17:36 -04:00
|
|
|
}
|