mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 22:22:21 -04:00
[Decompiler] Replace type hint system and improve variable types. (#320)
* get gkernel and gkernel-h at least somewhat working in the offline tests * strip comments from json * switch hints to casts. online tests passing, offline passing up to gkernel * variable retyping is added * fix up casts in lets * update
This commit is contained in:
+1
-1
@@ -3,4 +3,4 @@
|
||||
# Directory of this script
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
$DIR/build/game/gk -fakeiso -debug "$@"
|
||||
$DIR/build/game/gk -fakeiso -debug -nodisplay"$@"
|
||||
|
||||
@@ -19,6 +19,7 @@ add_library(common
|
||||
util/DgoReader.cpp
|
||||
util/DgoWriter.cpp
|
||||
util/FileUtil.cpp
|
||||
util/json_util.cpp
|
||||
util/Timer.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -509,6 +509,10 @@ void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) {
|
||||
}
|
||||
}
|
||||
|
||||
if (name == "begin") {
|
||||
breakList(pool, node->paren);
|
||||
}
|
||||
|
||||
if (name == "defun" || name == "defmethod" || name == "defun-debug" || name == "let" ||
|
||||
name == "let*") {
|
||||
auto* first_list = getNextListOnLine(node);
|
||||
|
||||
@@ -185,6 +185,7 @@ class Field {
|
||||
void set_dynamic();
|
||||
void set_array(int size);
|
||||
void set_inline();
|
||||
void mark_as_user_placed() { m_placed_by_user = true; }
|
||||
std::string print() const;
|
||||
const TypeSpec& type() const { return m_type; }
|
||||
bool is_inline() const { return m_inline; }
|
||||
@@ -193,6 +194,7 @@ class Field {
|
||||
const std::string& name() const { return m_name; }
|
||||
int offset() const { return m_offset; }
|
||||
bool skip_in_decomp() const { return m_skip_in_static_decomp; }
|
||||
bool user_placed() const { return m_placed_by_user; }
|
||||
bool operator==(const Field& other) const;
|
||||
|
||||
int alignment() const {
|
||||
@@ -221,6 +223,7 @@ class Field {
|
||||
int m_array_size = 0;
|
||||
int m_alignment = -1;
|
||||
bool m_skip_in_static_decomp = false;
|
||||
bool m_placed_by_user = false; // was this field placed manually by the user?
|
||||
};
|
||||
|
||||
class StructureType : public ReferenceType {
|
||||
@@ -243,6 +246,8 @@ class StructureType : public ReferenceType {
|
||||
bool is_dynamic() const { return m_dynamic; }
|
||||
~StructureType() = default;
|
||||
void set_pack(bool pack) { m_pack = pack; }
|
||||
bool is_packed() const { return m_pack; }
|
||||
bool is_allowed_misalign() const { return m_allow_misalign; };
|
||||
void set_allow_misalign(bool misalign) { m_allow_misalign = misalign; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -301,7 +301,10 @@ Type* TypeSystem::lookup_type_allow_partial_def(const std::string& name) const {
|
||||
} else if (fwd_dec->second == BASIC) {
|
||||
return lookup_type("basic");
|
||||
} else {
|
||||
fmt::print("[TypeSystem] The type {} is not fully define (allow partial).\n", name);
|
||||
fmt::print(
|
||||
"[TypeSystem] The type {} is known to be a type, but has not been defined with deftype "
|
||||
"or properly forward declared with declare-type\n",
|
||||
name);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -646,6 +649,7 @@ int TypeSystem::add_field_to_type(StructureType* type,
|
||||
offset = align(type->get_size_in_memory(), field_alignment);
|
||||
} else {
|
||||
int aligned_offset = align(offset, field_alignment);
|
||||
field.mark_as_user_placed();
|
||||
if (offset != aligned_offset) {
|
||||
fmt::print(
|
||||
"[TypeSystem] Tried to overwrite offset of field to be {}, but it is not aligned "
|
||||
@@ -1277,6 +1281,17 @@ void TypeSystem::add_field_to_bitfield(BitFieldType* type,
|
||||
*/
|
||||
std::string TypeSystem::generate_deftype_footer(const Type* type) const {
|
||||
std::string result;
|
||||
|
||||
auto as_structure = dynamic_cast<const StructureType*>(type);
|
||||
if (as_structure) {
|
||||
if (as_structure->is_packed()) {
|
||||
result.append(" :pack-me\n");
|
||||
}
|
||||
if (as_structure->is_allowed_misalign()) {
|
||||
result.append(" :allow-misaligned");
|
||||
}
|
||||
}
|
||||
|
||||
auto method_count = get_next_method_id(type);
|
||||
result.append(fmt::format(" :method-count-assert {}\n", get_next_method_id(type)));
|
||||
result.append(fmt::format(" :size-assert #x{:x}\n", type->get_size_in_memory()));
|
||||
@@ -1332,8 +1347,9 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st)
|
||||
int longest_type_name = 0;
|
||||
int longest_mods = 0;
|
||||
|
||||
std::string inline_string = ":inline";
|
||||
std::string dynamic_string = ":dynamic";
|
||||
const std::string inline_string = ":inline";
|
||||
const std::string dynamic_string = ":dynamic";
|
||||
const std::string user_offset_string = ":offset xxx";
|
||||
|
||||
for (size_t i = st->first_unique_field_idx(); i < st->fields().size(); i++) {
|
||||
const auto& field = st->fields().at(i);
|
||||
@@ -1359,6 +1375,13 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st)
|
||||
}
|
||||
mods += dynamic_string.size();
|
||||
}
|
||||
|
||||
if (field.user_placed()) {
|
||||
if (mods) {
|
||||
mods++;
|
||||
}
|
||||
mods += user_offset_string.size();
|
||||
}
|
||||
longest_mods = std::max(longest_mods, mods);
|
||||
}
|
||||
|
||||
@@ -1385,11 +1408,18 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st)
|
||||
mods += dynamic_string;
|
||||
mods += " ";
|
||||
}
|
||||
result.append(mods);
|
||||
result.append(longest_mods - int(mods.size() - 1), ' ');
|
||||
|
||||
result.append(":offset-assert ");
|
||||
result.append(std::to_string(field.offset()));
|
||||
if (field.user_placed()) {
|
||||
mods += fmt::format(":offset {:3d}", field.offset());
|
||||
}
|
||||
result.append(mods);
|
||||
|
||||
if (!field.user_placed()) {
|
||||
result.append(longest_mods - int(mods.size() - 1), ' ');
|
||||
result.append(":offset-assert ");
|
||||
result.append(std::to_string(field.offset()));
|
||||
}
|
||||
|
||||
result.append(")\n ");
|
||||
}
|
||||
|
||||
|
||||
@@ -198,15 +198,7 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def
|
||||
TypeSpec function_typespec("function");
|
||||
|
||||
for_each_in_list(args, [&](const goos::Object& o) {
|
||||
if (o.is_symbol()) {
|
||||
function_typespec.add_arg(parse_typespec(type_system, o));
|
||||
} else {
|
||||
auto next = cdr(&o);
|
||||
function_typespec.add_arg(parse_typespec(type_system, car(next)));
|
||||
if (!cdr(next)->is_empty_list()) {
|
||||
throw std::runtime_error("too many things in method def arg type: " + def.print());
|
||||
};
|
||||
}
|
||||
function_typespec.add_arg(parse_typespec(type_system, o));
|
||||
});
|
||||
function_typespec.add_arg(parse_typespec(type_system, return_type));
|
||||
|
||||
@@ -415,7 +407,7 @@ BitFieldTypeDefResult parse_bitfield_type_def(BitFieldType* type,
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
|
||||
TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src) {
|
||||
if (src.is_symbol()) {
|
||||
return type_system->make_typespec(symbol_string(src));
|
||||
} else if (src.is_pair()) {
|
||||
|
||||
@@ -18,4 +18,4 @@ struct DeftypeResult {
|
||||
};
|
||||
|
||||
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts);
|
||||
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src);
|
||||
TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
|
||||
/*!
|
||||
* A Range is similar to a Python range. It can represent an empty range or a range of consecutive
|
||||
* values. It supports iterators so you can write a loop like:
|
||||
* for (auto x : Range<int>(1, 4)) {
|
||||
* }
|
||||
* and it will work like you expect, without allocating memory for all the elements.
|
||||
*
|
||||
* A Range's iterator actually store values, not references, so this is designed to be used
|
||||
* with small things, like integers.
|
||||
*
|
||||
* Note - for size and iterator distances, a Range will use the same type as its elements.
|
||||
*/
|
||||
template <typename T>
|
||||
class Range {
|
||||
public:
|
||||
Range() = default;
|
||||
Range(const T& start, const T& end) : m_start(start), m_end(end) {}
|
||||
const T& first() const { return m_start; }
|
||||
const T& last() const { return m_end; }
|
||||
bool contains(T& val) const { return val >= m_start && val < m_end; }
|
||||
bool empty() const { return m_end <= m_start; }
|
||||
T size() const { return m_end - m_start; }
|
||||
|
||||
struct Iterator {
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using difference_type = T;
|
||||
using value_type = T;
|
||||
using pointer = const T*;
|
||||
using reference = const T&;
|
||||
|
||||
Iterator(const T& val) : m_val(val) {}
|
||||
|
||||
reference operator*() const { return m_val; }
|
||||
pointer operator->() { return &m_val; }
|
||||
|
||||
Iterator& operator++() {
|
||||
m_val++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Iterator operator++(int) {
|
||||
Iterator old = *this;
|
||||
++(*this);
|
||||
return old;
|
||||
}
|
||||
|
||||
friend bool operator==(const Iterator& a, const Iterator& b) { return a.m_val == b.m_val; }
|
||||
friend bool operator!=(const Iterator& a, const Iterator& b) { return a.m_val != b.m_val; }
|
||||
|
||||
private:
|
||||
T m_val;
|
||||
};
|
||||
|
||||
Iterator begin() { return Iterator(m_start); }
|
||||
Iterator end() { return Iterator(m_end); }
|
||||
|
||||
private:
|
||||
T m_start = {};
|
||||
T m_end = {};
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
#include <cassert>
|
||||
#include "json_util.h"
|
||||
|
||||
/*!
|
||||
* Strip out // and / * comments
|
||||
* Does not strip comments from within a string.
|
||||
* Assumes \" is used to escape quotes inside of a string.
|
||||
*/
|
||||
std::string strip_cpp_style_comments(const std::string& input) {
|
||||
std::string output;
|
||||
|
||||
enum State { NORMAL, BLOCK_COMMENT, LINE_COMMENT, STRING } state = NORMAL;
|
||||
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
char c = input[i];
|
||||
char next = i < (input.size() - 1) ? input[i + 1] : '\0';
|
||||
char prev = i > 0 ? input[i - 1] : '\0';
|
||||
|
||||
switch (state) {
|
||||
case NORMAL:
|
||||
if (c == '/' && next == '*') {
|
||||
// Normal -> block
|
||||
state = BLOCK_COMMENT;
|
||||
i++; // skip over *
|
||||
} else if (c == '/' && next == '/') {
|
||||
// Normal -> line
|
||||
state = LINE_COMMENT;
|
||||
i++;
|
||||
} else if (c == '"') {
|
||||
// Normal -> string
|
||||
state = STRING;
|
||||
output.push_back(c);
|
||||
} else {
|
||||
// Normal -> normal
|
||||
output.push_back(c);
|
||||
}
|
||||
break;
|
||||
case BLOCK_COMMENT:
|
||||
if (c == '*' && next == '/') {
|
||||
// Block -> normal
|
||||
state = NORMAL;
|
||||
i++;
|
||||
}
|
||||
// otherwise stay in block comment
|
||||
break;
|
||||
case LINE_COMMENT:
|
||||
if (c == '\n') {
|
||||
state = NORMAL;
|
||||
output.push_back(c);
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
if (c == '"' && prev != '\\') {
|
||||
state = NORMAL;
|
||||
}
|
||||
output.push_back(c);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (state == BLOCK_COMMENT) {
|
||||
throw std::runtime_error("strip_cpp_style_comments ended in a block comment");
|
||||
}
|
||||
|
||||
if (state == STRING) {
|
||||
throw std::runtime_error("strip_cpp_style_comments ended in a string.");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Parse JSON file with comments stripped. Unlike the default comment stripping feature
|
||||
* of nlohmann::json, this allows you to have multiple comments in a row!
|
||||
*/
|
||||
nlohmann::json parse_commented_json(const std::string& input) {
|
||||
return nlohmann::json::parse(strip_cpp_style_comments(input));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Parse something like:
|
||||
* 2 -> Range(2, 3)
|
||||
* [2, 3] -> Range(2, 3)
|
||||
* [2, 10] -> Range(2, 10)
|
||||
*/
|
||||
Range<int> parse_json_optional_integer_range(const nlohmann::json& json) {
|
||||
if (json.is_number_integer()) {
|
||||
auto first = json.get<int>();
|
||||
return Range<int>(first, first + 1);
|
||||
} else if (json.is_array()) {
|
||||
if (json.size() != 2) {
|
||||
throw std::runtime_error("Invalid array size in parse_json_optional_integer_range");
|
||||
}
|
||||
return {json[0].get<int>(), json[1].get<int>()};
|
||||
} else {
|
||||
throw std::runtime_error("Invalid json as input to parse_json_optional_integer_range");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include "common/util/Range.h"
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
std::string strip_cpp_style_comments(const std::string& input);
|
||||
nlohmann::json parse_commented_json(const std::string& input);
|
||||
Range<int> parse_json_optional_integer_range(const nlohmann::json& json);
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
namespace versions {
|
||||
// language version (OpenGOAL)
|
||||
constexpr s32 GOAL_VERSION_MAJOR = 0;
|
||||
constexpr s32 GOAL_VERSION_MINOR = 6;
|
||||
constexpr s32 GOAL_VERSION_MINOR = 7;
|
||||
|
||||
constexpr int DECOMPILER_VERSION = 4;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ add_library(
|
||||
analysis/cfg_builder.cpp
|
||||
analysis/expression_build.cpp
|
||||
analysis/final_output.cpp
|
||||
analysis/inline_asm_rewrite.cpp
|
||||
analysis/insert_lets.cpp
|
||||
analysis/reg_usage.cpp
|
||||
analysis/variable_naming.cpp
|
||||
@@ -39,6 +40,7 @@ add_library(
|
||||
IR2/FormExpressionAnalysis.cpp
|
||||
IR2/FormStack.cpp
|
||||
IR2/GenericElementMatcher.cpp
|
||||
IR2/OpenGoalMapping.cpp
|
||||
|
||||
ObjectFile/LinkedObjectFile.cpp
|
||||
ObjectFile/LinkedObjectFileCreation.cpp
|
||||
@@ -50,8 +52,7 @@ add_library(
|
||||
util/DecompilerTypeSystem.cpp
|
||||
util/TP_Type.cpp
|
||||
|
||||
config.cpp
|
||||
"analysis/inline_asm_rewrite.cpp" "analysis/inline_asm_rewrite.h" "IR2/OpenGoalMapping.cpp")
|
||||
config.cpp)
|
||||
|
||||
target_link_libraries(decomp
|
||||
lzokay
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
@@ -135,6 +136,10 @@ class Register {
|
||||
Register() = default;
|
||||
Register(Reg::RegisterKind kind, uint32_t num);
|
||||
Register(const std::string& name);
|
||||
static Register get_arg_reg(int idx) {
|
||||
assert(idx >= 0 && idx < 8);
|
||||
return Register(Reg::GPR, Reg::A0 + idx);
|
||||
}
|
||||
const char* to_charp() const;
|
||||
std::string to_string() const;
|
||||
Reg::RegisterKind get_kind() const;
|
||||
|
||||
@@ -94,7 +94,7 @@ class Function {
|
||||
bool run_type_analysis_ir2(const TypeSpec& my_type,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
const std::unordered_map<int, std::vector<TypeHint>>& hints,
|
||||
const std::unordered_map<int, std::vector<TypeCast>>& casts,
|
||||
const std::unordered_map<std::string, LabelType>& label_types);
|
||||
BlockTopologicalSort bb_topo_sort();
|
||||
|
||||
@@ -169,6 +169,7 @@ class Function {
|
||||
std::string debug_form_string;
|
||||
bool print_debug_forms = false;
|
||||
bool expressions_succeeded = false;
|
||||
bool types_succeeded = false;
|
||||
} ir2;
|
||||
|
||||
private:
|
||||
|
||||
@@ -22,10 +22,29 @@ TypeState construct_initial_typestate(const TypeSpec& f_ts) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void apply_hints(const std::vector<TypeHint>& hints, TypeState* state, DecompilerTypeSystem& dts) {
|
||||
for (auto& hint : hints) {
|
||||
/*!
|
||||
* Modify the the given type state based on the given casts.
|
||||
*/
|
||||
void modify_input_types_for_casts(
|
||||
const std::vector<TypeCast>& casts,
|
||||
TypeState* state,
|
||||
std::unordered_map<Register, TP_Type, Register::hash>* changed_types,
|
||||
DecompilerTypeSystem& dts) {
|
||||
for (auto& cast : casts) {
|
||||
try {
|
||||
state->get(hint.reg) = TP_Type::make_from_ts(dts.parse_type_spec(hint.type_name));
|
||||
auto type_from_cast = TP_Type::make_from_ts(dts.parse_type_spec(cast.type_name));
|
||||
auto original_type = state->get(cast.reg);
|
||||
// fmt::print("Cast reg {} : {} -> {}\n", cast.reg.to_string(), original_type.print(),
|
||||
// type_from_cast.print());
|
||||
if (original_type != type_from_cast) {
|
||||
// the cast will have an effect on types. If we are removing the original type, remember it
|
||||
if (changed_types && changed_types->find(cast.reg) == changed_types->end()) {
|
||||
(*changed_types)[cast.reg] = original_type;
|
||||
}
|
||||
|
||||
// and actually update the typ
|
||||
state->get(cast.reg) = type_from_cast;
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
printf("failed to parse hint: %s\n", e.what());
|
||||
assert(false);
|
||||
@@ -33,13 +52,16 @@ void apply_hints(const std::vector<TypeHint>& hints, TypeState* state, Decompile
|
||||
}
|
||||
}
|
||||
|
||||
void try_apply_hints(int idx,
|
||||
const std::unordered_map<int, std::vector<TypeHint>>& hints,
|
||||
TypeState* state,
|
||||
DecompilerTypeSystem& dts) {
|
||||
auto kv = hints.find(idx);
|
||||
if (kv != hints.end()) {
|
||||
apply_hints(kv->second, state, dts);
|
||||
void try_modify_input_types_for_casts(
|
||||
int idx,
|
||||
const std::unordered_map<int, std::vector<TypeCast>>& casts,
|
||||
TypeState* state,
|
||||
std::unordered_map<Register, TP_Type, Register::hash>* changed_types,
|
||||
DecompilerTypeSystem& dts) {
|
||||
auto kv = casts.find(idx);
|
||||
if (kv != casts.end()) {
|
||||
// fmt::print("at idx {}, casting:\n", idx);
|
||||
modify_input_types_for_casts(kv->second, state, changed_types, dts);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -48,10 +70,10 @@ bool Function::run_type_analysis_ir2(
|
||||
const TypeSpec& my_type,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
const std::unordered_map<int, std::vector<TypeHint>>& hints,
|
||||
const std::unordered_map<int, std::vector<TypeCast>>& casts,
|
||||
const std::unordered_map<std::string, LabelType>& label_types) {
|
||||
(void)file;
|
||||
ir2.env.set_type_hints(hints);
|
||||
ir2.env.set_type_casts(casts);
|
||||
ir2.env.set_label_types(label_types);
|
||||
// STEP 0 - set decompiler type system settings for this function. In config we can manually
|
||||
// specify some settings for type propagation to reduce the strictness of type propagation.
|
||||
@@ -95,7 +117,8 @@ bool Function::run_type_analysis_ir2(
|
||||
// STEP 2 - initialize type state for the first block to the function argument types.
|
||||
block_init_types.at(0) = construct_initial_typestate(my_type);
|
||||
// and add hints from config
|
||||
try_apply_hints(0, hints, &block_init_types.at(0), dts);
|
||||
// TODO - this should be moved into the construct_initial_typestate
|
||||
// try_apply_hints(0, hints, &block_init_types.at(0), dts);
|
||||
|
||||
// STEP 3 - propagate types until the result stops changing
|
||||
bool run_again = true;
|
||||
@@ -108,14 +131,14 @@ bool Function::run_type_analysis_ir2(
|
||||
for (int op_id = aop->block_id_to_first_atomic_op.at(block_id);
|
||||
op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) {
|
||||
// apply type hints only if we are not the first op.
|
||||
if (op_id != aop->block_id_to_first_atomic_op.at(block_id)) {
|
||||
try_apply_hints(op_id, hints, init_types, dts);
|
||||
}
|
||||
|
||||
std::unordered_map<Register, TP_Type, Register::hash> restore_cast_types;
|
||||
// if (op_id != aop->block_id_to_first_atomic_op.at(block_id)) {
|
||||
try_modify_input_types_for_casts(op_id, casts, init_types, &restore_cast_types, dts);
|
||||
//}
|
||||
|
||||
auto& op = aop->ops.at(op_id);
|
||||
|
||||
// while the implementation of propagate_types_internal is in progress, it may throw
|
||||
// for unimplemented cases. Eventually this try/catch should be removed.
|
||||
try {
|
||||
op_types.at(op_id) = op->propagate_types(*init_types, ir2.env, dts);
|
||||
} catch (std::runtime_error& e) {
|
||||
@@ -125,7 +148,15 @@ bool Function::run_type_analysis_ir2(
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo, set run again??
|
||||
// undo casts.
|
||||
for (auto& restore_kv : restore_cast_types) {
|
||||
if (std::find(op->write_regs().begin(), op->write_regs().end(), restore_kv.first) ==
|
||||
op->write_regs().end()) {
|
||||
op_types.at(op_id).get(restore_kv.first) = restore_kv.second;
|
||||
}
|
||||
|
||||
init_types->get(restore_kv.first) = restore_kv.second;
|
||||
}
|
||||
|
||||
// for the next op...
|
||||
init_types = &op_types.at(op_id);
|
||||
@@ -135,8 +166,9 @@ bool Function::run_type_analysis_ir2(
|
||||
for (auto succ_block_id : {block.succ_ft, block.succ_branch}) {
|
||||
if (succ_block_id != -1) {
|
||||
// apply hint
|
||||
try_apply_hints(aop->block_id_to_first_atomic_op.at(succ_block_id), hints, init_types,
|
||||
dts);
|
||||
// try_apply_hints(aop->block_id_to_first_atomic_op.at(succ_block_id), hints,
|
||||
// init_types,
|
||||
// dts);
|
||||
|
||||
// set types to LCA (current, new)
|
||||
if (dts.tp_lca(&block_init_types.at(succ_block_id), *init_types)) {
|
||||
@@ -153,6 +185,19 @@ bool Function::run_type_analysis_ir2(
|
||||
warnings.info("Return type mismatch {} vs {}.", last_type.print(), my_type.last_arg().print());
|
||||
}
|
||||
|
||||
// and apply final casts:
|
||||
for (auto block_id : order.vist_order) {
|
||||
for (int op_id = aop->block_id_to_first_atomic_op.at(block_id);
|
||||
op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) {
|
||||
if (op_id == aop->block_id_to_first_atomic_op.at(block_id)) {
|
||||
try_modify_input_types_for_casts(op_id, casts, &block_init_types.at(block_id), nullptr,
|
||||
dts);
|
||||
} else {
|
||||
try_modify_input_types_for_casts(op_id, casts, &op_types.at(op_id - 1), nullptr, dts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -32,10 +32,10 @@ goos::Object RegisterAccess::to_form(const Env& env, Print mode) const {
|
||||
return pretty_print::to_symbol(fmt::format("{}-{:03d}-{}", m_reg.to_charp(), m_atomic_idx,
|
||||
m_mode == AccessMode::READ ? 'r' : 'w'));
|
||||
case Print::AS_VARIABLE:
|
||||
return env.get_variable_name(m_reg, m_atomic_idx, m_mode);
|
||||
return env.get_variable_name_with_cast(m_reg, m_atomic_idx, m_mode);
|
||||
case Print::AUTOMATIC:
|
||||
if (env.has_local_vars()) {
|
||||
return env.get_variable_name(m_reg, m_atomic_idx, m_mode);
|
||||
return env.get_variable_name_with_cast(m_reg, m_atomic_idx, m_mode);
|
||||
} else {
|
||||
return pretty_print::to_symbol(m_reg.to_string());
|
||||
}
|
||||
|
||||
@@ -284,6 +284,7 @@ class SetVarOp : public AtomicOp {
|
||||
private:
|
||||
RegisterAccess m_dst;
|
||||
SimpleExpression m_src;
|
||||
std::optional<TypeSpec> m_source_type;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -464,6 +465,7 @@ class LoadVarOp : public AtomicOp {
|
||||
int m_size = -1;
|
||||
RegisterAccess m_dst;
|
||||
SimpleExpression m_src;
|
||||
std::optional<TypeSpec> m_type;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -59,14 +59,17 @@ FormElement* SetVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
}
|
||||
auto load =
|
||||
pool.alloc_single_element_form<DerefElement>(nullptr, source, rd.addr_of, tokens);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_source_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create element
|
||||
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_src, m_my_idx);
|
||||
auto result = pool.alloc_element<SetVarElement>(m_dst, source, is_sequence_point());
|
||||
auto result = pool.alloc_element<SetVarElement>(m_dst, source, is_sequence_point(),
|
||||
m_source_type.value_or(TypeSpec("object")));
|
||||
|
||||
// do some analysis to look for coloring moves which are already eliminated,
|
||||
// dead sets, and dead set falses.
|
||||
@@ -101,7 +104,7 @@ FormElement* AsmOp::get_as_form(FormPool& pool, const Env&) const {
|
||||
FormElement* SetVarConditionOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
return pool.alloc_element<SetVarElement>(
|
||||
m_dst, pool.alloc_single_form(nullptr, m_condition.get_as_form(pool, env, m_my_idx)),
|
||||
is_sequence_point());
|
||||
is_sequence_point(), TypeSpec("symbol"));
|
||||
}
|
||||
|
||||
FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
@@ -309,7 +312,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
|
||||
nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx);
|
||||
auto load = pool.alloc_single_element_form<DerefElement>(nullptr, source, false, tokens);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
|
||||
// todo structure method
|
||||
@@ -321,7 +325,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
// access method vtable. The input is type + (4 * method), and the 16 is the offset
|
||||
// of method 0.
|
||||
auto load = pool.alloc_single_element_form<DynamicMethodAccess>(nullptr, ro.var);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
|
||||
if (input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) {
|
||||
@@ -348,7 +353,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
// different in different cases.
|
||||
auto load = pool.alloc_single_element_form<ArrayFieldAccess>(
|
||||
nullptr, ro.var, tokens, input_type.get_multiplier(), ro.offset);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +369,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
auto load = pool.alloc_single_element_form<GenericElement>(
|
||||
nullptr, GenericOperator::make_fixed(FixedOperatorKind::CDR), source);
|
||||
// cdr = another pair.
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
} else if (ro.offset == -2) {
|
||||
// car = some object.
|
||||
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(
|
||||
@@ -371,7 +378,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
auto load = pool.alloc_single_element_form<GenericElement>(
|
||||
nullptr, GenericOperator::make_fixed(FixedOperatorKind::CAR), source);
|
||||
// cdr = another pair.
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +408,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
|
||||
auto load =
|
||||
pool.alloc_single_element_form<DerefElement>(nullptr, source, rd.addr_of, tokens);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
|
||||
if (input_type.typespec() == TypeSpec("pointer")) {
|
||||
@@ -433,7 +442,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
nullptr, TypeSpec("pointer", {TypeSpec(cast_type)}), dest);
|
||||
auto deref = pool.alloc_single_element_form<DerefElement>(nullptr, cast_dest, false,
|
||||
std::vector<DerefToken>());
|
||||
return pool.alloc_element<SetVarElement>(m_dst, deref, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, deref, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,7 +463,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
float value;
|
||||
memcpy(&value, &word.data, 4);
|
||||
auto float_elt = pool.alloc_single_element_form<ConstantFloatElement>(nullptr, value);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, float_elt, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, float_elt, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
} else if (hint->second.type_name == "uint64" && m_kind != Kind::FLOAT && m_size == 8) {
|
||||
assert((label.offset % 8) == 0);
|
||||
auto word0 = env.file->words_by_seg.at(label.target_segment).at(label.offset / 4);
|
||||
@@ -466,7 +477,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
memcpy(((u8*)&value) + 4, &word1.data, 4);
|
||||
auto val_elt = pool.alloc_single_element_form<ConstantTokenElement>(
|
||||
nullptr, fmt::format("#x{:x}", value));
|
||||
return pool.alloc_element<SetVarElement>(m_dst, val_elt, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, val_elt, true,
|
||||
m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,7 +486,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
|
||||
auto source = pool.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_src, m_my_idx);
|
||||
auto load = pool.alloc_single_element_form<LoadSourceElement>(nullptr, source, m_size, m_kind);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true);
|
||||
return pool.alloc_element<SetVarElement>(m_dst, load, true, m_type.value_or(TypeSpec("object")));
|
||||
}
|
||||
|
||||
FormElement* BranchOp::get_as_form(FormPool& pool, const Env&) const {
|
||||
@@ -500,7 +512,14 @@ FormElement* CallOp::get_as_form(FormPool& pool, const Env& env) const {
|
||||
// this is a little scary in the case that type analysis doesn't run and relies on the fact
|
||||
// that CallOp falls back to writing v0 in the case where the function type isn't known.
|
||||
RegisterAccess out_var(AccessMode::WRITE, Register(Reg::GPR, Reg::V0), m_my_idx);
|
||||
return pool.alloc_element<SetVarElement>(out_var, pool.alloc_single_form(nullptr, call), true);
|
||||
TypeSpec result_type("object");
|
||||
|
||||
if (m_call_type_set) {
|
||||
result_type = m_call_type.last_arg();
|
||||
}
|
||||
|
||||
return pool.alloc_element<SetVarElement>(out_var, pool.alloc_single_form(nullptr, call), true,
|
||||
result_type);
|
||||
} else {
|
||||
throw std::runtime_error("CallOp::get_as_expr not yet implemented");
|
||||
}
|
||||
|
||||
@@ -465,9 +465,12 @@ TypeState SetVarOp::propagate_types_internal(const TypeState& input,
|
||||
m_src.get_arg(0).get_int() == 0) {
|
||||
// mtc fX, r0 should be a float type. GOAL was smart enough to do this.
|
||||
result.get(m_dst.reg()) = TP_Type::make_from_ts("float");
|
||||
m_source_type = TypeSpec("float");
|
||||
return result;
|
||||
}
|
||||
result.get(m_dst.reg()) = m_src.get_type(input, env, dts);
|
||||
auto type = m_src.get_type(input, env, dts);
|
||||
result.get(m_dst.reg()) = type;
|
||||
m_source_type = type.typespec();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -735,7 +738,9 @@ TypeState LoadVarOp::propagate_types_internal(const TypeState& input,
|
||||
const Env& env,
|
||||
DecompilerTypeSystem& dts) {
|
||||
TypeState result = input;
|
||||
result.get(m_dst.reg()) = get_src_type(input, env, dts);
|
||||
auto load_type = get_src_type(input, env, dts);
|
||||
result.get(m_dst.reg()) = load_type;
|
||||
m_type = load_type.typespec();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+104
-7
@@ -1,6 +1,7 @@
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <decompiler/util/DecompilerTypeSystem.h>
|
||||
#include "Env.h"
|
||||
#include "Form.h"
|
||||
#include "decompiler/analysis/atomic_op_builder.h"
|
||||
@@ -62,6 +63,23 @@ void Env::map_args_from_config(const std::vector<std::string>& args_names,
|
||||
}
|
||||
}
|
||||
|
||||
void Env::map_args_from_config(
|
||||
const std::vector<std::string>& args_names,
|
||||
const std::unordered_map<std::string, LocalVarOverride>& var_overrides) {
|
||||
for (size_t i = 0; i < args_names.size(); i++) {
|
||||
std::string var_name;
|
||||
var_name.push_back(i >= 4 ? 't' : 'a');
|
||||
var_name.push_back('0' + (i % 4));
|
||||
var_name.push_back('-');
|
||||
var_name.push_back('0');
|
||||
m_var_remap[var_name] = args_names[i];
|
||||
}
|
||||
|
||||
for (auto& x : var_overrides) {
|
||||
m_var_remap[x.first] = x.second.name;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& Env::remapped_name(const std::string& name) const {
|
||||
auto kv = m_var_remap.find(name);
|
||||
if (kv != m_var_remap.end()) {
|
||||
@@ -71,22 +89,79 @@ const std::string& Env::remapped_name(const std::string& name) const {
|
||||
}
|
||||
}
|
||||
|
||||
goos::Object Env::get_variable_name(Register reg, int atomic_idx, AccessMode mode) const {
|
||||
goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const {
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
|
||||
std::string lookup_name = m_var_names.lookup(reg, atomic_idx, mode).name();
|
||||
auto remapped = m_var_remap.find(lookup_name);
|
||||
auto& var_info = m_var_names.lookup(reg, atomic_idx, mode);
|
||||
// this is a bit of a confusing process. The first step is to grab the auto-generated name:
|
||||
std::string original_name = var_info.name();
|
||||
auto lookup_name = original_name;
|
||||
|
||||
// and then see if there's a user remapping of it
|
||||
auto remapped = m_var_remap.find(original_name);
|
||||
if (remapped != m_var_remap.end()) {
|
||||
lookup_name = remapped->second;
|
||||
}
|
||||
auto type_kv = m_typehints.find(atomic_idx);
|
||||
if (type_kv != m_typehints.end()) {
|
||||
|
||||
// get the type of the variable. This is the type of thing if we do no casts.
|
||||
// first, get the type the decompiler found
|
||||
auto type_of_var = var_info.type.typespec();
|
||||
// and the user's type.
|
||||
auto retype_kv = m_var_retype.find(original_name);
|
||||
if (retype_kv != m_var_retype.end()) {
|
||||
type_of_var = retype_kv->second;
|
||||
}
|
||||
|
||||
// next, we insert type casts that make enforce the user override.
|
||||
auto type_kv = m_typecasts.find(atomic_idx);
|
||||
if (type_kv != m_typecasts.end()) {
|
||||
for (auto& x : type_kv->second) {
|
||||
if (x.reg == reg) {
|
||||
// TODO - redo this!
|
||||
return pretty_print::build_list("the-as", x.type_name, lookup_name);
|
||||
// let's make sure the above claim is true
|
||||
TypeSpec type_in_reg;
|
||||
if (has_type_analysis() && mode == AccessMode::READ) {
|
||||
type_in_reg = get_types_for_op_mode(atomic_idx, AccessMode::READ).get(reg).typespec();
|
||||
if (type_in_reg.print() != x.type_name) {
|
||||
lg::error(
|
||||
"Decompiler type consistency error. There was a typecast for reg {} at idx {} "
|
||||
"(var {}) to type {}, but the actual type is {} ({})",
|
||||
reg.to_charp(), atomic_idx, lookup_name, x.type_name, type_in_reg.print(),
|
||||
type_in_reg.print());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (type_of_var != type_in_reg) {
|
||||
// TODO - use the when possible?
|
||||
return pretty_print::build_list("the-as", x.type_name, lookup_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// type analysis stuff runs before variable types, so we insert casts that account
|
||||
// for the changing types due to the lca(uses) that is used to generate variable types.
|
||||
auto type_of_reg = get_types_for_op_mode(atomic_idx, mode).get(reg).typespec();
|
||||
if (mode == AccessMode::READ) {
|
||||
// note - this may be stricter than needed. but that's ok.
|
||||
|
||||
if (type_of_var != type_of_reg) {
|
||||
// fmt::print("casting {} (reg {}, idx {}): reg type {} var type {} remapped var type
|
||||
// {}\n ",
|
||||
// lookup_name, reg.to_charp(), atomic_idx, type_of_reg.print(),
|
||||
// var_info.type.typespec().print(), type_of_var.print());
|
||||
return pretty_print::build_list("the-as", type_of_reg.print(), lookup_name);
|
||||
}
|
||||
} else {
|
||||
// if we're setting a variable, we are a little less strict.
|
||||
// let's leave this to set!'s for now. This is tricky with stuff like (if y x) where the move
|
||||
// is eliminated so the RegisterAccess points to the "wrong" place.
|
||||
// if (!dts->ts.tc(type_of_var, type_of_reg)) {
|
||||
// fmt::print("op {} reg {} type {}\n", atomic_idx, reg.to_charp(),
|
||||
// get_types_for_op_mode(atomic_idx, mode).get(reg).print()); return
|
||||
// pretty_print::build_list("the-as", type_of_reg.print(), lookup_name);
|
||||
// }
|
||||
}
|
||||
|
||||
return pretty_print::to_symbol(lookup_name);
|
||||
} else {
|
||||
return pretty_print::to_symbol(reg.to_charp());
|
||||
@@ -106,6 +181,28 @@ std::string Env::get_variable_name(const RegisterAccess& access) const {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the type of the variable currently in the register.
|
||||
* NOTE: this is _NOT_ the most specific type known to the decompiler, but instead the type
|
||||
* of the variable.
|
||||
*/
|
||||
TypeSpec Env::get_variable_type(const RegisterAccess& access) const {
|
||||
if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) {
|
||||
auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode());
|
||||
std::string original_name = var_info.name();
|
||||
|
||||
auto type_of_var = var_info.type.typespec();
|
||||
auto retype_kv = m_var_retype.find(original_name);
|
||||
if (retype_kv != m_var_retype.end()) {
|
||||
type_of_var = retype_kv->second;
|
||||
}
|
||||
|
||||
return type_of_var;
|
||||
} else {
|
||||
throw std::runtime_error("Types are not supported for this kind of register");
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Update the Env with the result of the type analysis pass.
|
||||
*/
|
||||
|
||||
+18
-4
@@ -44,8 +44,9 @@ class Env {
|
||||
}
|
||||
|
||||
// TODO - remove this.
|
||||
goos::Object get_variable_name(Register reg, int atomic_idx, AccessMode mode) const;
|
||||
goos::Object get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const;
|
||||
std::string get_variable_name(const RegisterAccess& access) const;
|
||||
TypeSpec get_variable_type(const RegisterAccess& access) const;
|
||||
|
||||
/*!
|
||||
* Get the types in registers _after_ the given operation has completed.
|
||||
@@ -60,6 +61,14 @@ class Env {
|
||||
return *m_op_init_types.at(atomic_op_id);
|
||||
}
|
||||
|
||||
const TypeState& get_types_for_op_mode(int atomic_op_id, AccessMode mode) const {
|
||||
if (mode == AccessMode::READ) {
|
||||
return get_types_before_op(atomic_op_id);
|
||||
} else {
|
||||
return get_types_after_op(atomic_op_id);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the types in registers at the beginning of this basic block, before any operations
|
||||
* have occurred.
|
||||
@@ -92,8 +101,8 @@ class Env {
|
||||
|
||||
bool allow_sloppy_pair_typing() const { return m_allow_sloppy_pair_typing; }
|
||||
void set_sloppy_pair_typing() { m_allow_sloppy_pair_typing = true; }
|
||||
void set_type_hints(const std::unordered_map<int, std::vector<TypeHint>>& hints) {
|
||||
m_typehints = hints;
|
||||
void set_type_casts(const std::unordered_map<int, std::vector<TypeCast>>& casts) {
|
||||
m_typecasts = casts;
|
||||
}
|
||||
|
||||
void set_remap_for_function(int nargs);
|
||||
@@ -101,6 +110,8 @@ class Env {
|
||||
void set_remap_for_new_method(int nargs);
|
||||
void map_args_from_config(const std::vector<std::string>& args_names,
|
||||
const std::unordered_map<std::string, std::string>& var_names);
|
||||
void map_args_from_config(const std::vector<std::string>& args_names,
|
||||
const std::unordered_map<std::string, LocalVarOverride>& var_overrides);
|
||||
|
||||
const std::string& remapped_name(const std::string& name) const;
|
||||
|
||||
@@ -130,6 +141,8 @@ class Env {
|
||||
|
||||
void set_defined_in_let(const std::string& var) { m_vars_defined_in_let.insert(var); }
|
||||
|
||||
void set_retype_map(const std::unordered_map<std::string, TypeSpec>& map) { m_var_retype = map; }
|
||||
|
||||
LinkedObjectFile* file = nullptr;
|
||||
DecompilerTypeSystem* dts = nullptr;
|
||||
|
||||
@@ -149,8 +162,9 @@ class Env {
|
||||
|
||||
bool m_allow_sloppy_pair_typing = false;
|
||||
|
||||
std::unordered_map<int, std::vector<TypeHint>> m_typehints;
|
||||
std::unordered_map<int, std::vector<TypeCast>> m_typecasts;
|
||||
std::unordered_map<std::string, std::string> m_var_remap;
|
||||
std::unordered_map<std::string, TypeSpec> m_var_retype;
|
||||
std::unordered_map<std::string, LabelType> m_label_types;
|
||||
|
||||
std::unordered_set<std::string> m_vars_defined_in_let;
|
||||
|
||||
@@ -284,8 +284,13 @@ void SimpleAtomElement::get_modified_regs(RegSet& regs) const {
|
||||
SetVarElement::SetVarElement(const RegisterAccess& var,
|
||||
Form* value,
|
||||
bool is_sequence_point,
|
||||
TypeSpec src_type,
|
||||
const SetVarInfo& info)
|
||||
: m_dst(var), m_src(value), m_is_sequence_point(is_sequence_point), m_var_info(info) {
|
||||
: m_dst(var),
|
||||
m_src(value),
|
||||
m_is_sequence_point(is_sequence_point),
|
||||
m_src_type(src_type),
|
||||
m_var_info(info) {
|
||||
value->parent_element = this;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ class SetVarElement : public FormElement {
|
||||
SetVarElement(const RegisterAccess& var,
|
||||
Form* value,
|
||||
bool is_sequence_point,
|
||||
TypeSpec src_type,
|
||||
const SetVarInfo& info = {});
|
||||
goos::Object to_form_internal(const Env& env) const override;
|
||||
void apply(const std::function<void(FormElement*)>& f) override;
|
||||
@@ -266,12 +267,13 @@ class SetVarElement : public FormElement {
|
||||
void mark_as_dead_false() { m_var_info.is_dead_false = true; }
|
||||
|
||||
const SetVarInfo& info() const { return m_var_info; }
|
||||
const TypeSpec src_type() const { return m_src_type; }
|
||||
|
||||
private:
|
||||
RegisterAccess m_dst;
|
||||
Form* m_src = nullptr;
|
||||
bool m_is_sequence_point = true;
|
||||
|
||||
TypeSpec m_src_type;
|
||||
SetVarInfo m_var_info;
|
||||
};
|
||||
|
||||
|
||||
@@ -999,7 +999,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta
|
||||
|
||||
// hack for method stuff
|
||||
if (is_dead_set()) {
|
||||
stack.push_value_to_reg_dead(m_dst, m_src, true, m_var_info);
|
||||
stack.push_value_to_reg_dead(m_dst, m_src, true, m_src_type, m_var_info);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1020,7 +1020,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta
|
||||
auto& info = env.reg_use().op.at(var.idx());
|
||||
if (info.consumes.find(var.reg()) != info.consumes.end()) {
|
||||
stack.push_non_seq_reg_to_reg(m_dst, src_as_se->expr().get_arg(0).var(), m_src,
|
||||
m_var_info);
|
||||
m_src_type, m_var_info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +1043,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta
|
||||
// not sure this is the best place for this.
|
||||
stack.push_value_to_reg(m_dst,
|
||||
pool.alloc_single_element_form<ConstantFloatElement>(nullptr, 0.0),
|
||||
true, m_var_info);
|
||||
true, m_src_type, m_var_info);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1058,7 +1058,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta
|
||||
}
|
||||
}
|
||||
|
||||
stack.push_value_to_reg(m_dst, m_src, true, m_var_info);
|
||||
stack.push_value_to_reg(m_dst, m_src, true, m_src_type, m_var_info);
|
||||
for (auto x : m_src->elts()) {
|
||||
assert(x->parent_form == m_src);
|
||||
}
|
||||
@@ -1648,7 +1648,9 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
|
||||
}
|
||||
|
||||
if (used_as_value) {
|
||||
stack.push_value_to_reg(final_destination, pool.alloc_single_form(nullptr, this), true);
|
||||
// TODO - is this wrong?
|
||||
stack.push_value_to_reg(final_destination, pool.alloc_single_form(nullptr, this), true,
|
||||
env.get_variable_type(final_destination));
|
||||
} else {
|
||||
stack.push_form_element(this, true);
|
||||
}
|
||||
@@ -1772,7 +1774,8 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
|
||||
if (set_unused) {
|
||||
stack.push_form_element(this, true);
|
||||
} else {
|
||||
stack.push_value_to_reg(*last_var, pool.alloc_single_form(nullptr, this), true);
|
||||
stack.push_value_to_reg(*last_var, pool.alloc_single_form(nullptr, this), true,
|
||||
env.get_variable_type(*last_var));
|
||||
}
|
||||
} else {
|
||||
stack.push_form_element(this, true);
|
||||
@@ -1830,7 +1833,8 @@ void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStac
|
||||
}
|
||||
|
||||
assert(used_as_value.has_value());
|
||||
stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true);
|
||||
stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true,
|
||||
env.get_variable_type(final_result));
|
||||
already_rewritten = true;
|
||||
}
|
||||
}
|
||||
@@ -2533,7 +2537,7 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
|
||||
auto popped = pop_to_forms({old_value, source}, env, pool, stack, true);
|
||||
if (!is_symbol_true(popped.at(0))) {
|
||||
lg::warn("Failed to ConditionalMoveFalseElement::push_to_stack");
|
||||
stack.push_value_to_reg(source, popped.at(1), true);
|
||||
stack.push_value_to_reg(source, popped.at(1), true, TypeSpec("symbol"));
|
||||
stack.push_form_element(this, true);
|
||||
return;
|
||||
}
|
||||
@@ -2543,7 +2547,7 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
|
||||
GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO
|
||||
: IR2_Condition::Kind::ZERO),
|
||||
std::vector<Form*>{popped.at(1)}),
|
||||
true);
|
||||
true, TypeSpec("symbol"));
|
||||
}
|
||||
|
||||
void SimpleAtomElement::update_from_stack(const Env&,
|
||||
|
||||
@@ -41,6 +41,7 @@ std::string FormStack::print(const Env& env) {
|
||||
void FormStack::push_value_to_reg(RegisterAccess var,
|
||||
Form* value,
|
||||
bool sequence_point,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info) {
|
||||
assert(value);
|
||||
StackEntry entry;
|
||||
@@ -48,6 +49,7 @@ void FormStack::push_value_to_reg(RegisterAccess var,
|
||||
entry.sequence_point = sequence_point;
|
||||
entry.destination = var;
|
||||
entry.source = value;
|
||||
entry.set_type = type;
|
||||
entry.set_info = info;
|
||||
m_stack.push_back(entry);
|
||||
}
|
||||
@@ -55,6 +57,7 @@ void FormStack::push_value_to_reg(RegisterAccess var,
|
||||
void FormStack::push_value_to_reg_dead(RegisterAccess var,
|
||||
Form* value,
|
||||
bool sequence_point,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info) {
|
||||
assert(value);
|
||||
StackEntry entry;
|
||||
@@ -62,6 +65,7 @@ void FormStack::push_value_to_reg_dead(RegisterAccess var,
|
||||
entry.sequence_point = sequence_point;
|
||||
entry.destination = var;
|
||||
entry.source = value;
|
||||
entry.set_type = type;
|
||||
entry.set_info = info;
|
||||
m_stack.push_back(entry);
|
||||
}
|
||||
@@ -69,6 +73,7 @@ void FormStack::push_value_to_reg_dead(RegisterAccess var,
|
||||
void FormStack::push_non_seq_reg_to_reg(const RegisterAccess& dst,
|
||||
const RegisterAccess& src,
|
||||
Form* src_as_form,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info) {
|
||||
assert(src_as_form);
|
||||
StackEntry entry;
|
||||
@@ -77,6 +82,7 @@ void FormStack::push_non_seq_reg_to_reg(const RegisterAccess& dst,
|
||||
entry.destination = dst;
|
||||
entry.non_seq_source = src;
|
||||
entry.source = src_as_form;
|
||||
entry.set_type = type;
|
||||
entry.set_info = info;
|
||||
entry.is_compactable = true;
|
||||
m_stack.push_back(entry);
|
||||
@@ -217,7 +223,7 @@ FormElement* FormStack::pop_back(FormPool& pool) {
|
||||
} else {
|
||||
assert(back.destination.has_value());
|
||||
auto elt = pool.alloc_element<SetVarElement>(*back.destination, back.source,
|
||||
back.sequence_point, back.set_info);
|
||||
back.sequence_point, back.set_type, back.set_info);
|
||||
back.source->parent_element = elt;
|
||||
return elt;
|
||||
}
|
||||
@@ -320,7 +326,7 @@ std::vector<FormElement*> FormStack::rewrite(FormPool& pool, const Env& env) {
|
||||
}
|
||||
|
||||
auto elt = pool.alloc_element<SetVarElement>(*e.destination, simplified_source,
|
||||
e.sequence_point, e.set_info);
|
||||
e.sequence_point, e.set_type, e.set_info);
|
||||
e.source->parent_element = elt;
|
||||
|
||||
auto final_elt = try_rewrites_in_place(elt, env, pool);
|
||||
|
||||
@@ -16,15 +16,19 @@ class FormStack {
|
||||
void push_value_to_reg(RegisterAccess var,
|
||||
Form* value,
|
||||
bool sequence_point,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info = {});
|
||||
void push_non_seq_reg_to_reg(const RegisterAccess& dst,
|
||||
const RegisterAccess& src,
|
||||
Form* src_as_form,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info = {});
|
||||
void push_value_to_reg_dead(RegisterAccess var,
|
||||
Form* value,
|
||||
bool sequence_point,
|
||||
TypeSpec type,
|
||||
const SetVarInfo& info = {});
|
||||
|
||||
void push_form_element(FormElement* elt, bool sequence_point);
|
||||
Form* pop_reg(const RegisterAccess& var,
|
||||
const RegSet& barrier,
|
||||
@@ -53,10 +57,11 @@ class FormStack {
|
||||
|
||||
FormElement* elt = nullptr;
|
||||
bool sequence_point = false;
|
||||
TP_Type type;
|
||||
// TP_Type type;
|
||||
bool is_compactable = false;
|
||||
|
||||
SetVarInfo set_info;
|
||||
TypeSpec set_type;
|
||||
|
||||
std::string print(const Env& env) const;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "decompiler/IR/BasicOpBuilder.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
#include "common/log/log.h"
|
||||
#include "third-party/json.hpp"
|
||||
#include "common/util/json_util.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
@@ -162,7 +162,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
}
|
||||
|
||||
void ObjectFileDB::load_map_file(const std::string& map_data) {
|
||||
auto j = nlohmann::json::parse(map_data, nullptr, true, true);
|
||||
auto j = parse_commented_json(map_data);
|
||||
|
||||
for (auto& x : j) {
|
||||
auto mapped_name = x[0].get<std::string>();
|
||||
|
||||
@@ -293,10 +293,12 @@ void ObjectFileDB::ir2_type_analysis_pass() {
|
||||
func.type = ts;
|
||||
attempted_functions++;
|
||||
// try type analysis here.
|
||||
auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()];
|
||||
auto hints =
|
||||
get_config().type_casts_by_function_by_atomic_op_idx[func.guessed_name.to_string()];
|
||||
auto label_types = get_config().label_types[data.to_unique_name()];
|
||||
if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints, label_types)) {
|
||||
successful_functions++;
|
||||
func.ir2.types_succeeded = true;
|
||||
} else {
|
||||
func.warnings.type_prop_warning("Type analysis failed");
|
||||
}
|
||||
@@ -419,7 +421,17 @@ void ObjectFileDB::ir2_build_expressions() {
|
||||
total++;
|
||||
if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars()) {
|
||||
attempted++;
|
||||
if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func, dts)) {
|
||||
auto name = func.guessed_name.to_string();
|
||||
auto arg_config = get_config().function_arg_names.find(name);
|
||||
auto var_config = get_config().function_var_overrides.find(name);
|
||||
if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func,
|
||||
arg_config != get_config().function_arg_names.end()
|
||||
? arg_config->second
|
||||
: std::vector<std::string>{},
|
||||
var_config != get_config().function_var_overrides.end()
|
||||
? var_config->second
|
||||
: std::unordered_map<std::string, LocalVarOverride>{},
|
||||
dts)) {
|
||||
successful++;
|
||||
func.ir2.print_debug_forms = true;
|
||||
func.ir2.expressions_succeeded = true;
|
||||
|
||||
@@ -618,7 +618,7 @@ void convert_cond_no_else_to_compare(FormPool& pool,
|
||||
dynamic_cast<BranchElement*>(cne->entries.front().condition->try_as_single_element());
|
||||
auto condition_replacement = condition.first->op()->get_condition_as_form(pool, f.ir2.env);
|
||||
auto crf = pool.alloc_single_form(nullptr, condition_replacement);
|
||||
auto replacement = pool.alloc_element<SetVarElement>(dst, crf, true);
|
||||
auto replacement = pool.alloc_element<SetVarElement>(dst, crf, true, TypeSpec("symbol"));
|
||||
replacement->parent_form = cne->parent_form;
|
||||
|
||||
if (condition_as_single) {
|
||||
@@ -1004,7 +1004,7 @@ Form* try_sc_as_abs(FormPool& pool, Function& f, const ShortCircuit* vtx) {
|
||||
}
|
||||
|
||||
auto src_abs = pool.alloc_single_element_form<AbsElement>(nullptr, input.var(), consumed);
|
||||
auto replacement = pool.alloc_element<SetVarElement>(output, src_abs, true);
|
||||
auto replacement = pool.alloc_element<SetVarElement>(output, src_abs, true, TypeSpec("int"));
|
||||
b0_ptr->push_back(replacement);
|
||||
|
||||
return b0_ptr;
|
||||
@@ -1117,7 +1117,7 @@ Form* try_sc_as_ash(FormPool& pool, Function& f, const ShortCircuit* vtx) {
|
||||
// setup
|
||||
auto ash_form = pool.alloc_single_element_form<AshElement>(
|
||||
nullptr, shift_ir.var(), value_ir.var(), clobber_ir, is_arith, consumed);
|
||||
auto set_form = pool.alloc_element<SetVarElement>(dest_ir, ash_form, true);
|
||||
auto set_form = pool.alloc_element<SetVarElement>(dest_ir, ash_form, true, TypeSpec("int"));
|
||||
b0_c_ptr->push_back(set_form);
|
||||
|
||||
// fix up reg info
|
||||
@@ -1268,7 +1268,7 @@ Form* try_sc_as_type_of(FormPool& pool, Function& f, const ShortCircuit* vtx) {
|
||||
auto obj = pool.alloc_single_element_form<SimpleExpressionElement>(
|
||||
nullptr, shift->expr().get_arg(0).as_expr(), set_shift->dst().idx());
|
||||
auto type_op = pool.alloc_single_element_form<TypeOfElement>(nullptr, obj, clobber);
|
||||
auto op = pool.alloc_element<SetVarElement>(else_case->dst(), type_op, true);
|
||||
auto op = pool.alloc_element<SetVarElement>(else_case->dst(), type_op, true, TypeSpec("type"));
|
||||
b0_ptr->push_back(op);
|
||||
|
||||
// fix register info
|
||||
|
||||
@@ -7,66 +7,27 @@
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
/*void clean_up_ifs(Form* top_level_form, const Env&) {
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
for (auto x : top_level_form->elts()) {
|
||||
assert(x->parent_form == top_level_form);
|
||||
}
|
||||
changed = false;
|
||||
|
||||
for (auto x : top_level_form->elts()) {
|
||||
assert(x->parent_form == top_level_form);
|
||||
}
|
||||
top_level_form->apply([&](FormElement* elt) {
|
||||
auto as_ge = dynamic_cast<GenericElement*>(elt);
|
||||
if (!as_ge) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (as_ge->op().kind() == GenericOperator::Kind::CONDITION_OPERATOR) {
|
||||
if (as_ge->op().condition_kind() == IR2_Condition::Kind::TRUTHY) {
|
||||
assert(as_ge->elts().size() == 1);
|
||||
auto top_condition = as_ge->elts().front();
|
||||
if (!top_condition->is_single_element() && elt->parent_form) {
|
||||
auto real_condition = top_condition->back();
|
||||
top_condition->pop_back();
|
||||
|
||||
auto& parent_vector = elt->parent_form->elts();
|
||||
// find us in the parent vector
|
||||
auto me = std::find_if(parent_vector.begin(), parent_vector.end(),
|
||||
[&](FormElement* x) { return x == elt; });
|
||||
assert(me != parent_vector.end());
|
||||
|
||||
// now insert the fake condition
|
||||
for (auto& x : top_condition->elts()) {
|
||||
x->parent_form = elt->parent_form;
|
||||
}
|
||||
parent_vector.insert(me, top_condition->elts().begin(), top_condition->elts().end());
|
||||
top_condition->elts() = {real_condition};
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
bool convert_to_expressions(Form* top_level_form,
|
||||
FormPool& pool,
|
||||
Function& f,
|
||||
const DecompilerTypeSystem& dts) {
|
||||
/*!
|
||||
* The main expression building pass.
|
||||
*/
|
||||
bool convert_to_expressions(
|
||||
Form* top_level_form,
|
||||
FormPool& pool,
|
||||
Function& f,
|
||||
const std::vector<std::string>& arg_names,
|
||||
const std::unordered_map<std::string, LocalVarOverride>& var_override_map,
|
||||
const DecompilerTypeSystem& dts) {
|
||||
assert(top_level_form);
|
||||
|
||||
// fmt::print("Before anything:\n{}\n",
|
||||
// pretty_print::to_string(top_level_form->to_form(f.ir2.env)));
|
||||
try {
|
||||
// create the root expression stack for the function
|
||||
FormStack stack(true);
|
||||
// and add all entries
|
||||
for (auto& entry : top_level_form->elts()) {
|
||||
// fmt::print("push {} to stack\n", entry->to_form(f.ir2.env).print());
|
||||
entry->push_to_stack(f.ir2.env, pool, stack);
|
||||
// fmt::print("Stack is now:\n{}\n", stack.print(f.ir2.env));
|
||||
}
|
||||
|
||||
// rewrite the stack to get the correct final value
|
||||
std::vector<FormElement*> new_entries;
|
||||
if (f.type.last_arg() != TypeSpec("none")) {
|
||||
auto return_var = f.ir2.atomic_ops->end_op().return_var();
|
||||
@@ -82,20 +43,26 @@ bool convert_to_expressions(Form* top_level_form,
|
||||
new_entries.push_back(cast);
|
||||
}
|
||||
} else {
|
||||
// or just get all the expressions
|
||||
new_entries = stack.rewrite(pool, f.ir2.env);
|
||||
}
|
||||
|
||||
// if we are a totally empty function, insert a placeholder so we don't have to handle
|
||||
// the zero element case ever.
|
||||
if (new_entries.empty()) {
|
||||
new_entries.push_back(pool.alloc_element<EmptyElement>());
|
||||
}
|
||||
|
||||
// turn us back into a form.
|
||||
top_level_form->clear();
|
||||
for (auto x : new_entries) {
|
||||
top_level_form->push_back(x);
|
||||
}
|
||||
|
||||
// and sanity check for tree errors.
|
||||
for (auto x : top_level_form->elts()) {
|
||||
assert(x->parent_form == top_level_form);
|
||||
}
|
||||
// fix up stuff
|
||||
|
||||
} catch (std::exception& e) {
|
||||
f.warnings.expression_build_warning("In {}: {}", f.guessed_name.to_string(), e.what());
|
||||
@@ -103,6 +70,8 @@ bool convert_to_expressions(Form* top_level_form,
|
||||
return false;
|
||||
}
|
||||
|
||||
// set argument names to some reasonable defaults. these will be used if the user doesn't
|
||||
// give us anything more specific.
|
||||
if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) {
|
||||
f.ir2.env.set_remap_for_function(f.type.arg_count() - 1);
|
||||
} else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) {
|
||||
@@ -113,20 +82,18 @@ bool convert_to_expressions(Form* top_level_form,
|
||||
}
|
||||
}
|
||||
|
||||
auto config_map = get_config().function_arg_names.find(f.guessed_name.to_string());
|
||||
if (config_map != get_config().function_arg_names.end()) {
|
||||
std::unordered_map<std::string, std::string> map2;
|
||||
auto var_map = get_config().function_var_names.find(f.guessed_name.to_string());
|
||||
if (var_map != get_config().function_var_names.end()) {
|
||||
map2 = var_map->second;
|
||||
}
|
||||
f.ir2.env.map_args_from_config(config_map->second, map2);
|
||||
} else {
|
||||
auto var_map = get_config().function_var_names.find(f.guessed_name.to_string());
|
||||
if (var_map != get_config().function_var_names.end()) {
|
||||
f.ir2.env.map_args_from_config({}, var_map->second);
|
||||
// get variable names from the user.
|
||||
f.ir2.env.map_args_from_config(arg_names, var_override_map);
|
||||
|
||||
// override variable types from the user.
|
||||
|
||||
std::unordered_map<std::string, TypeSpec> retype;
|
||||
for (auto& remap : var_override_map) {
|
||||
if (remap.second.type) {
|
||||
retype[remap.first] = dts.parse_type_spec(*remap.second.type);
|
||||
}
|
||||
}
|
||||
f.ir2.env.set_retype_map(retype);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
class Form;
|
||||
class Function;
|
||||
class FormPool;
|
||||
class DecompilerTypeSystem;
|
||||
bool convert_to_expressions(Form* top_level_form,
|
||||
FormPool& pool,
|
||||
Function& f,
|
||||
const DecompilerTypeSystem& dts);
|
||||
struct LocalVarOverride;
|
||||
bool convert_to_expressions(
|
||||
Form* top_level_form,
|
||||
FormPool& pool,
|
||||
Function& f,
|
||||
const std::vector<std::string>& arg_names,
|
||||
const std::unordered_map<std::string, LocalVarOverride>& var_override_map,
|
||||
const DecompilerTypeSystem& dts);
|
||||
} // namespace decompiler
|
||||
@@ -216,6 +216,21 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool) {
|
||||
// nothing matched.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Form* insert_cast_for_let(RegisterAccess dst,
|
||||
const TypeSpec& src_type,
|
||||
Form* src,
|
||||
FormPool& pool,
|
||||
const Env& env) {
|
||||
auto dst_type = env.get_variable_type(dst);
|
||||
|
||||
if (src_type != dst_type) {
|
||||
// fmt::print("inserting let cast because {} != {}\n", dst_type.print(), src_type.print());
|
||||
return pool.alloc_single_element_form<CastElement>(nullptr, dst_type, src);
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_level_form) {
|
||||
@@ -407,7 +422,10 @@ LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_l
|
||||
}
|
||||
assert(elt_idx == let_desc.end_elt);
|
||||
auto new_let = pool.alloc_element<LetElement>(pool.alloc_sequence_form(nullptr, body));
|
||||
new_let->add_def(let_desc.set_form->dst(), let_desc.set_form->src());
|
||||
// insert a cast, if needed.
|
||||
auto casted_src = insert_cast_for_let(let_desc.set_form->dst(), let_desc.set_form->src_type(),
|
||||
let_desc.set_form->src(), pool, env);
|
||||
new_let->add_def(let_desc.set_form->dst(), casted_src);
|
||||
env.set_defined_in_let(let_desc.name);
|
||||
lets.at(let_idx) = new_let;
|
||||
}
|
||||
|
||||
@@ -611,6 +611,45 @@ void SSA::make_vars(const Function& function, const DecompilerTypeSystem& dts) {
|
||||
init_types = end_types;
|
||||
}
|
||||
}
|
||||
|
||||
// override the types of the variables for function arguments:
|
||||
assert(function.type.arg_count() > 0);
|
||||
for (int arg_idx = 0; arg_idx < int(function.type.arg_count()) - 1; arg_idx++) {
|
||||
auto arg_reg = Register::get_arg_reg(arg_idx);
|
||||
if (!program_read_vars[arg_reg].empty()) {
|
||||
program_read_vars[arg_reg].at(0).type = TP_Type::make_from_ts(function.type.get_arg(arg_idx));
|
||||
}
|
||||
|
||||
if (!program_write_vars[arg_reg].empty()) {
|
||||
program_write_vars[arg_reg].at(0).type =
|
||||
TP_Type::make_from_ts(function.type.get_arg(arg_idx));
|
||||
}
|
||||
}
|
||||
|
||||
// copy types from input argument coloring moves:
|
||||
for (auto& instr : blocks.at(0).ins) {
|
||||
if (instr.is_arg_coloring_move) {
|
||||
auto src_ssa = instr.src.at(0);
|
||||
for (int arg_idx = 0; arg_idx < int(function.type.arg_count()) - 1; arg_idx++) {
|
||||
if (Register::get_arg_reg(arg_idx) == src_ssa.reg()) {
|
||||
// copy the type from here.
|
||||
auto dst = instr.dst;
|
||||
assert(dst);
|
||||
auto dst_reg = instr.dst->reg();
|
||||
auto dst_varid = map.var_id(*dst);
|
||||
if ((int)program_read_vars[dst_reg].size() > dst_varid) {
|
||||
program_read_vars[dst_reg].at(dst_varid).type =
|
||||
TP_Type::make_from_ts(function.type.get_arg(arg_idx));
|
||||
}
|
||||
|
||||
if ((int)program_write_vars[dst_reg].size() > dst_varid) {
|
||||
program_write_vars[dst_reg].at(dst_varid).type =
|
||||
TP_Type::make_from_ts(function.type.get_arg(arg_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remap_color_move(
|
||||
|
||||
+25
-16
@@ -2,6 +2,7 @@
|
||||
#include "third-party/json.hpp"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/json_util.h"
|
||||
|
||||
namespace decompiler {
|
||||
Config gConfig;
|
||||
@@ -18,7 +19,7 @@ namespace {
|
||||
nlohmann::json read_json_file_from_config(const nlohmann::json& cfg, const std::string& file_key) {
|
||||
auto file_name = cfg.at(file_key).get<std::string>();
|
||||
auto file_txt = file_util::read_text_file(file_util::get_file_path({file_name}));
|
||||
return nlohmann::json::parse(file_txt, nullptr, true, true);
|
||||
return parse_commented_json(file_txt);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -27,8 +28,7 @@ nlohmann::json read_json_file_from_config(const nlohmann::json& cfg, const std::
|
||||
*/
|
||||
void set_config(const std::string& path_to_config_file) {
|
||||
auto config_str = file_util::read_text_file(path_to_config_file);
|
||||
// to ignore comments in json, which may be useful
|
||||
auto cfg = nlohmann::json::parse(config_str, nullptr, true, true);
|
||||
auto cfg = parse_commented_json(config_str);
|
||||
|
||||
gConfig.game_version = cfg.at("game_version").get<int>();
|
||||
gConfig.dgo_names = cfg.at("dgo_names").get<std::vector<std::string>>();
|
||||
@@ -83,18 +83,18 @@ void set_config(const std::string& path_to_config_file) {
|
||||
gConfig.allowed_objects.insert(x);
|
||||
}
|
||||
|
||||
auto type_hints_json = read_json_file_from_config(cfg, "type_hints_file");
|
||||
for (auto& kv : type_hints_json.items()) {
|
||||
auto type_casts_json = read_json_file_from_config(cfg, "type_casts_file");
|
||||
for (auto& kv : type_casts_json.items()) {
|
||||
auto& function_name = kv.key();
|
||||
auto& hints = kv.value();
|
||||
for (auto& hint : hints) {
|
||||
auto idx = hint.at(0).get<int>();
|
||||
for (size_t i = 1; i < hint.size(); i++) {
|
||||
auto& assignment = hint.at(i);
|
||||
TypeHint type_hint;
|
||||
type_hint.reg = Register(assignment.at(0).get<std::string>());
|
||||
type_hint.type_name = assignment.at(1).get<std::string>();
|
||||
gConfig.type_hints_by_function_by_idx[function_name][idx].push_back(type_hint);
|
||||
auto& casts = kv.value();
|
||||
for (auto& cast : casts) {
|
||||
auto idx_range = parse_json_optional_integer_range(cast.at(0));
|
||||
for (auto idx : idx_range) {
|
||||
TypeCast type_cast;
|
||||
type_cast.atomic_op_idx = idx;
|
||||
type_cast.reg = Register(cast.at(1));
|
||||
type_cast.type_name = cast.at(2).get<std::string>();
|
||||
gConfig.type_casts_by_function_by_atomic_op_idx[function_name][idx].push_back(type_cast);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,8 +121,17 @@ void set_config(const std::string& path_to_config_file) {
|
||||
|
||||
auto var = kv.value().find("vars");
|
||||
if (var != kv.value().end()) {
|
||||
for (auto& vkv : var->get<std::unordered_map<std::string, std::string>>()) {
|
||||
gConfig.function_var_names[function_name][vkv.first] = vkv.second;
|
||||
for (auto& vkv : var->get<std::unordered_map<std::string, nlohmann::json>>()) {
|
||||
LocalVarOverride override;
|
||||
if (vkv.second.is_string()) {
|
||||
override.name = vkv.second.get<std::string>();
|
||||
} else if (vkv.second.is_array()) {
|
||||
override.name = vkv.second[0].get<std::string>();
|
||||
override.type = vkv.second[1].get<std::string>();
|
||||
} else {
|
||||
throw std::runtime_error("Invalid function var override.");
|
||||
}
|
||||
gConfig.function_var_overrides[function_name][vkv.first] = override;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -8,7 +8,8 @@
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
namespace decompiler {
|
||||
struct TypeHint {
|
||||
struct TypeCast {
|
||||
int atomic_op_idx = -1;
|
||||
Register reg;
|
||||
std::string type_name;
|
||||
};
|
||||
@@ -19,6 +20,13 @@ struct LabelType {
|
||||
std::optional<int> array_size;
|
||||
};
|
||||
|
||||
struct LocalVarOverride {
|
||||
std::string name;
|
||||
// this may be left out, indicating that the variable should use the type determined
|
||||
// by the type analysis pass.
|
||||
std::optional<std::string> type;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
int game_version = -1;
|
||||
std::vector<std::string> dgo_names;
|
||||
@@ -45,12 +53,13 @@ struct Config {
|
||||
std::unordered_set<std::string> pair_functions_by_name;
|
||||
std::unordered_set<std::string> no_type_analysis_functions_by_name;
|
||||
std::unordered_set<std::string> allowed_objects;
|
||||
std::unordered_map<std::string, std::unordered_map<int, std::vector<TypeHint>>>
|
||||
type_hints_by_function_by_idx;
|
||||
std::unordered_map<std::string, std::unordered_map<int, std::vector<TypeCast>>>
|
||||
type_casts_by_function_by_atomic_op_idx;
|
||||
std::unordered_map<std::string, std::unordered_map<int, std::string>>
|
||||
anon_function_types_by_obj_by_id;
|
||||
std::unordered_map<std::string, std::vector<std::string>> function_arg_names;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> function_var_names;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, LocalVarOverride>>
|
||||
function_var_overrides;
|
||||
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, LabelType>> label_types;
|
||||
bool run_ir2 = false;
|
||||
|
||||
@@ -222,9 +222,9 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(stack-size-set! ((this thread) (stack-size int)) none 9)
|
||||
(thread-suspend ((this _type_)) none 10)
|
||||
(thread-resume ((to-resume _type_)) none 11)
|
||||
(stack-size-set! (_type_ int) none 9)
|
||||
(thread-suspend (_type_) none 10)
|
||||
(thread-resume (_type_) none 11)
|
||||
)
|
||||
|
||||
:method-count-assert 12
|
||||
@@ -244,9 +244,9 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (parent-process process) (name symbol) (stack-size int) (stack-top pointer)) _type_ 0)
|
||||
(thread-suspend ((this _type_)) none 10)
|
||||
(thread-resume ((to-resume _type_)) none 11)
|
||||
(new (symbol type process symbol int pointer) _type_ 0)
|
||||
(thread-suspend (_type_) none 10)
|
||||
(thread-resume (_type_) none 11)
|
||||
)
|
||||
|
||||
:method-count-assert 12
|
||||
@@ -266,11 +266,11 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0)
|
||||
(activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9)
|
||||
(deactivate ((obj _type_)) none 10)
|
||||
(new (symbol type basic) _type_ 0)
|
||||
(activate (_type_ process-tree basic pointer) process-tree 9)
|
||||
(deactivate (_type_) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? ((obj _type_)) symbol 12)
|
||||
(run-logic? (_type_) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
)
|
||||
|
||||
@@ -303,11 +303,11 @@
|
||||
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0)
|
||||
(activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9)
|
||||
(deactivate ((obj process)) none 10)
|
||||
(new (symbol type basic int) _type_ 0)
|
||||
(activate (_type_ process-tree basic pointer) process-tree 9)
|
||||
(deactivate (process) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? ((obj process)) symbol 12)
|
||||
(run-logic? (process) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
)
|
||||
|
||||
@@ -322,9 +322,9 @@
|
||||
;; nothing new!
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (count int) (stack-size int) (name basic)) _type_ 0)
|
||||
(get-process ((pool _type_) (type-to-make type) (stack-size int)) process 14)
|
||||
(return-process ((pool _type_) (proc process)) none 15)
|
||||
(new (symbol type int int basic) _type_ 0)
|
||||
(get-process (_type_ type int) process 14)
|
||||
(return-process ( _type_ process) none 15)
|
||||
)
|
||||
:size-assert #x20
|
||||
:method-count-assert 16
|
||||
@@ -360,18 +360,18 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (allocated-length int) (heap-size int)) _type_ 0)
|
||||
(compact ((this dead-pool-heap) (count int)) none 16)
|
||||
(shrink-heap ((this dead-pool-heap) (proc process)) dead-pool-heap 17)
|
||||
(churn ((this dead-pool-heap) (count int)) none 18)
|
||||
(memory-used ((this dead-pool-heap)) int 19)
|
||||
(memory-total ((this dead-pool-heap)) int 20)
|
||||
(gap-size ((this dead-pool-heap) (rec dead-pool-heap-rec)) int 21)
|
||||
(gap-location ((this dead-pool-heap) (rec dead-pool-heap-rec)) pointer 22)
|
||||
(find-gap ((this dead-pool-heap) (rec dead-pool-heap-rec)) dead-pool-heap-rec 23)
|
||||
(find-gap-by-size ((this dead-pool-heap) (size int)) dead-pool-heap-rec 24)
|
||||
(memory-free ((this dead-pool-heap)) int 25)
|
||||
(compact-time ((this dead-pool-heap)) uint 26)
|
||||
(new (symbol type basic int int) _type_ 0)
|
||||
(compact (dead-pool-heap int) none 16)
|
||||
(shrink-heap (dead-pool-heap process) dead-pool-heap 17)
|
||||
(churn (dead-pool-heap int) none 18)
|
||||
(memory-used (dead-pool-heap) int 19)
|
||||
(memory-total (dead-pool-heap) int 20)
|
||||
(gap-size (dead-pool-heap dead-pool-heap-rec) int 21)
|
||||
(gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22)
|
||||
(find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23)
|
||||
(find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24)
|
||||
(memory-free (dead-pool-heap) int 25)
|
||||
(compact-time (dead-pool-heap) uint 26)
|
||||
)
|
||||
|
||||
:method-count-assert 27
|
||||
@@ -400,7 +400,7 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name symbol) (func function) (params (pointer uint64))) object 0)
|
||||
(new (symbol type symbol function (pointer uint64)) object 0)
|
||||
)
|
||||
|
||||
:method-count-assert 9
|
||||
@@ -412,7 +412,7 @@
|
||||
((exit (function object) :offset-assert 12)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame)
|
||||
(new (symbol type (function object)) protect-frame)
|
||||
)
|
||||
|
||||
:method-count-assert 9
|
||||
@@ -438,11 +438,11 @@
|
||||
(event (function basic int basic event-message-block object) :offset-assert 32)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (code function)
|
||||
(trans (function object))
|
||||
(enter (function object object object object object object object))
|
||||
(exit (function object))
|
||||
(event (function basic int basic event-message-block object))) _type_ 0)
|
||||
(new (symbol type basic function
|
||||
(function object)
|
||||
(function object object object object object object object)
|
||||
(function object)
|
||||
(function basic int basic event-message-block object)) _type_ 0)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x24
|
||||
@@ -576,8 +576,7 @@
|
||||
;;(define-extern post object) ;; unknown type
|
||||
;;(define-extern dead object) ;; unknown type
|
||||
|
||||
;; todo
|
||||
;;(define-extern *dead-pool-list* object) ;; unknown type
|
||||
(define-extern *dead-pool-list* pair)
|
||||
|
||||
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -3187,7 +3186,7 @@
|
||||
(file uint32 :offset-assert 16)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name string) (mode basic)) _type_)
|
||||
(new (symbol type string basic) _type_)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x14
|
||||
@@ -3990,20 +3989,20 @@
|
||||
(:methods
|
||||
(new (symbol type basic int) _type_ 0)
|
||||
(inspect-all-connections (engine) none 9)
|
||||
(apply-to-connections (engine (xx (function connectable none))) none 10)
|
||||
(apply-to-connections-reverse (engine (xx (function connectable none))) none 11)
|
||||
(apply-to-connections (engine (function connectable none)) none 10)
|
||||
(apply-to-connections-reverse (engine (function connectable none)) none 11)
|
||||
(execute-connections (engine object) none 12)
|
||||
(execute-connections-and-move-to-dead (engine object) none 13)
|
||||
(execute-connections-if-needed (engine object) none 14)
|
||||
(add-connection (engine process (xx (function object object object object object)) object object object) connection 15)
|
||||
(add-connection (engine process (function object object object object object) object object object) connection 15)
|
||||
(remove-from-process (engine process) none 16)
|
||||
(remove-matching (engine (xx (function connection engine symbol))) none 17)
|
||||
(remove-matching (engine (function connection engine symbol)) none 17)
|
||||
(remove-all (engine) none 18)
|
||||
(remove-by-param1 (engine object) none 19)
|
||||
(remove-by-param2 (engine int) none 20)
|
||||
(get-first-connectable (engine) connectable 21)
|
||||
(get-last-connectable (engine) connectable 22)
|
||||
(unknown-1 (engine (xx (pointer uint32))) uint 23)
|
||||
(unknown-1 (engine (pointer uint32)) uint 23)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9202,7 +9201,7 @@
|
||||
(dummy-14 () none 14)
|
||||
(dummy-15 () none 15)
|
||||
(get-head (_type_) touching-prims-entry 16)
|
||||
(unknown1 (_type_ (xx (pointer uint32))) uint 17)
|
||||
(unknown1 (_type_ (pointer uint32)) uint 17)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"str_file_names_":[],
|
||||
"allowed_objects":["gcommon"],
|
||||
|
||||
"type_hints_file":"decompiler/config/jak1_ntsc_black_label/type_hints.jsonc",
|
||||
"type_casts_file":"decompiler/config/jak1_ntsc_black_label/type_casts.jsonc",
|
||||
"anonymous_function_types_file":"decompiler/config/jak1_ntsc_black_label/anonymous_function_types.jsonc",
|
||||
"var_names_file":"decompiler/config/jak1_ntsc_black_label/var_names.jsonc",
|
||||
"label_types_file":"decompiler/config/jak1_ntsc_black_label/label_types.jsonc",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
["L346", "uint64", true]
|
||||
],
|
||||
|
||||
"gkernel": [
|
||||
["L345", "_auto_", true]
|
||||
],
|
||||
|
||||
"math": [
|
||||
["L41", "float", true],
|
||||
["L34", "float", true],
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// This file replaces type_hints.jsonc.
|
||||
// Functions are identified by their "unique name". This is the name after ".function" in the IR2 file.
|
||||
// Each cast entry represents an override of a register's type at a certain point the function.
|
||||
// These modifications do not propagate like normal types, so you may have to apply these modifications
|
||||
// over a range of indices.
|
||||
|
||||
// Entry format: [index, register, override]
|
||||
// - The index can either be specified as a single integer, or as [min, max].
|
||||
// In the case of [min, max], the min index is included, but the max is not. ([1, 4] = 1, 2, 3).
|
||||
// - The register is a string with the plain PS2 register name.
|
||||
// - The type is a string with a valid GOAL typespec.
|
||||
// It is parsed exactly like the compiler, so you can use compound types.
|
||||
// You should only use register types here.
|
||||
|
||||
{
|
||||
// GCOMMON
|
||||
"(method 2 array)": [
|
||||
[23, "gp", "(array int32)"],
|
||||
[43, "gp", "(array uint32)"],
|
||||
[63, "gp", "(array int64)"],
|
||||
[83, "gp", "(array uint64)"],
|
||||
[102, "gp", "(array int8)"],
|
||||
[121, "gp", "(array uint8)"],
|
||||
[141, "gp", "(array int16)"],
|
||||
[161, "gp", "(array uint16)"],
|
||||
[186, "gp", "(array uint128)"],
|
||||
[204, "gp", "(array int32)"],
|
||||
[223, "gp", "(array float)"],
|
||||
[232, "gp", "(array float)"],
|
||||
[249, "gp", "(array basic)"],
|
||||
[258, "gp", "(array basic)"]
|
||||
],
|
||||
|
||||
"(method 3 array)": [
|
||||
[44, "gp", "(array int32)"],
|
||||
[62, "gp", "(array uint32)"],
|
||||
[80, "gp", "(array int64)"],
|
||||
[98, "gp", "(array uint64)"],
|
||||
[115, "gp", "(array int8)"],
|
||||
[132, "gp", "(array int8)"], // bug in game
|
||||
[150, "gp", "(array int16)"],
|
||||
[168, "gp", "(array uint16)"],
|
||||
[190, "gp", "(array uint128)"],
|
||||
[207, "gp", "(array int32)"],
|
||||
[226, "gp", "(array float)"],
|
||||
[243, "gp", "(array basic)"]
|
||||
],
|
||||
|
||||
// GKERNEL-H
|
||||
"(method 2 handle)": [
|
||||
[10, "a2", "(pointer process)"],
|
||||
[11, "a3", "process"],
|
||||
[12, "v1", "int"],
|
||||
[16, "gp", "int"]
|
||||
],
|
||||
|
||||
// GKERNEL
|
||||
"(method 0 cpu-thread)": [[[13, 28], "v0", "cpu-thread"]],
|
||||
|
||||
"(method 0 process)": [
|
||||
[12, "a0", "int"],
|
||||
[[13, 43], "v0", "process"]
|
||||
],
|
||||
|
||||
"(method 0 dead-pool-heap)": [
|
||||
[60, "v0", "int"], // a lie, actually the 115 is an align16 constant propagated on addr of heap start.
|
||||
[63, "a0", "pointer"],
|
||||
[[61, 73], "v0", "dead-pool-heap"]
|
||||
],
|
||||
|
||||
"(method 21 dead-pool-heap)": [
|
||||
[5, "v1", "pointer"],
|
||||
[13, "a0", "pointer"],
|
||||
[25, "v1", "pointer"]
|
||||
],
|
||||
|
||||
"(method 5 dead-pool-heap)": [
|
||||
[3, "v1", "int"],
|
||||
[3, "a0", "int"]
|
||||
],
|
||||
|
||||
"(method 0 protect-frame)": [
|
||||
[0, "a0", "int"],
|
||||
[[1, 8], "v0", "protect-frame"]
|
||||
],
|
||||
|
||||
"(method 10 process)": [[[24, 30], "s4", "protect-frame"]],
|
||||
|
||||
"(method 9 process)": [[43, "s5", "process"]],
|
||||
|
||||
"(method 14 dead-pool)": [
|
||||
[[24, 26], "v1", "(pointer process-tree)"],
|
||||
[[30, 39], "s4", "(pointer process-tree)"]
|
||||
],
|
||||
|
||||
"inspect-process-heap": [
|
||||
[[4, 11], "s5", "basic"],
|
||||
[17, "s5", "int"]
|
||||
],
|
||||
|
||||
"name=": [
|
||||
[24, "a1", "symbol"],
|
||||
[39, "a0", "symbol"]
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// THIS FILE IS NO LONGER USED!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// see type_casts.jsonc instead.
|
||||
|
||||
{
|
||||
"(method 2 array)":[
|
||||
[23, ["gp", "(array int32)"]],
|
||||
|
||||
@@ -195,6 +195,11 @@
|
||||
"args":["this", "rec"]
|
||||
},
|
||||
|
||||
|
||||
"(method 0 dead-pool-heap)":{
|
||||
"vars":{"v0-0":["obj", "dead-pool-heap"]}
|
||||
},
|
||||
|
||||
"seek":{
|
||||
"args":["x", "target", "diff"],
|
||||
"vars":{"f2-0":"err"}
|
||||
|
||||
@@ -88,7 +88,7 @@ void DecompilerTypeSystem::parse_type_defs(const std::vector<std::string>& file_
|
||||
});
|
||||
}
|
||||
|
||||
TypeSpec DecompilerTypeSystem::parse_type_spec(const std::string& str) {
|
||||
TypeSpec DecompilerTypeSystem::parse_type_spec(const std::string& str) const {
|
||||
auto read = m_reader.read_from_string(str);
|
||||
auto data = cdr(read);
|
||||
return parse_typespec(&ts, car(data));
|
||||
|
||||
@@ -32,7 +32,7 @@ class DecompilerTypeSystem {
|
||||
|
||||
void add_symbol(const std::string& name, const TypeSpec& type_spec);
|
||||
void parse_type_defs(const std::vector<std::string>& file_path);
|
||||
TypeSpec parse_type_spec(const std::string& str);
|
||||
TypeSpec parse_type_spec(const std::string& str) const;
|
||||
void add_type_flags(const std::string& name, u64 flags);
|
||||
void add_type_parent(const std::string& child, const std::string& parent);
|
||||
std::string dump_symbol_types();
|
||||
@@ -55,7 +55,7 @@ class DecompilerTypeSystem {
|
||||
} type_prop_settings;
|
||||
|
||||
private:
|
||||
goos::Reader m_reader;
|
||||
mutable goos::Reader m_reader;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
|
||||
+5
-1
@@ -119,4 +119,8 @@
|
||||
- Static type reference link data now has the correct number of methods. This prevents errors like `dkernel: trying to redefine a type 'game-info' with 29 methods when it had 12, try restarting` when you did not actually redefine the number of methods.
|
||||
- Added `get-info` to figure out what something is and where it's defined.
|
||||
- Added `autocomplete` to get auto-completions for a prefix.
|
||||
- Added `add-macro-to-autocomplete` to add a macro to the auto-completer.
|
||||
- Added `add-macro-to-autocomplete` to add a macro to the auto-completer.
|
||||
|
||||
## V0.7
|
||||
- There is now an option for `allow-misaligned` which allows the alignment of an struct type to be less than 16-bytes when inlined, without enabling array packing. This seems like a stupid option, but GOAL has this in some places, so we support it too.
|
||||
- In method declarations in a `deftype`, you can no longer provide argument names. There was ambiguity when parsing a compound typespec vs named argument. The names were not used for anything.
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# Directory of this script
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
$DIR/build/game/gk -fakeiso -debug -nokernel "$@"
|
||||
$DIR/build/game/gk -fakeiso -debug -nokernel -nodisplay "$@"
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
(dummy-14 () none 14)
|
||||
(dummy-15 () none 15)
|
||||
(get-head (_type_) touching-prims-entry 16)
|
||||
(unknown1 (_type_ (xx (pointer uint32))) uint 17)
|
||||
(unknown1 (_type_ (pointer uint32)) uint 17)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -101,20 +101,20 @@
|
||||
(:methods
|
||||
(new (symbol type basic int) _type_ 0)
|
||||
(inspect-all-connections (engine) none 9)
|
||||
(apply-to-connections (engine (xx (function connectable none))) none 10)
|
||||
(apply-to-connections-reverse (engine (xx (function connectable none))) none 11)
|
||||
(apply-to-connections (engine (function connectable none)) none 10)
|
||||
(apply-to-connections-reverse (engine (function connectable none)) none 11)
|
||||
(execute-connections (engine object) none 12)
|
||||
(execute-connections-and-move-to-dead (engine object) none 13)
|
||||
(execute-connections-if-needed (engine object) none 14)
|
||||
(add-connection (engine process (xx (function object object object object object)) object object object) connection 15)
|
||||
(add-connection (engine process (function object object object object object) object object object) connection 15)
|
||||
(remove-from-process (engine process) none 16)
|
||||
(remove-matching (engine (xx (function connection engine symbol))) none 17)
|
||||
(remove-matching (engine (function connection engine symbol)) none 17)
|
||||
(remove-all (engine) none 18)
|
||||
(remove-by-param1 (engine object) none 19)
|
||||
(remove-by-param2 (engine int) none 20)
|
||||
(get-first-connectable (engine) connectable 21)
|
||||
(get-last-connectable (engine) connectable 22)
|
||||
(unknown-1 (engine (xx (pointer uint32))) uint 23)
|
||||
(unknown-1 (engine (pointer uint32)) uint 23)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
(file uint32 :offset-assert 16)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name string) (mode basic)) _type_)
|
||||
(new (symbol type string basic) _type_)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x14
|
||||
|
||||
@@ -158,3 +158,48 @@
|
||||
|
||||
(define-extern rpc-call (function int uint uint uint int uint int uint))
|
||||
(define-extern rpc-busy? (function int uint))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; kernel types
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; These are types that are both:
|
||||
;; - set up in the kernel
|
||||
;; - not set up in GOAL code
|
||||
;; So these deftypes generate no code.
|
||||
|
||||
(deftype process-tree (basic)
|
||||
((name basic :offset-assert 4)
|
||||
(mask uint32 :offset-assert 8)
|
||||
(parent (pointer process-tree) :offset-assert 12)
|
||||
(brother (pointer process-tree) :offset-assert 16)
|
||||
(child (pointer process-tree) :offset-assert 20)
|
||||
(ppointer (pointer process-tree) :offset-assert 24)
|
||||
(self process-tree :offset-assert 28)
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new (symbol type basic) _type_ 0)
|
||||
(activate (_type_ process-tree basic pointer) process-tree 9)
|
||||
(deactivate (_type_) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? (_type_) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
)
|
||||
|
||||
:size-assert #x20
|
||||
:method-count-assert 14
|
||||
:no-runtime-type ;; already defined by kscheme. Don't do it again.
|
||||
)
|
||||
|
||||
(deftype stack-frame (basic)
|
||||
((name basic :offset 4)
|
||||
(next stack-frame :offset 8) ;; which way does this point?
|
||||
)
|
||||
|
||||
:size-assert #xc
|
||||
:method-count-assert 9
|
||||
:flag-assert #x90000000c
|
||||
:no-runtime-type ;; already constructed, don't do it again.
|
||||
)
|
||||
@@ -157,10 +157,9 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
;; todo, triple check these method numbers.
|
||||
(stack-size-set! ((this thread) (stack-size int)) none 9)
|
||||
(thread-suspend ((this _type_)) none 10) ;; only safe on a cpu-thread, but slot exists for thread
|
||||
(thread-resume ((to-resume _type_)) none 11) ;; only safe on a cpu-thread, but slot exists for thread
|
||||
(stack-size-set! (_type_ int) none 9)
|
||||
(thread-suspend (_type_) none 10)
|
||||
(thread-resume (_type_) none 11)
|
||||
)
|
||||
|
||||
:size-assert #x28
|
||||
@@ -185,9 +184,9 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (parent-process process) (name symbol) (stack-size int) (stack-top pointer)) _type_ 0)
|
||||
(thread-suspend ((this _type_)) none 10)
|
||||
(thread-resume ((to-resume _type_)) none 11)
|
||||
(new (symbol type process symbol int pointer) _type_ 0)
|
||||
(thread-suspend (_type_) none 10)
|
||||
(thread-resume (_type_) none 11)
|
||||
)
|
||||
|
||||
:size-assert #x80
|
||||
@@ -209,12 +208,12 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0)
|
||||
(activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9)
|
||||
(deactivate ((obj _type_)) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? ((obj _type_)) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
(new (symbol type basic) _type_ 0)
|
||||
(activate (_type_ process-tree basic pointer) process-tree 9)
|
||||
(deactivate (_type_) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? (_type_) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
)
|
||||
|
||||
:size-assert #x20
|
||||
@@ -246,12 +245,12 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0)
|
||||
(activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9)
|
||||
(deactivate ((obj process)) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? ((obj process)) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
(new (symbol type basic int) _type_ 0)
|
||||
(activate (_type_ process-tree basic pointer) process-tree 9)
|
||||
(deactivate (process) none 10)
|
||||
(dummy-method-11 () none 11)
|
||||
(run-logic? (process) symbol 12)
|
||||
(dummy-method () none 13)
|
||||
)
|
||||
|
||||
:size-assert #x70
|
||||
@@ -266,9 +265,9 @@
|
||||
;; nothing new!
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (count int) (stack-size int) (name basic)) _type_ 0)
|
||||
(get-process ((pool _type_) (type-to-make type) (stack-size int)) process 14)
|
||||
(return-process ((pool _type_) (proc process)) none 15)
|
||||
(new (symbol type int int basic) _type_ 0)
|
||||
(get-process (_type_ type int) process 14)
|
||||
(return-process ( _type_ process) none 15)
|
||||
)
|
||||
:size-assert #x20
|
||||
:method-count-assert 16
|
||||
@@ -310,18 +309,18 @@
|
||||
(process-list dead-pool-heap-rec :inline :dynamic :offset-assert 104)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (allocated-length int) (heap-size int)) _type_ 0)
|
||||
(compact ((this dead-pool-heap) (count int)) none 16)
|
||||
(shrink-heap ((this dead-pool-heap) (proc process)) dead-pool-heap 17)
|
||||
(churn ((this dead-pool-heap) (count int)) none 18)
|
||||
(memory-used ((this dead-pool-heap)) int 19)
|
||||
(memory-total ((this dead-pool-heap)) int 20)
|
||||
(gap-size ((this dead-pool-heap) (rec dead-pool-heap-rec)) int 21)
|
||||
(gap-location ((this dead-pool-heap) (rec dead-pool-heap-rec)) pointer 22)
|
||||
(find-gap ((this dead-pool-heap) (rec dead-pool-heap-rec)) dead-pool-heap-rec 23)
|
||||
(find-gap-by-size ((this dead-pool-heap) (size int)) dead-pool-heap-rec 24)
|
||||
(memory-free ((this dead-pool-heap)) int 25)
|
||||
(compact-time ((this dead-pool-heap)) uint 26)
|
||||
(new (symbol type basic int int) _type_ 0)
|
||||
(compact (dead-pool-heap int) none 16)
|
||||
(shrink-heap (dead-pool-heap process) dead-pool-heap 17)
|
||||
(churn (dead-pool-heap int) none 18)
|
||||
(memory-used (dead-pool-heap) int 19)
|
||||
(memory-total (dead-pool-heap) int 20)
|
||||
(gap-size (dead-pool-heap dead-pool-heap-rec) int 21)
|
||||
(gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22)
|
||||
(find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23)
|
||||
(find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24)
|
||||
(memory-free (dead-pool-heap) int 25)
|
||||
(compact-time (dead-pool-heap) uint 26)
|
||||
)
|
||||
|
||||
:size-assert #x68
|
||||
@@ -360,7 +359,7 @@
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name symbol) (func function) (params (pointer uint64))) object 0)
|
||||
(new (symbol type symbol function (pointer uint64)) object 0)
|
||||
)
|
||||
:size-assert #xb0
|
||||
:method-count-assert 9
|
||||
@@ -372,7 +371,7 @@
|
||||
((exit (function object) :offset-assert 12)) ;; function to call to clean up
|
||||
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame)
|
||||
(new (symbol type (function object)) protect-frame)
|
||||
)
|
||||
:size-assert 16
|
||||
:method-count-assert 9
|
||||
@@ -436,9 +435,11 @@
|
||||
(event (function basic int basic event-message-block object) :offset-assert 32)
|
||||
)
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (name basic) (code function)
|
||||
(trans (function object)) (enter (function object object object object object object object)) (exit (function object))
|
||||
(event (function basic int basic event-message-block object))) _type_ 0)
|
||||
(new (symbol type basic function
|
||||
(function object)
|
||||
(function object object object object object object object)
|
||||
(function object)
|
||||
(function basic int basic event-message-block object)) _type_ 0)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x24
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "decompiler/analysis/final_output.h"
|
||||
#include "decompiler/analysis/insert_lets.h"
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
#include "common/util/json_util.h"
|
||||
#include "decompiler/IR2/Form.h"
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
@@ -67,6 +68,43 @@ void FormRegressionTest::TestData::add_string_at_label(const std::string& label_
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::string>, std::unordered_map<std::string, LocalVarOverride>>
|
||||
parse_var_json(const std::string& str) {
|
||||
if (str.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> args;
|
||||
std::unordered_map<std::string, LocalVarOverride> var_overrides;
|
||||
|
||||
auto j = parse_commented_json(str);
|
||||
|
||||
auto arg = j.find("args");
|
||||
if (arg != j.end()) {
|
||||
for (auto& x : arg.value()) {
|
||||
args.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
auto var = j.find("vars");
|
||||
if (var != j.end()) {
|
||||
for (auto& vkv : var->get<std::unordered_map<std::string, nlohmann::json>>()) {
|
||||
LocalVarOverride override;
|
||||
if (vkv.second.is_string()) {
|
||||
override.name = vkv.second.get<std::string>();
|
||||
} else if (vkv.second.is_array()) {
|
||||
override.name = vkv.second[0].get<std::string>();
|
||||
override.type = vkv.second[1].get<std::string>();
|
||||
} else {
|
||||
throw std::runtime_error("Invalid function var override.");
|
||||
}
|
||||
var_overrides[vkv.first] = override;
|
||||
}
|
||||
}
|
||||
|
||||
return {args, var_overrides};
|
||||
}
|
||||
|
||||
std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
||||
const std::string& code,
|
||||
const TypeSpec& function_type,
|
||||
@@ -74,7 +112,8 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
||||
bool allow_pairs,
|
||||
const std::string& method_name,
|
||||
const std::vector<std::pair<std::string, std::string>>& strings,
|
||||
const std::unordered_map<int, std::vector<TypeHint>>& hints) {
|
||||
const std::unordered_map<int, std::vector<TypeCast>>& casts,
|
||||
const std::string& var_map_json) {
|
||||
dts->type_prop_settings.locked = true;
|
||||
dts->type_prop_settings.reset();
|
||||
dts->type_prop_settings.allow_pair = allow_pairs;
|
||||
@@ -112,7 +151,7 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
||||
test->func.ir2.atomic_ops_succeeded = true;
|
||||
test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var());
|
||||
|
||||
EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, hints, {}));
|
||||
EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, casts, {}));
|
||||
|
||||
test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func));
|
||||
|
||||
@@ -133,8 +172,9 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
||||
test->func.ir2.top_form->collect_vars(vars, true);
|
||||
|
||||
if (do_expressions) {
|
||||
auto config = parse_var_json(var_map_json);
|
||||
bool success = convert_to_expressions(test->func.ir2.top_form, *test->func.ir2.form_pool,
|
||||
test->func, *dts);
|
||||
test->func, config.first, config.second, *dts);
|
||||
|
||||
EXPECT_TRUE(success);
|
||||
if (!success) {
|
||||
@@ -167,9 +207,11 @@ void FormRegressionTest::test(const std::string& code,
|
||||
bool allow_pairs,
|
||||
const std::string& method_name,
|
||||
const std::vector<std::pair<std::string, std::string>>& strings,
|
||||
const std::unordered_map<int, std::vector<TypeHint>>& hints) {
|
||||
const std::unordered_map<int, std::vector<TypeCast>>& casts,
|
||||
const std::string& var_map_json) {
|
||||
auto ts = dts->parse_type_spec(type);
|
||||
auto test = make_function(code, ts, do_expressions, allow_pairs, method_name, strings, hints);
|
||||
auto test = make_function(code, ts, do_expressions, allow_pairs, method_name, strings, casts,
|
||||
var_map_json);
|
||||
ASSERT_TRUE(test);
|
||||
auto expected_form =
|
||||
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
|
||||
@@ -193,9 +235,10 @@ void FormRegressionTest::test_final_function(
|
||||
const std::string& expected,
|
||||
bool allow_pairs,
|
||||
const std::vector<std::pair<std::string, std::string>>& strings,
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints) {
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts,
|
||||
const std::string& var_map_json) {
|
||||
auto ts = dts->parse_type_spec(type);
|
||||
auto test = make_function(code, ts, true, allow_pairs, "", strings, hints);
|
||||
auto test = make_function(code, ts, true, allow_pairs, "", strings, casts, var_map_json);
|
||||
ASSERT_TRUE(test);
|
||||
auto expected_form =
|
||||
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
|
||||
@@ -211,20 +254,22 @@ void FormRegressionTest::test_final_function(
|
||||
EXPECT_TRUE(expected_form == actual_form);
|
||||
}
|
||||
|
||||
std::unordered_map<int, std::vector<decompiler::TypeHint>> FormRegressionTest::parse_hint_json(
|
||||
std::unordered_map<int, std::vector<decompiler::TypeCast>> FormRegressionTest::parse_cast_json(
|
||||
const std::string& in) {
|
||||
std::unordered_map<int, std::vector<decompiler::TypeHint>> out;
|
||||
auto hints = nlohmann::json::parse(in);
|
||||
for (auto& hint : hints) {
|
||||
auto idx = hint.at(0).get<int>();
|
||||
for (size_t i = 1; i < hint.size(); i++) {
|
||||
auto& assignment = hint.at(i);
|
||||
TypeHint type_hint;
|
||||
type_hint.reg = Register(assignment.at(0).get<std::string>());
|
||||
type_hint.type_name = assignment.at(1).get<std::string>();
|
||||
out[idx].push_back(type_hint);
|
||||
std::unordered_map<int, std::vector<decompiler::TypeCast>> out;
|
||||
auto casts = nlohmann::json::parse(in);
|
||||
|
||||
for (auto& cast : casts) {
|
||||
auto idx_range = parse_json_optional_integer_range(cast.at(0));
|
||||
for (auto idx : idx_range) {
|
||||
TypeCast type_cast;
|
||||
type_cast.atomic_op_idx = idx;
|
||||
type_cast.reg = Register(cast.at(1));
|
||||
type_cast.type_name = cast.at(2).get<std::string>();
|
||||
out[idx].push_back(type_cast);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
|
||||
namespace decompiler {
|
||||
struct TypeHint;
|
||||
struct TypeCast;
|
||||
}
|
||||
|
||||
class FormRegressionTest : public ::testing::Test {
|
||||
@@ -34,7 +34,8 @@ class FormRegressionTest : public ::testing::Test {
|
||||
bool allow_pairs = false,
|
||||
const std::string& method_name = "",
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints = {});
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
|
||||
const std::string& var_map_json = "");
|
||||
|
||||
void test(const std::string& code,
|
||||
const std::string& type,
|
||||
@@ -43,7 +44,8 @@ class FormRegressionTest : public ::testing::Test {
|
||||
bool allow_pairs = false,
|
||||
const std::string& method_name = "",
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints = {});
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
|
||||
const std::string& var_map_json = "");
|
||||
|
||||
void test_no_expr(const std::string& code,
|
||||
const std::string& type,
|
||||
@@ -51,19 +53,20 @@ class FormRegressionTest : public ::testing::Test {
|
||||
bool allow_pairs = false,
|
||||
const std::string& method_name = "",
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints = {}) {
|
||||
test(code, type, expected, false, allow_pairs, method_name, strings, hints);
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
|
||||
const std::string& var_map_json = "") {
|
||||
test(code, type, expected, false, allow_pairs, method_name, strings, casts, var_map_json);
|
||||
}
|
||||
|
||||
void test_with_expr(
|
||||
const std::string& code,
|
||||
const std::string& type,
|
||||
const std::string& expected,
|
||||
bool allow_pairs = false,
|
||||
const std::string& method_name = "",
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints = {}) {
|
||||
test(code, type, expected, true, allow_pairs, method_name, strings, hints);
|
||||
void test_with_expr(const std::string& code,
|
||||
const std::string& type,
|
||||
const std::string& expected,
|
||||
bool allow_pairs = false,
|
||||
const std::string& method_name = "",
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
|
||||
const std::string& var_map_json = "") {
|
||||
test(code, type, expected, true, allow_pairs, method_name, strings, casts, var_map_json);
|
||||
}
|
||||
|
||||
void test_final_function(
|
||||
@@ -72,7 +75,8 @@ class FormRegressionTest : public ::testing::Test {
|
||||
const std::string& expected,
|
||||
bool allow_pairs = false,
|
||||
const std::vector<std::pair<std::string, std::string>>& strings = {},
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeHint>>& hints = {});
|
||||
const std::unordered_map<int, std::vector<decompiler::TypeCast>>& casts = {},
|
||||
const std::string& var_map_json = "");
|
||||
|
||||
std::unordered_map<int, std::vector<decompiler::TypeHint>> parse_hint_json(const std::string& in);
|
||||
std::unordered_map<int, std::vector<decompiler::TypeCast>> parse_cast_json(const std::string& in);
|
||||
};
|
||||
@@ -235,7 +235,10 @@
|
||||
(let ((obj-type (-> obj type))
|
||||
(end-type object)
|
||||
)
|
||||
(until (begin (set! obj-type (-> obj-type parent)) (= obj-type end-type))
|
||||
(until (begin
|
||||
(set! obj-type (-> obj-type parent))
|
||||
(= obj-type end-type)
|
||||
)
|
||||
(if (= obj-type parent-type)
|
||||
(return #t)
|
||||
)
|
||||
@@ -247,11 +250,10 @@
|
||||
;; definition for function type-type?
|
||||
(defun type-type? ((child-type type) (parent-type type))
|
||||
(let ((end-type object))
|
||||
(until
|
||||
(begin
|
||||
(set! child-type (-> child-type parent))
|
||||
(or (= child-type end-type) (zero? child-type))
|
||||
)
|
||||
(until (begin
|
||||
(set! child-type (-> child-type parent))
|
||||
(or (= child-type end-type) (zero? child-type))
|
||||
)
|
||||
(if (= child-type parent-type)
|
||||
(return #t)
|
||||
)
|
||||
@@ -834,7 +836,7 @@
|
||||
)
|
||||
(else
|
||||
(dotimes (s5-9 (-> obj length))
|
||||
(format #t "~T [~D] ~D~%" s5-9 (-> obj s5-9))
|
||||
(format #t "~T [~D] ~D~%" s5-9 (-> (the-as (array int32) obj) s5-9))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -844,12 +846,12 @@
|
||||
(cond
|
||||
((= (-> obj content-type) float)
|
||||
(dotimes (s5-10 (-> obj length))
|
||||
(format #t "~T [~D] ~f~%" s5-10 (-> obj s5-10))
|
||||
(format #t "~T [~D] ~f~%" s5-10 (-> (the-as (array float) obj) s5-10))
|
||||
)
|
||||
)
|
||||
(else
|
||||
(dotimes (s5-11 (-> obj length))
|
||||
(format #t "~T [~D] ~A~%" s5-11 (-> obj s5-11))
|
||||
(format #t "~T [~D] ~A~%" s5-11 (-> (the-as (array basic) obj) s5-11))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1111,9 +1113,10 @@
|
||||
)
|
||||
#f
|
||||
)
|
||||
((or (not in-goal-mem) (begin (let ((v1-10 #x8000))
|
||||
(.daddu v1-11 v1-10 s7-0)
|
||||
)
|
||||
((or (not in-goal-mem) (begin
|
||||
(let ((v1-10 #x8000))
|
||||
(.daddu v1-11 v1-10 s7-0)
|
||||
)
|
||||
(< (the-as uint obj) (the-as uint v1-11))
|
||||
)
|
||||
)
|
||||
@@ -1283,9 +1286,10 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
((begin (let ((v1-47 #x8000))
|
||||
(.daddu v1-48 v1-47 s7-0)
|
||||
)
|
||||
((begin
|
||||
(let ((v1-47 #x8000))
|
||||
(.daddu v1-48 v1-47 s7-0)
|
||||
)
|
||||
(< (the-as uint obj) (the-as uint v1-48))
|
||||
)
|
||||
(if
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
|
||||
;; definition of type kernel-context
|
||||
(deftype kernel-context (basic)
|
||||
((prevent-from-run uint32 :offset-assert 4)
|
||||
(require-for-run uint32 :offset-assert 8)
|
||||
(allow-to-run uint32 :offset-assert 12)
|
||||
(next-pid int32 :offset-assert 16)
|
||||
(fast-stack-top pointer :offset-assert 20)
|
||||
(current-process basic :offset-assert 24)
|
||||
(relocating-process basic :offset-assert 28)
|
||||
(relocating-min int32 :offset-assert 32)
|
||||
(relocating-max int32 :offset-assert 36)
|
||||
(relocating-offset int32 :offset-assert 40)
|
||||
(low-memory-message basic :offset-assert 44)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x30
|
||||
:flag-assert #x900000030
|
||||
)
|
||||
|
||||
;; definition for method 3 of type kernel-context
|
||||
(defmethod inspect kernel-context ((obj kernel-context))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tprevent-from-run: ~D~%" (-> obj prevent-from-run))
|
||||
(format #t "~Trequire-for-run: ~D~%" (-> obj require-for-run))
|
||||
(format #t "~Tallow-to-run: ~D~%" (-> obj allow-to-run))
|
||||
(format #t "~Tnext-pid: ~D~%" (-> obj next-pid))
|
||||
(format #t "~Tfast-stack-top: #x~X~%" (-> obj fast-stack-top))
|
||||
(format #t "~Tcurrent-process: ~A~%" (-> obj current-process))
|
||||
(format #t "~Trelocating-process: ~A~%" (-> obj relocating-process))
|
||||
(format #t "~Trelocating-min: #x~X~%" (-> obj relocating-min))
|
||||
(format #t "~Trelocating-max: #x~X~%" (-> obj relocating-max))
|
||||
(format #t "~Trelocating-offset: ~D~%" (-> obj relocating-offset))
|
||||
(format #t "~Tlow-memory-message: ~A~%" (-> obj low-memory-message))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type thread
|
||||
(deftype thread (basic)
|
||||
((name basic :offset-assert 4)
|
||||
(process process :offset-assert 8)
|
||||
(previous thread :offset-assert 12)
|
||||
(suspend-hook (function cpu-thread none) :offset-assert 16)
|
||||
(resume-hook (function cpu-thread none) :offset-assert 20)
|
||||
(pc pointer :offset-assert 24)
|
||||
(sp pointer :offset-assert 28)
|
||||
(stack-top pointer :offset-assert 32)
|
||||
(stack-size int32 :offset-assert 36)
|
||||
)
|
||||
:method-count-assert 12
|
||||
:size-assert #x28
|
||||
:flag-assert #xc00000028
|
||||
(:methods
|
||||
(stack-size-set! (_type_ int) none 9)
|
||||
(thread-suspend (_type_) none 10)
|
||||
(thread-resume (_type_) none 11)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type thread
|
||||
(defmethod inspect thread ((obj thread))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tprocess: ~A~%" (-> obj process))
|
||||
(format #t "~Tprevious: ~A~%" (-> obj previous))
|
||||
(format #t "~Tsuspend-hook: ~A~%" (-> obj suspend-hook))
|
||||
(format #t "~Tresume-hook: ~A~%" (-> obj resume-hook))
|
||||
(format #t "~Tpc: #x~X~%" (-> obj pc))
|
||||
(format #t "~Tsp: #x~X~%" (-> obj sp))
|
||||
(format #t "~Tstack-top: #x~X~%" (-> obj stack-top))
|
||||
(format #t "~Tstack-size: ~D~%" (-> obj stack-size))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type cpu-thread
|
||||
(deftype cpu-thread (thread)
|
||||
((rreg uint64 7 :offset-assert 40)
|
||||
(freg float 8 :offset-assert 96)
|
||||
(stack uint8 :dynamic :offset-assert 128)
|
||||
)
|
||||
:method-count-assert 12
|
||||
:size-assert #x80
|
||||
:flag-assert #xc00000080
|
||||
(:methods
|
||||
(new (symbol type process symbol int pointer) _type_ 0)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type cpu-thread
|
||||
(defmethod inspect cpu-thread ((obj cpu-thread))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tprocess: ~A~%" (-> obj process))
|
||||
(format #t "~Tprevious: ~A~%" (-> obj previous))
|
||||
(format #t "~Tsuspend-hook: ~A~%" (-> obj suspend-hook))
|
||||
(format #t "~Tresume-hook: ~A~%" (-> obj resume-hook))
|
||||
(format #t "~Tpc: #x~X~%" (-> obj pc))
|
||||
(format #t "~Tsp: #x~X~%" (-> obj sp))
|
||||
(format #t "~Tstack-top: #x~X~%" (-> obj stack-top))
|
||||
(format #t "~Tstack-size: ~D~%" (-> obj stack-size))
|
||||
(format #t "~Trreg[8] @ #x~X~%" (-> obj rreg))
|
||||
(format #t "~Tfreg[6] @ #x~X~%" (&-> obj freg 2))
|
||||
(format #t "~Tstack[0] @ #x~X~%" (-> obj stack))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type dead-pool
|
||||
(deftype dead-pool (process-tree)
|
||||
()
|
||||
:method-count-assert 16
|
||||
:size-assert #x20
|
||||
:flag-assert #x1000000020
|
||||
(:methods
|
||||
(new (symbol type int int basic) _type_ 0)
|
||||
(get-process (_type_ type int) process 14)
|
||||
(return-process (_type_ process) none 15)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type dead-pool
|
||||
(defmethod inspect dead-pool ((obj dead-pool))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tmask: ~D~%" (-> obj mask))
|
||||
(format #t "~Tparent: #x~X~%" (-> obj parent))
|
||||
(format #t "~Tbrother: #x~X~%" (-> obj brother))
|
||||
(format #t "~Tchild: #x~X~%" (-> obj child))
|
||||
(format #t "~Tppointer: #x~X~%" (-> obj ppointer))
|
||||
(format #t "~Tself: ~A~%" (-> obj self))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type dead-pool-heap-rec
|
||||
(deftype dead-pool-heap-rec (structure)
|
||||
((process process :offset-assert 0)
|
||||
(prev dead-pool-heap-rec :offset-assert 4)
|
||||
(next dead-pool-heap-rec :offset-assert 8)
|
||||
)
|
||||
:pack-me
|
||||
:method-count-assert 9
|
||||
:size-assert #xc
|
||||
:flag-assert #x90000000c
|
||||
)
|
||||
|
||||
;; definition for method 3 of type dead-pool-heap-rec
|
||||
(defmethod inspect dead-pool-heap-rec ((obj dead-pool-heap-rec))
|
||||
(format #t "[~8x] ~A~%" obj 'dead-pool-heap-rec)
|
||||
(format #t "~Tprocess: ~A~%" (-> obj process))
|
||||
(format #t "~Tprev: #<dead-pool-heap-rec @ #x~X>~%" (-> obj prev))
|
||||
(format #t "~Tnext: #<dead-pool-heap-rec @ #x~X>~%" (-> obj next))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type dead-pool-heap
|
||||
(deftype dead-pool-heap (dead-pool)
|
||||
((allocated-length int32 :offset-assert 32)
|
||||
(compact-time uint32 :offset-assert 36)
|
||||
(compact-count-targ uint32 :offset-assert 40)
|
||||
(compact-count uint32 :offset-assert 44)
|
||||
(fill-percent float :offset-assert 48)
|
||||
(first-gap dead-pool-heap-rec :offset-assert 52)
|
||||
(first-shrink dead-pool-heap-rec :offset-assert 56)
|
||||
(heap kheap :inline :offset-assert 64)
|
||||
(alive-list dead-pool-heap-rec :inline :offset-assert 80)
|
||||
(last dead-pool-heap-rec :offset 84)
|
||||
(dead-list dead-pool-heap-rec :inline :offset-assert 92)
|
||||
(process-list dead-pool-heap-rec :inline :dynamic :offset-assert 104)
|
||||
)
|
||||
:method-count-assert 27
|
||||
:size-assert #x68
|
||||
:flag-assert #x1b00000068
|
||||
(:methods
|
||||
(new (symbol type basic int int) _type_ 0)
|
||||
(compact (dead-pool-heap int) none 16)
|
||||
(shrink-heap (dead-pool-heap process) dead-pool-heap 17)
|
||||
(churn (dead-pool-heap int) none 18)
|
||||
(memory-used (dead-pool-heap) int 19)
|
||||
(memory-total (dead-pool-heap) int 20)
|
||||
(gap-size (dead-pool-heap dead-pool-heap-rec) int 21)
|
||||
(gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22)
|
||||
(find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23)
|
||||
(find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24)
|
||||
(memory-free (dead-pool-heap) int 25)
|
||||
(compact-time (dead-pool-heap) uint 26)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type dead-pool-heap
|
||||
;; INFO: this function exists in multiple non-identical object files
|
||||
(defmethod inspect dead-pool-heap ((obj dead-pool-heap))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tmask: ~D~%" (-> obj mask))
|
||||
(format #t "~Tparent: #x~X~%" (-> obj parent))
|
||||
(format #t "~Tbrother: #x~X~%" (-> obj brother))
|
||||
(format #t "~Tchild: #x~X~%" (-> obj child))
|
||||
(format #t "~Tppointer: #x~X~%" (-> obj ppointer))
|
||||
(format #t "~Tself: ~A~%" (-> obj self))
|
||||
(format #t "~Tallocated-length: ~D~%" (-> obj allocated-length))
|
||||
(format #t "~Tcompact-time: ~D~%" (-> obj compact-time))
|
||||
(format #t "~Tcompact-count-targ: ~D~%" (-> obj compact-count-targ))
|
||||
(format #t "~Tcompact-count: ~D~%" (-> obj compact-count))
|
||||
(format #t "~Tfill-percent: ~f~%" (-> obj fill-percent))
|
||||
(format #t "~Tfirst-gap: #<dead-pool-heap-rec @ #x~X>~%" (-> obj first-gap))
|
||||
(format
|
||||
#t
|
||||
"~Tfirst-shrink: #<dead-pool-heap-rec @ #x~X>~%"
|
||||
(-> obj first-shrink)
|
||||
)
|
||||
(format #t "~Theap: #<kheap @ #x~X>~%" (-> obj heap))
|
||||
(format #t "~Talive-list: #<dead-pool-heap-rec @ #x~X>~%" (-> obj alive-list))
|
||||
(format #t "~Tlast: #<dead-pool-heap-rec @ #x~X>~%" (-> obj alive-list prev))
|
||||
(format #t "~Tdead-list: #<dead-pool-heap-rec @ #x~X>~%" (-> obj dead-list))
|
||||
(format #t "~Tprocess-list[0] @ #x~X~%" (-> obj process-list))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type catch-frame
|
||||
(deftype catch-frame (stack-frame)
|
||||
((sp int32 :offset-assert 12)
|
||||
(ra int32 :offset-assert 16)
|
||||
(freg float 10 :offset-assert 20)
|
||||
(rreg uint128 7 :offset-assert 64)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #xb0
|
||||
:flag-assert #x9000000b0
|
||||
(:methods
|
||||
(new (symbol type symbol function (pointer uint64)) object 0)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type catch-frame
|
||||
(defmethod inspect catch-frame ((obj catch-frame))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tnext: ~A~%" (-> obj next))
|
||||
(format #t "~Tsp: #x~X~%" (-> obj sp))
|
||||
(format #t "~Tra: #x~X~%" (-> obj ra))
|
||||
(format #t "~Tfreg[6] @ #x~X~%" (-> obj freg))
|
||||
(format #t "~Trreg[8] @ #x~X~%" (&-> obj freg 7))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type protect-frame
|
||||
(deftype protect-frame (stack-frame)
|
||||
((exit (function object) :offset-assert 12)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x10
|
||||
:flag-assert #x900000010
|
||||
(:methods
|
||||
(new (symbol type (function object)) protect-frame 0)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type protect-frame
|
||||
(defmethod inspect protect-frame ((obj protect-frame))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tnext: ~A~%" (-> obj next))
|
||||
(format #t "~Texit: ~A~%" (-> obj exit))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type handle
|
||||
(deftype handle (uint64)
|
||||
((process (pointer process) :offset 0 :size 32)
|
||||
(pid int32 :offset 32 :size 32)
|
||||
(u64 uint64 :offset 0 :size 64)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x8
|
||||
:flag-assert #x900000008
|
||||
)
|
||||
|
||||
;; definition for method 3 of type handle
|
||||
(defmethod inspect handle ((obj handle))
|
||||
(format #t "[~8x] ~A~%" obj 'handle)
|
||||
(format #t "~Tprocess: #x~X~%" (shr (shl (the-as int obj) 32) 32))
|
||||
(format #t "~Tpid: ~D~%" (sar (the-as int obj) 32))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition for method 2 of type handle
|
||||
;; WARN: Unsupported inline assembly instruction kind - [5]
|
||||
;; WARN: Unsupported inline assembly instruction kind - [73]
|
||||
(defmethod print handle ((obj handle))
|
||||
(local-vars (a2-0 int) (s7-0 none))
|
||||
(if (nonzero? obj)
|
||||
(let ((t9-0 format)
|
||||
(a0-1 #t)
|
||||
(a1-0 "#<handle :process ~A :pid ~D>")
|
||||
(v1-0 obj)
|
||||
)
|
||||
(.subu a2-0 (the-as handle v1-0) s7-0)
|
||||
(let ((a2-1 (and (nonzero? a2-0) (begin
|
||||
(.sllv a2-2 (the-as handle v1-0) r0-0)
|
||||
(let ((a3-0 (-> a2-2 0)))
|
||||
(set!
|
||||
a2-1
|
||||
(if (= (sar v1-0 32) (-> a3-0 pid))
|
||||
a3-0
|
||||
)
|
||||
)
|
||||
)
|
||||
(let ((v1-2 a2-1))
|
||||
)
|
||||
a2-1
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(t9-0 a0-1 a1-0 a2-1 (sar (the-as int obj) 32))
|
||||
)
|
||||
)
|
||||
(format #t "#<handle :process 0 :pid 0>")
|
||||
)
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type state
|
||||
(deftype state (protect-frame)
|
||||
((code function :offset-assert 16)
|
||||
(trans (function object) :offset-assert 20)
|
||||
(post function :offset-assert 24)
|
||||
(enter (function object object object object object object object) :offset-assert 28)
|
||||
(event (function basic int basic event-message-block object) :offset-assert 32)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x24
|
||||
:flag-assert #x900000024
|
||||
(:methods
|
||||
(new (symbol type basic function (function object) (function object object object object object object object) (function object) (function basic int basic event-message-block object)) _type_ 0)
|
||||
)
|
||||
)
|
||||
|
||||
;; definition for method 3 of type state
|
||||
(defmethod inspect state ((obj state))
|
||||
(format #t "[~8x] ~A~%" obj (-> obj type))
|
||||
(format #t "~Tname: ~A~%" (-> obj name))
|
||||
(format #t "~Tnext: ~A~%" (-> obj next))
|
||||
(format #t "~Texit: ~A~%" (-> obj exit))
|
||||
(format #t "~Tcode: ~A~%" (-> obj code))
|
||||
(format #t "~Ttrans: ~A~%" (-> obj trans))
|
||||
(format #t "~Tpost: ~A~%" (-> obj post))
|
||||
(format #t "~Tenter: ~A~%" (-> obj enter))
|
||||
(format #t "~Tevent: ~A~%" (-> obj event))
|
||||
obj
|
||||
)
|
||||
|
||||
;; definition of type event-message-block
|
||||
(deftype event-message-block (structure)
|
||||
((to basic :offset-assert 0)
|
||||
(from basic :offset-assert 4)
|
||||
(num-params int32 :offset-assert 8)
|
||||
(message basic :offset-assert 12)
|
||||
(param uint64 7 :offset-assert 16)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x48
|
||||
:flag-assert #x900000048
|
||||
)
|
||||
|
||||
;; definition for method 3 of type event-message-block
|
||||
(defmethod inspect event-message-block ((obj event-message-block))
|
||||
(format #t "[~8x] ~A~%" obj 'event-message-block)
|
||||
(format #t "~Tto: ~A~%" (-> obj to))
|
||||
(format #t "~Tfrom: ~A~%" (-> obj from))
|
||||
(format #t "~Tnum-params: ~D~%" (-> obj num-params))
|
||||
(format #t "~Tmessage: ~A~%" (-> obj message))
|
||||
(format #t "~Tparam[7] @ #x~X~%" (-> obj param))
|
||||
obj
|
||||
)
|
||||
|
||||
;; failed to figure out what this is:
|
||||
(let ((v0-11 0))
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ TEST_F(FormRegressionTest, SimpleLoopMergeCheck) {
|
||||
" lw v0, -2(a0)\n"
|
||||
" jr ra\n"
|
||||
" daddu sp, sp, r0";
|
||||
std::string type = "(function pair int)";
|
||||
std::string type = "(function object int)";
|
||||
std::string expected =
|
||||
"(begin\n"
|
||||
" (set! v1-0 0)\n"
|
||||
|
||||
@@ -2273,15 +2273,17 @@ TEST_F(FormRegressionTest, ExprPrintName) {
|
||||
" (string= (the-as string arg0) (the-as string arg1))\n"
|
||||
" )\n"
|
||||
" ((and (= (-> arg0 type) string) (= (-> arg1 type) symbol))\n"
|
||||
" (string= (the-as string arg0) (-> (+ 65336 (the-as int arg1)) 0))\n"
|
||||
" (string= (the-as string arg0) (the-as string (-> (the-as (pointer uint32) (+ 65336 "
|
||||
"(the-as int arg1))))))\n"
|
||||
" )\n"
|
||||
" ((and (= (-> arg1 type) string) (= (-> arg0 type) symbol))\n"
|
||||
" (string= (the-as string arg1) (-> (+ 65336 (the-as int arg0)) 0))\n"
|
||||
" (string= (the-as string arg1) (the-as string (-> (the-as (pointer uint32) (+ 65336 "
|
||||
"(the-as int arg0))))))\n"
|
||||
" )\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "", {},
|
||||
parse_hint_json("[\t\t[24, [\"a1\", \"symbol\"]],\n"
|
||||
"\t\t[39, [\"a0\", \"symbol\"]]]"));
|
||||
parse_cast_json("[\t\t[24, \"a1\", \"symbol\"],\n"
|
||||
"\t\t[39, \"a0\", \"symbol\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprProfileBarMethod9) {
|
||||
|
||||
@@ -690,21 +690,21 @@ TEST_F(FormRegressionTest, ExprArrayMethod2) {
|
||||
{"L336", "~A"},
|
||||
{"L335", " ~A"},
|
||||
{"L334", ")"}},
|
||||
parse_hint_json("["
|
||||
"\t\t[23, [\"gp\", \"(array int32)\"]],\n"
|
||||
"\t\t[43, [\"gp\", \"(array uint32)\"]],\n"
|
||||
"\t\t[63, [\"gp\", \"(array int64)\"]],\n"
|
||||
"\t\t[83, [\"gp\", \"(array uint64)\"]],\n"
|
||||
"\t\t[102, [\"gp\", \"(array int8)\"]],\n"
|
||||
"\t\t[121, [\"gp\", \"(array uint8)\"]],\n"
|
||||
"\t\t[141, [\"gp\", \"(array int16)\"]],\n"
|
||||
"\t\t[161, [\"gp\", \"(array uint16)\"]],\n"
|
||||
"\t\t[186, [\"gp\", \"(array uint128)\"]],\n"
|
||||
"\t\t[204, [\"gp\", \"(array int32)\"]],\n"
|
||||
"\t\t[223, [\"gp\", \"(array float)\"]],\n"
|
||||
"\t\t[232, [\"gp\", \"(array float)\"]],\n"
|
||||
"\t\t[249, [\"gp\", \"(array basic)\"]],\n"
|
||||
"\t\t[258, [\"gp\", \"(array basic)\"]]]"));
|
||||
parse_cast_json("["
|
||||
"\t\t[23, \"gp\", \"(array int32)\"],\n"
|
||||
"\t\t[43, \"gp\", \"(array uint32)\"],\n"
|
||||
"\t\t[63, \"gp\", \"(array int64)\"],\n"
|
||||
"\t\t[83, \"gp\", \"(array uint64)\"],\n"
|
||||
"\t\t[102, \"gp\", \"(array int8)\"],\n"
|
||||
"\t\t[121, \"gp\", \"(array uint8)\"],\n"
|
||||
"\t\t[141, \"gp\", \"(array int16)\"],\n"
|
||||
"\t\t[161, \"gp\", \"(array uint16)\"],\n"
|
||||
"\t\t[186, \"gp\", \"(array uint128)\"],\n"
|
||||
"\t\t[204, \"gp\", \"(array int32)\"],\n"
|
||||
"\t\t[223, \"gp\", \"(array float)\"],\n"
|
||||
"\t\t[232, \"gp\", \"(array float)\"],\n"
|
||||
"\t\t[249, \"gp\", \"(array basic)\"],\n"
|
||||
"\t\t[258, \"gp\", \"(array basic)\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprArrayMethod3) {
|
||||
@@ -1238,7 +1238,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) {
|
||||
" (else\n"
|
||||
" (dotimes\n"
|
||||
" (s5-9 (-> arg0 length))\n"
|
||||
" (format #t \"~T [~D] ~D~%\" s5-9 (-> arg0 s5-9))\n"
|
||||
" (format #t \"~T [~D] ~D~%\" s5-9 (-> (the-as (array int32) arg0) s5-9))\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
@@ -1272,18 +1272,18 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) {
|
||||
{"L327", "~T [~D] #x~X~%"},
|
||||
{"L326", "~T [~D] ~f~%"},
|
||||
{"L325", "~T [~D] ~A~%"}},
|
||||
parse_hint_json("[\t\t[44, [\"gp\", \"(array int32)\"]],\n"
|
||||
"\t\t[62, [\"gp\", \"(array uint32)\"]],\n"
|
||||
"\t\t[80, [\"gp\", \"(array int64)\"]],\n"
|
||||
"\t\t[98, [\"gp\", \"(array uint64)\"]],\n"
|
||||
"\t\t[115, [\"gp\", \"(array int8)\"]],\n"
|
||||
"\t\t[132, [\"gp\", \"(array int8)\"]],\n"
|
||||
"\t\t[150, [\"gp\", \"(array int16)\"]],\n"
|
||||
"\t\t[168, [\"gp\", \"(array uint16)\"]],\n"
|
||||
"\t\t[191, [\"gp\", \"(array uint128)\"]],\n"
|
||||
"\t\t[204, [\"gp\", \"(array int32)\"]],\n"
|
||||
"\t\t[226, [\"gp\", \"(array float)\"]],\n"
|
||||
"\t\t[243, [\"gp\", \"(array basic)\"]]]"));
|
||||
parse_cast_json("[\t\t[44, \"gp\", \"(array int32)\"],\n"
|
||||
"\t\t[62, \"gp\", \"(array uint32)\"],\n"
|
||||
"\t\t[80, \"gp\", \"(array int64)\"],\n"
|
||||
"\t\t[98, \"gp\", \"(array uint64)\"],\n"
|
||||
"\t\t[115, \"gp\", \"(array int8)\"],\n"
|
||||
"\t\t[132, \"gp\", \"(array int8)\"],\n"
|
||||
"\t\t[150, \"gp\", \"(array int16)\"],\n"
|
||||
"\t\t[168, \"gp\", \"(array uint16)\"],\n"
|
||||
"\t\t[191, \"gp\", \"(array uint128)\"],\n"
|
||||
"\t\t[207, \"gp\", \"(array int32)\"],\n"
|
||||
"\t\t[226, \"gp\", \"(array float)\"],\n"
|
||||
"\t\t[243, \"gp\", \"(array basic)\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprValid) {
|
||||
@@ -2387,7 +2387,7 @@ TEST_F(FormRegressionTest, ExprStringToInt) {
|
||||
" (>= (the-as uint 70) (-> a0-3 0))\n"
|
||||
" )\n"
|
||||
" (set! v0-0 (+ (+ (-> a0-3 0) -55) (the-as uint (shl v0-0 4))))\n"
|
||||
" (let ((a1-14 v0-0)))\n"
|
||||
" (let ((a1-14 (the-as uint v0-0))))\n"
|
||||
" )\n"
|
||||
" (else\n"
|
||||
" (cond\n"
|
||||
@@ -2396,11 +2396,11 @@ TEST_F(FormRegressionTest, ExprStringToInt) {
|
||||
" (>= (the-as uint 102) (-> a0-3 0))\n"
|
||||
" )\n"
|
||||
" (set! v0-0 (+ (+ (-> a0-3 0) -87) (the-as uint (shl v0-0 4))))\n"
|
||||
" (let ((a1-20 v0-0)))\n"
|
||||
" (let ((a1-20 (the-as uint v0-0))))\n"
|
||||
" )\n"
|
||||
" (else\n"
|
||||
" (set! v0-0 (+ (+ (-> a0-3 0) -48) (the-as uint (shl v0-0 4))))\n"
|
||||
" (let ((a1-23 v0-0)))\n"
|
||||
" (let ((a1-23 (the-as uint v0-0))))\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
|
||||
@@ -323,7 +323,7 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
|
||||
" sw t0, 32(v0)\n"
|
||||
" jr ra\n"
|
||||
" daddu sp, sp, r0";
|
||||
std::string type = "(function symbol type process symbol int pointer thread)";
|
||||
std::string type = "(function symbol type process symbol int pointer cpu-thread)";
|
||||
std::string expected =
|
||||
"(let\n"
|
||||
" ((v0-0\n"
|
||||
@@ -339,19 +339,21 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) type) arg1)\n"
|
||||
" (set! (-> v0-0 name) arg3)\n"
|
||||
" (set! (-> v0-0 process) arg2)\n"
|
||||
" (set! (-> v0-0 sp) arg5)\n"
|
||||
" (set! (-> v0-0 stack-top) arg5)\n"
|
||||
" (set! (-> v0-0 previous) (-> arg2 top-thread))\n"
|
||||
" (set! (-> arg2 top-thread) v0-0)\n"
|
||||
" (set! (-> v0-0 suspend-hook) (method-of-object v0-0 thread-suspend))\n"
|
||||
" (set! (-> v0-0 resume-hook) (method-of-object v0-0 thread-resume))\n"
|
||||
" (set! (-> v0-0 stack-size) arg4)\n"
|
||||
" v0-0\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) name) arg3)\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) process) arg2)\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) sp) arg5)\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) stack-top) arg5)\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) previous) (-> arg2 top-thread))\n"
|
||||
" (set! (-> arg2 top-thread) (the-as cpu-thread v0-0))\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) suspend-hook) (method-of-object (the-as cpu-thread "
|
||||
"v0-0) thread-suspend))\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) resume-hook) (method-of-object (the-as cpu-thread "
|
||||
"v0-0) thread-resume))\n"
|
||||
" (set! (-> (the-as cpu-thread v0-0) stack-size) arg4)\n"
|
||||
" (the-as cpu-thread v0-0)\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "cpu-thread", {},
|
||||
parse_hint_json("[[13, [\"v0\", \"cpu-thread\"]]]"));
|
||||
parse_cast_json("[[[13, 28], \"v0\", \"cpu-thread\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprMethod5CpuThread) {
|
||||
@@ -635,36 +637,37 @@ TEST_F(FormRegressionTest, ExprMethod0Process) {
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (set! (-> (the-as process v0-0) name) arg2)\n"
|
||||
" (set! (-> v0-0 status) (quote dead))\n"
|
||||
" (set! (-> v0-0 pid) 0)\n"
|
||||
" (set! (-> v0-0 pool) #f)\n"
|
||||
" (set! (-> v0-0 allocated-length) arg3)\n"
|
||||
" (set! (-> v0-0 top-thread) #f)\n"
|
||||
" (set! (-> v0-0 main-thread) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) status) (quote dead))\n"
|
||||
" (set! (-> (the-as process v0-0) pid) 0)\n"
|
||||
" (set! (-> (the-as process v0-0) pool) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) allocated-length) arg3)\n"
|
||||
" (set! (-> (the-as process v0-0) top-thread) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) main-thread) #f)\n"
|
||||
" (let\n"
|
||||
" ((v1-5 (-> v0-0 stack)))\n"
|
||||
" (set! (-> v0-0 heap-cur) v1-5)\n"
|
||||
" (set! (-> v0-0 heap-base) v1-5)\n"
|
||||
" ((v1-5 (-> (the-as process v0-0) stack)))\n"
|
||||
" (set! (-> (the-as process v0-0) heap-cur) v1-5)\n"
|
||||
" (set! (-> (the-as process v0-0) heap-base) v1-5)\n"
|
||||
" )\n"
|
||||
" (set! (-> v0-0 heap-top) (&-> v0-0 stack (-> v0-0 allocated-length)))\n"
|
||||
" (set! (-> v0-0 stack-frame-top) (-> v0-0 heap-top))\n"
|
||||
" (set! (-> v0-0 stack-frame-top) #f)\n"
|
||||
" (set! (-> v0-0 state) #f)\n"
|
||||
" (set! (-> v0-0 next-state) #f)\n"
|
||||
" (set! (-> v0-0 entity) #f)\n"
|
||||
" (set! (-> v0-0 trans-hook) #f)\n"
|
||||
" (set! (-> v0-0 post-hook) #f)\n"
|
||||
" (set! (-> v0-0 event-hook) #f)\n"
|
||||
" (set! (-> v0-0 parent) #f)\n"
|
||||
" (set! (-> v0-0 brother) #f)\n"
|
||||
" (set! (-> v0-0 child) #f)\n"
|
||||
" (set! (-> v0-0 self) v0-0)\n"
|
||||
" (set! (-> v0-0 ppointer) (&-> v0-0 self))\n"
|
||||
" v0-0\n"
|
||||
" (set! (-> (the-as process v0-0) heap-top) (&-> (the-as process v0-0) stack (-> (the-as "
|
||||
"process v0-0) allocated-length)))\n"
|
||||
" (set! (-> (the-as process v0-0) stack-frame-top) (-> (the-as process v0-0) heap-top))\n"
|
||||
" (set! (-> (the-as process v0-0) stack-frame-top) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) state) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) next-state) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) entity) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) trans-hook) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) post-hook) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) event-hook) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) parent) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) brother) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) child) #f)\n"
|
||||
" (set! (-> (the-as process v0-0) self) (the-as process v0-0))\n"
|
||||
" (set! (-> (the-as process v0-0) ppointer) (&-> (the-as process v0-0) self))\n"
|
||||
" (the-as process v0-0)\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "process", {},
|
||||
parse_hint_json("[\t\t[12, [\"a0\", \"int\"]],\n"
|
||||
"\t\t[13, [\"v0\", \"process\"]]]"));
|
||||
parse_cast_json("[\t\t[12, \"a0\", \"int\"],\n"
|
||||
"\t\t[[13, 43], \"v0\", \"process\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprInspectProcessHeap) {
|
||||
@@ -720,18 +723,19 @@ TEST_F(FormRegressionTest, ExprInspectProcessHeap) {
|
||||
std::string expected =
|
||||
"(begin\n"
|
||||
" (let\n"
|
||||
" ((s5-0 (&+ (-> arg0 heap-base) 4)))\n"
|
||||
" ((obj (the-as basic (&+ (-> arg0 heap-base) 4))))\n"
|
||||
" (while\n"
|
||||
" (< (the-as int s5-0) (the-as int (-> arg0 heap-cur)))\n"
|
||||
" (inspect (the-as basic s5-0))\n"
|
||||
" (+! (the-as int s5-0) (logand -16 (+ (asize-of s5-0) 15)))\n"
|
||||
" (< (the-as int obj) (the-as int (-> arg0 heap-cur)))\n"
|
||||
" (inspect obj)\n"
|
||||
" (+! (the-as int obj) (logand -16 (+ (asize-of obj) 15)))\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" #f\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "", {},
|
||||
parse_hint_json("[\t\t[4, [\"s5\", \"basic\"]],\n"
|
||||
"\t\t[17, [\"s5\", \"int\"]]]"));
|
||||
parse_cast_json("[\t\t[[4,11], \"s5\", \"basic\"],\n"
|
||||
"\t\t[[17,20], \"s5\", \"int\"]]"),
|
||||
"{\"vars\":{\"s5-0\":[\"obj\", \"basic\"]}}");
|
||||
}
|
||||
|
||||
// note: skipped method 3 process
|
||||
@@ -1049,50 +1053,50 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
|
||||
std::string type = "(function dead-pool type int process)";
|
||||
// todo - why does one work but not the other??
|
||||
std::string expected =
|
||||
"(let\n"
|
||||
" ((s4-0 (-> arg0 child)))\n"
|
||||
"(let ((s4-0 (the-as object (-> arg0 child))))\n"
|
||||
" (when\n"
|
||||
" (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n"
|
||||
" (and\n"
|
||||
" (not (the-as (pointer process-tree) s4-0))\n"
|
||||
" *debug-segment*\n"
|
||||
" (!= arg0 *debug-dead-pool*)\n"
|
||||
" )\n"
|
||||
" (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n"
|
||||
" (if\n"
|
||||
" s4-0\n"
|
||||
" (let\n"
|
||||
" ((t9-1 format)\n"
|
||||
" (a0-2 0)\n"
|
||||
" (a1-2\n"
|
||||
" \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n"
|
||||
" )\n"
|
||||
" (a2-1 arg1)\n"
|
||||
" (v1-6 s4-0)\n"
|
||||
" )\n"
|
||||
" (t9-1\n"
|
||||
" a0-2\n"
|
||||
" a1-2\n"
|
||||
" a2-1\n"
|
||||
" (if v1-6 (-> (the-as (pointer process-tree) v1-6) 0 self))\n"
|
||||
" (if (the-as process s4-0)\n"
|
||||
" (let ((t9-1 format)\n"
|
||||
" (a0-2 0)\n"
|
||||
" (a1-2\n"
|
||||
" \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was "
|
||||
"empty.~%\"\n"
|
||||
" )\n"
|
||||
" (a2-1 arg1)\n"
|
||||
" (v1-6 (the-as process s4-0))\n"
|
||||
" )\n"
|
||||
" (t9-1 a0-2 a1-2 a2-1 (if (the-as process v1-6)\n"
|
||||
" (-> (the-as (pointer process-tree) v1-6) 0 self)\n"
|
||||
" )\n"
|
||||
" (-> arg0 name)\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (the-as\n"
|
||||
" process\n"
|
||||
" (cond\n"
|
||||
" (s4-0\n"
|
||||
" (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n"
|
||||
" (-> s4-0 0)\n"
|
||||
" )\n"
|
||||
" (else\n"
|
||||
" (format\n"
|
||||
" 0\n"
|
||||
" \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n"
|
||||
" arg1\n"
|
||||
" (if s4-0 (-> s4-0 0 self))\n"
|
||||
" (-> arg0 name)\n"
|
||||
" )\n"
|
||||
" #f\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (the-as process (cond\n"
|
||||
" (s4-0\n"
|
||||
" (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n"
|
||||
" (-> (the-as (pointer process-tree) s4-0) 0)\n"
|
||||
" )\n"
|
||||
" (else\n"
|
||||
" (format\n"
|
||||
" 0\n"
|
||||
" \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n"
|
||||
" arg1\n"
|
||||
" (if (the-as (pointer process-tree) s4-0)\n"
|
||||
" (-> (the-as (pointer process-tree) s4-0) 0 self)\n"
|
||||
" )\n"
|
||||
" (-> arg0 name)\n"
|
||||
" )\n"
|
||||
" #f\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )";
|
||||
|
||||
@@ -1101,8 +1105,8 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
|
||||
func, type, expected, false, "dead-pool",
|
||||
{{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%"},
|
||||
{"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}},
|
||||
parse_hint_json("[\t\t[24, [\"v1\", \"(pointer process-tree)\"]],\n"
|
||||
"\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]"));
|
||||
parse_cast_json("[\t\t[24, \"v1\", \"(pointer process-tree)\"],\n"
|
||||
"\t\t[[30,39], \"s4\", \"(pointer process-tree)\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprMethod15DeadPool) {
|
||||
@@ -1226,7 +1230,7 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
|
||||
std::string type = "(function symbol type basic int int dead-pool-heap)";
|
||||
std::string expected =
|
||||
"(let\n"
|
||||
" ((v0-0\n"
|
||||
" ((obj\n"
|
||||
" (object-new\n"
|
||||
" arg0\n"
|
||||
" arg1\n"
|
||||
@@ -1240,46 +1244,48 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) {
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (set! (-> v0-0 name) arg2)\n"
|
||||
" (set! (-> v0-0 mask) 256)\n"
|
||||
" (set! (-> v0-0 allocated-length) arg3)\n"
|
||||
" (set! (-> v0-0 parent) #f)\n"
|
||||
" (set! (-> v0-0 brother) #f)\n"
|
||||
" (set! (-> v0-0 child) #f)\n"
|
||||
" (set! (-> v0-0 self) v0-0)\n"
|
||||
" (set! (-> v0-0 ppointer) (&-> v0-0 self))\n"
|
||||
" (set! (-> obj name) arg2)\n"
|
||||
" (set! (-> obj mask) 256)\n"
|
||||
" (set! (-> obj allocated-length) arg3)\n"
|
||||
" (set! (-> obj parent) #f)\n"
|
||||
" (set! (-> obj brother) #f)\n"
|
||||
" (set! (-> obj child) #f)\n"
|
||||
" (set! (-> obj self) obj)\n"
|
||||
" (set! (-> obj ppointer) (&-> obj self))\n"
|
||||
" (let\n"
|
||||
" ((v1-4 arg3))\n"
|
||||
" (while\n"
|
||||
" (nonzero? v1-4)\n"
|
||||
" (+! v1-4 -1)\n"
|
||||
" (let\n"
|
||||
" ((a0-4 (-> v0-0 process-list v1-4)))\n"
|
||||
" ((a0-4 (-> obj process-list v1-4)))\n"
|
||||
" (set! (-> a0-4 process) *null-process*)\n"
|
||||
" (set! (-> a0-4 next) (-> v0-0 process-list (+ v1-4 1)))\n"
|
||||
" (set! (-> a0-4 next) (-> obj process-list (+ v1-4 1)))\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" )\n"
|
||||
" (set! (-> v0-0 dead-list next) (-> v0-0 process-list))\n"
|
||||
" (set! (-> v0-0 alive-list process) #f)\n"
|
||||
" (set! (-> v0-0 process-list (+ arg3 -1) next) #f)\n"
|
||||
" (set! (-> v0-0 alive-list prev) (-> v0-0 alive-list))\n"
|
||||
" (set! (-> v0-0 alive-list next) #f)\n"
|
||||
" (set! (-> v0-0 alive-list process) #f)\n"
|
||||
" (set! (-> v0-0 first-gap) (-> v0-0 alive-list))\n"
|
||||
" (set! (-> v0-0 first-shrink) #f)\n"
|
||||
" (set! (-> obj dead-list next) (-> obj process-list))\n"
|
||||
" (set! (-> obj alive-list process) #f)\n"
|
||||
" (set! (-> obj process-list (+ arg3 -1) next) #f)\n"
|
||||
" (set! (-> obj alive-list prev) (-> obj alive-list))\n"
|
||||
" (set! (-> obj alive-list next) #f)\n"
|
||||
" (set! (-> obj alive-list process) #f)\n"
|
||||
" (set! (-> obj first-gap) (-> obj alive-list))\n"
|
||||
" (set! (-> obj first-shrink) #f)\n"
|
||||
" (set!\n"
|
||||
" (-> v0-0 heap base)\n"
|
||||
" (logand -16 (the-as int (&+ (+ (the-as int v0-0) 115) (* 12 arg3))))\n"
|
||||
" (-> obj heap base)\n"
|
||||
" (logand -16 (+ (+ (the-as int obj) 115) (* 12 arg3)))\n"
|
||||
" )\n"
|
||||
" (set! (-> v0-0 heap current) (-> v0-0 heap base))\n"
|
||||
" (set! (-> v0-0 heap top) (&+ (-> v0-0 heap base) arg4))\n"
|
||||
" (set! (-> v0-0 heap top-base) (-> v0-0 heap top))\n"
|
||||
" v0-0\n"
|
||||
" (set! (-> obj heap current) (-> obj heap base))\n"
|
||||
" (set! (-> obj heap top) (&+ (-> obj heap base) arg4))\n"
|
||||
" (set! (-> obj heap top-base) (-> obj heap top))\n"
|
||||
" obj\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "dead-pool-heap", {},
|
||||
parse_hint_json("[\t\t[60, [\"v0\", \"int\"]],\n"
|
||||
"\t\t[61, [\"a0\", \"pointer\"], [\"v0\", \"dead-pool-heap\"]]]"));
|
||||
test_with_expr(
|
||||
func, type, expected, false, "dead-pool-heap", {},
|
||||
parse_cast_json("[\t\t[60, \"v0\", \"int\"],\n"
|
||||
"\t\t[61, \"a0\", \"pointer\"], [61, \"v0\", \"dead-pool-heap\"]]"),
|
||||
"{\"vars\":{\"v0-0\":[\"obj\", \"dead-pool-heap\"]}}");
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) {
|
||||
@@ -1399,9 +1405,9 @@ TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) {
|
||||
" )\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "", {},
|
||||
parse_hint_json("[\t\t[5, [\"v1\", \"pointer\"]],\n"
|
||||
"\t\t[13, [\"a0\", \"pointer\"]],\n"
|
||||
"\t\t[25, [\"v1\", \"pointer\"]]]"));
|
||||
parse_cast_json("[\t\t[5, \"v1\", \"pointer\"],\n"
|
||||
"\t\t[13, \"a0\", \"pointer\"],\n"
|
||||
"\t\t[25, \"v1\", \"pointer\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) {
|
||||
@@ -1595,7 +1601,7 @@ TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) {
|
||||
std::string type = "(function dead-pool-heap int)";
|
||||
std::string expected = "(+ (- -4 (the-as int arg0)) (-> arg0 heap top))";
|
||||
test_with_expr(func, type, expected, false, "", {},
|
||||
parse_hint_json("[[3, [\"v1\", \"int\"], [\"a0\", \"int\"]]]"));
|
||||
parse_cast_json("[[3, \"v1\", \"int\"], [3, \"a0\", \"int\"]]"));
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, ExprMethod19DeadPoolHeap) {
|
||||
@@ -1979,7 +1985,7 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPoolHeap) {
|
||||
std::string type = "(function dead-pool-heap type int process)";
|
||||
std::string expected =
|
||||
"(let\n"
|
||||
" ((s4-0 (-> arg0 dead-list next)) (s3-0 #f))\n"
|
||||
" ((s4-0 (-> arg0 dead-list next)) (s3-0 (the-as process #f)))\n"
|
||||
" (let\n"
|
||||
" ((s1-0 (find-gap-by-size arg0 (+ (-> process size) (the-as uint arg2)))))\n"
|
||||
" (cond\n"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
(count uint32)
|
||||
(pad uint8 12))
|
||||
(:methods
|
||||
(new ((allocation symbol) (type-to-make type) (init-count int)) _type_ 0)
|
||||
(new (symbol type int) _type_ 0)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
|
||||
namespace {
|
||||
// the object files to test
|
||||
const std::unordered_set<std::string> g_object_files_to_decompile = {
|
||||
"gcommon",
|
||||
"gstring-h",
|
||||
};
|
||||
const std::unordered_set<std::string> g_object_files_to_decompile = {"gcommon", "gstring-h",
|
||||
"gkernel-h", "gkernel"};
|
||||
|
||||
// the object files to check against a reference in test/decompiler/reference
|
||||
const std::vector<std::string> g_object_files_to_check_against_reference = {
|
||||
"gcommon", // NOTE: this file needs work, but adding it for now just to test the framework.
|
||||
"gstring-h"};
|
||||
"gstring-h", "gkernel-h",
|
||||
/*"gkernel"*/};
|
||||
|
||||
// the functions we expect the decompiler to skip
|
||||
const std::unordered_set<std::string> expected_skip_in_decompiler = {
|
||||
@@ -34,6 +33,7 @@ const std::unordered_set<std::string> expected_skip_in_decompiler = {
|
||||
"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
|
||||
@@ -47,25 +47,50 @@ const std::unordered_set<std::string> skip_in_compiling = {
|
||||
//////////////////////
|
||||
|
||||
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
|
||||
"abs", "ash", "min", "max", "lognor",
|
||||
"abs",
|
||||
"ash",
|
||||
"min",
|
||||
"max",
|
||||
"lognor",
|
||||
// weird PS2 specific debug registers:
|
||||
"breakpoint-range-set!",
|
||||
// these require 128-bit integers. We want these eventually, but disabling for now to focus
|
||||
// on more important issues.
|
||||
"(method 3 vec4s)", "(method 2 vec4s)", "qmem-copy<-!", "qmem-copy->!", "(method 2 array)",
|
||||
"(method 3 vec4s)",
|
||||
"(method 2 vec4s)",
|
||||
"qmem-copy<-!",
|
||||
"qmem-copy->!",
|
||||
"(method 2 array)",
|
||||
"(method 3 array)",
|
||||
// does weird stuff with the type system.
|
||||
"print", "printl", "inspect",
|
||||
"print",
|
||||
"printl",
|
||||
"inspect",
|
||||
// inline assembly
|
||||
"valid?"};
|
||||
"valid?",
|
||||
|
||||
//////////////////////
|
||||
// GKERNEL-H
|
||||
//////////////////////
|
||||
// bitfields, possibly inline assembly
|
||||
"(method 2 handle)",
|
||||
};
|
||||
|
||||
// The decompiler does not attempt to insert forward definitions, as this would be part of an
|
||||
// unimplemented full-program type analysis pass. For now, we manually specify all functions
|
||||
// that should have a forward definition here.
|
||||
const std::string g_forward_type_defs =
|
||||
// used out of order
|
||||
"(define-extern name= (function basic basic symbol))\n"
|
||||
// todo - check if recursive?
|
||||
"(define-extern fact (function int int))";
|
||||
// recursive
|
||||
"(define-extern fact (function int int))\n"
|
||||
// gkernel-h
|
||||
"(declare-type process basic)\n"
|
||||
"(declare-type stack-frame basic)\n"
|
||||
"(declare-type state basic)\n"
|
||||
"(declare-type cpu-thread basic)\n"
|
||||
"(declare-type dead-pool basic)\n"
|
||||
"(declare-type event-message-block structure)\n";
|
||||
|
||||
// default location for the data. It can be changed with a command line argument.
|
||||
std::string g_iso_data_path = "";
|
||||
@@ -252,7 +277,7 @@ 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()) {
|
||||
if (!func.ir2.env.has_type_analysis() || !func.ir2.types_succeeded) {
|
||||
lg::error("Function {} failed types", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "common/util/Trie.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/all_jak1_symbols.h"
|
||||
#include "common/util/json_util.h"
|
||||
#include "common/util/Range.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -38,4 +40,41 @@ TEST(CommonUtil, Trie) {
|
||||
EXPECT_FALSE(test.lookup("path1") == nullptr);
|
||||
EXPECT_TRUE(test.lookup("path-") == nullptr);
|
||||
EXPECT_FALSE(test.lookup("path1-k") == nullptr);
|
||||
}
|
||||
|
||||
TEST(CommonUtil, StripComments) {
|
||||
std::string test_input =
|
||||
R"(
|
||||
test "asdf /* y */ /////a\"bcd"
|
||||
///////// commented out!
|
||||
// /* also commented out
|
||||
|
||||
/* this is a block comment "with an unterminated string.
|
||||
*/ and its done
|
||||
)";
|
||||
|
||||
std::string test_expected =
|
||||
R"(
|
||||
test "asdf /* y */ /////a\"bcd"
|
||||
|
||||
|
||||
|
||||
and its done
|
||||
)";
|
||||
|
||||
EXPECT_EQ(strip_cpp_style_comments(test_input), test_expected);
|
||||
}
|
||||
|
||||
TEST(CommonUtil, RangeIterator) {
|
||||
std::vector<int> result = {}, expected_result = {4, 5, 6, 7};
|
||||
|
||||
for (auto x : Range<int>(4, 8)) {
|
||||
result.push_back(x);
|
||||
}
|
||||
|
||||
EXPECT_EQ(result, expected_result);
|
||||
EXPECT_TRUE(Range<int>().empty());
|
||||
EXPECT_FALSE(Range<int>(3, 4).empty());
|
||||
EXPECT_EQ(1, Range<int>(3, 4).size());
|
||||
EXPECT_EQ(4, Range<int>(4, 8).size());
|
||||
}
|
||||
Reference in New Issue
Block a user