[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:
water111
2021-03-13 16:10:39 -05:00
committed by GitHub
parent fe336b7b5f
commit 814480f9e5
61 changed files with 1652 additions and 497 deletions
+1 -1
View File
@@ -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"$@"
+1
View File
@@ -19,6 +19,7 @@ add_library(common
util/DgoReader.cpp
util/DgoWriter.cpp
util/FileUtil.cpp
util/json_util.cpp
util/Timer.cpp
)
+4
View File
@@ -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);
+5
View File
@@ -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:
+37 -7
View File
@@ -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 ");
}
+2 -10
View File
@@ -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()) {
+1 -1
View File
@@ -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);
+64
View File
@@ -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 = {};
};
+100
View File
@@ -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");
}
}
+9
View File
@@ -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
View File
@@ -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;
+3 -2
View File
@@ -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
+5
View File
@@ -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;
+2 -1
View File
@@ -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:
+66 -21
View File
@@ -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;
+2 -2
View File
@@ -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());
}
+2
View File
@@ -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;
};
/*!
+33 -14
View File
@@ -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");
}
+7 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+6 -1
View File
@@ -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;
}
+3 -1
View File
@@ -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;
};
+13 -9
View File
@@ -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&,
+8 -2
View File
@@ -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);
+6 -1
View File
@@ -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;
};
+2 -2
View File
@@ -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>();
+14 -2
View File
@@ -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;
+4 -4
View File
@@ -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
+33 -66
View File
@@ -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;
}
+12 -4
View File
@@ -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
+19 -1
View File
@@ -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;
}
+39
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+44 -45
View File
@@ -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"}
+1 -1
View File
@@ -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));
+2 -2
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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 "$@"
+1 -1
View File
@@ -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)
)
)
+5 -5
View File
@@ -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)
)
)
+1 -1
View File
@@ -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
+45
View File
@@ -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.
)
+40 -39
View File
@@ -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
+63 -18
View File
@@ -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;
}
+20 -16
View File
@@ -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);
};
+19 -15
View File
@@ -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
+384
View File
@@ -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"
+6 -4
View File
@@ -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"
+122 -116
View File
@@ -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)
)
)
+37 -12
View File
@@ -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++;
}
+39
View File
@@ -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());
}