[decompiler] Add tests and fixes for vector-h and math (#333)

* before messing with ssa stuff

* fix ash

* bounding box
This commit is contained in:
water111 2021-03-24 19:16:31 -04:00 committed by GitHub
parent 0d8742241b
commit 99683c0dac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2180 additions and 160 deletions

View File

@ -574,18 +574,19 @@ void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) {
}
if (name == "defun" || name == "defmethod" || name == "defun-debug" || name == "let" ||
name == "let*") {
name == "let*" || name == "rlet") {
auto* first_list = getNextListOrEmptyListOnLine(node);
if (first_list) {
if (first_list->tok->kind == FormToken::TokenKind::EMPTY_PAIR) {
insertNewlineAfter(pool, first_list, 0);
breakList(pool, node->paren, first_list);
} else {
insertNewlineAfter(pool, first_list->paren, 0);
breakList(pool, node->paren, first_list);
}
}
if ((name == "let" || name == "let*") && first_list) {
if ((name == "let" || name == "let*" || name == "rlet") && first_list) {
if (first_list->tok->kind == FormToken::TokenKind::OPEN_PAREN) {
// we only want to break the variable list if it has multiple.
bool single_var = false;

View File

@ -184,6 +184,16 @@ std::string Instruction::cop2_dest_to_char() const {
return dest;
}
int Instruction::cop2_dest_mask_intel() const {
int mask = 0;
for (int i = 0; i < 4; i++) { // x,y,z,w order
if (cop2_dest & (1 << (3 - i))) { // set for ps2
mask |= (1 << i);
}
}
return mask;
}
/*!
* Convert just the name of the opcode to a string, omitting src/dst, but including
* suffixes (interlock, broadcasts and destination)

View File

@ -102,12 +102,14 @@ class Instruction {
bool operator!=(const Instruction& other) const { return !((*this) == other); }
// extra fields for some COP2 instructions.
// this is stored like in the PS2 instruction, which is different from intel
uint8_t cop2_dest = 0xff; // 0xff indicates "don't print dest"
uint8_t cop2_bc = 0xff; // 0xff indicates "don't print bc"
uint8_t il = 0xff; // 0xff indicates "don't print il"
char cop2_bc_to_char() const;
std::string cop2_dest_to_char() const;
int cop2_dest_mask_intel() const;
};
} // namespace decompiler
#endif // NEXT_INSTRUCTION_H

View File

@ -169,7 +169,6 @@ class Function {
std::string debug_form_string;
bool print_debug_forms = false;
bool expressions_succeeded = false;
bool types_succeeded = false;
} ir2;
private:

View File

@ -125,6 +125,11 @@ TP_Type SimpleAtom::get_type(const TypeState& input,
} else if ((label.offset & 7) == PAIR_OFFSET) {
return TP_Type::make_from_ts(TypeSpec("pair"));
}
auto hint_kv = env.label_types().find(label.name);
if (hint_kv != env.label_types().end()) {
return TP_Type::make_from_ts(dts.parse_type_spec(hint_kv->second.type_name));
}
// throw std::runtime_error("IR_StaticAddress couldn't figure out the type: " + label.name);
lg::error("IR_StaticAddress doesn't know the type of {}", label.name);
return TP_Type::make_from_ts("object");

View File

@ -102,64 +102,67 @@ goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, Acce
lookup_name = remapped->second;
}
// 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;
}
if (types_succeeded) {
// 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) {
// 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);
// 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) {
// 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);
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.
// 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);
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);
// }
}
} 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);

View File

@ -24,6 +24,7 @@ struct FunctionAtomicOps;
*/
class Env {
public:
bool types_succeeded = false;
bool has_local_vars() const { return m_has_local_vars; }
bool has_type_analysis() const { return m_has_types; }
bool has_reg_use() const { return m_has_reg_use; }

View File

@ -1205,7 +1205,7 @@ class LetElement : public FormElement {
RegisterAccess dest;
Form* src = nullptr;
};
std::vector<Entry> entries() { return m_entries; }
std::vector<Entry>& entries() { return m_entries; }
void add_entry(const Entry& e);
bool is_star() const { return m_star; }

View File

@ -141,8 +141,8 @@ void pop_helper(const std::vector<RegisterAccess>& vars,
// fmt::print("Unsafe to pop {}: used {} times, def {} times, expected use {}\n",
// var.to_string(env), use_def.use_count(), use_def.def_count(),
// times);
// if (var.to_string(env) == "v1-3") {
// for (auto& use : use_def.defs) {
// if (var.to_string(env) == "a3-0") {
// for (auto& use : use_def.uses) {
// if (!use.disabled) {
// fmt::print(" at instruction {}\n", use.op_id);
// }
@ -1155,6 +1155,7 @@ Form* make_optional_cast(const std::optional<TypeSpec>& cast_type, Form* in, For
void StorePlainDeref::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
mark_popped();
if (m_expr.is_var()) {
// this matches the order in Compiler::compile_set
auto vars = std::vector<RegisterAccess>({m_expr.var(), m_base_var});
auto popped = pop_to_forms(vars, env, pool, stack, true);
m_dst->set_base(make_optional_cast(m_dst_cast_type, popped.at(1), pool));

View File

@ -199,7 +199,8 @@ std::vector<goos::Object> OpenGOALAsm::get_args(const std::vector<DecompilerLabe
// Handle destination masks
if (func.allows_modifier(MOD::DEST_MASK) && instr.cop2_dest != 0xff && instr.cop2_dest != 15) {
named_args.push_back(pretty_print::to_symbol(fmt::format(":mask #b{:b}", instr.cop2_dest)));
named_args.push_back(
pretty_print::to_symbol(fmt::format(":mask #b{:b}", instr.cop2_dest_mask_intel())));
}
// Some functions are configured, or its easiest to swap the source args

View File

@ -301,7 +301,7 @@ void ObjectFileDB::ir2_type_analysis_pass() {
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;
func.ir2.env.types_succeeded = true;
} else {
func.warnings.type_prop_warning("Type analysis failed");
}
@ -422,7 +422,8 @@ void ObjectFileDB::ir2_build_expressions() {
(void)segment_id;
(void)data;
total++;
if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars()) {
if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars() &&
func.ir2.env.types_succeeded) {
attempted++;
auto name = func.guessed_name.to_string();
auto arg_config = get_config().function_arg_names.find(name);

View File

@ -1123,6 +1123,17 @@ Form* try_sc_as_ash(FormPool& pool, Function& f, const ShortCircuit* vtx) {
// fix up reg info
f.ir2.env.disable_use(delay->src().get_arg(0).var());
// fix up the other ones:
f.ir2.env.disable_use(branch->op()->condition().src(0).var()); // bgezl X, L
auto dsubu_var = dynamic_cast<SimpleExpressionElement*>(dsubu_set->src()->try_as_single_element())
->expr()
.get_arg(0)
.var();
f.ir2.env.disable_use(dsubu_var);
// and the def too
f.ir2.env.disable_def(dsrav_set->dst());
return b0_c_ptr;
}

View File

@ -199,6 +199,66 @@ FormElement* fix_up_abs(LetElement* in, const Env& env, FormPool& pool) {
src);
}
FormElement* fix_up_abs_2(LetElement* in, const Env& env, FormPool& pool) {
/*
* (let ((result in))
* (set! result (abs result))
* ...
* )
*
* -> should become.
* (let ((result (abs in)))
* )
*/
if (in->entries().size() != 1) {
return nullptr;
}
if (in->body()->elts().empty()) {
return nullptr;
}
// look for setting a temp.
auto temp = in->entries().at(0).dest;
auto temp_name = env.get_variable_name(temp);
Form* src = in->entries().at(0).src;
auto first_as_set = dynamic_cast<SetVarElement*>(in->body()->elts().front());
if (!first_as_set) {
return nullptr;
}
auto dest_var_name = env.get_variable_name(first_as_set->dst());
if (dest_var_name != temp_name) {
return nullptr;
}
auto matcher =
Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ABS), {Matcher::any_reg(0)});
auto mr = match(matcher, first_as_set->src());
if (!mr.matched) {
return nullptr;
}
assert(mr.maps.regs.at(0));
auto abs_var_name = env.get_variable_name(*mr.maps.regs.at(0));
if (abs_var_name != temp_name) {
return nullptr;
}
// success!
// modify the let entry:
in->entries().at(0).src = pool.alloc_single_element_form<GenericElement>(
nullptr, GenericOperator::make_fixed(FixedOperatorKind::ABS), src);
// remove the (set! x (abs x))
in->body()->elts().erase(in->body()->elts().begin());
return in;
}
/*!
* Attempt to rewrite a let as another form. If it cannot be rewritten, this will return nullptr.
*/
@ -213,6 +273,11 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool) {
return as_abs;
}
auto as_abs_2 = fix_up_abs_2(in, env, pool);
if (as_abs_2) {
return as_abs_2;
}
// nothing matched.
return nullptr;
}

View File

@ -736,50 +736,9 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
;; math
(deftype random-generator (basic)
((seed uint32 :offset-assert 4)
)
:method-count-assert 9
:size-assert #x8
:flag-assert #x900000008
)
(define-extern truncate (function float float))
(define-extern integral? (function float symbol))
(define-extern fractional-part (function float float))
(define-extern log2 (function int int))
(define-extern seek (function float float float float))
(define-extern lerp (function float float float float))
(define-extern lerp-scale (function float float float float float float))
(define-extern lerp-clamp (function float float float float))
(define-extern seekl (function int int int int))
(define-extern rand-vu-init (function float float))
(define-extern rand-vu (function float))
(define-extern rand-vu-nostep (function float))
(define-extern rand-vu-float-range (function float float float))
(define-extern rand-vu-percent? (function float symbol))
(define-extern rand-vu-int-range (function int int int))
(define-extern rand-vu-int-count (function int int))
; ;;(define-extern xyzwh object) ;; unknown type
;
;
;
; (define-extern lerp function)
; (define-extern lerp-scale function)
;
; ;;(define-extern random-generator object) ;; unknown type
; (define-extern integral? function)
; (define-extern rand-uint31-gen function)
;
; (define-extern fractional-part function)
; (define-extern seek function)
; ;;(define-extern xyzw object) ;; unknown type
;
(define-extern *random-generator* random-generator) ;; unknown type
;
(deftype rgba (uint32)
((r uint8 :offset 0)
@ -802,6 +761,29 @@
:flag-assert #x900000010
)
(define-extern log2 (function int int))
(define-extern seek (function float float float float))
(define-extern lerp (function float float float float))
(define-extern lerp-scale (function float float float float float float))
(define-extern lerp-clamp (function float float float float))
(define-extern seekl (function int int int int))
(define-extern rand-vu-init (function float float))
(define-extern rand-vu (function float))
(define-extern rand-vu-nostep (function float))
(define-extern rand-vu-float-range (function float float float))
(define-extern rand-vu-percent? (function float symbol))
(define-extern rand-vu-int-range (function int int int))
(define-extern rand-vu-int-count (function int int))
(deftype random-generator (basic)
((seed uint32 :offset-assert 4)
)
:method-count-assert 9
:size-assert #x8
:flag-assert #x900000008
)
(define-extern *random-generator* random-generator)
(define-extern rand-uint31-gen (function random-generator uint))
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
@ -1180,13 +1162,13 @@
:size-assert #x20
:flag-assert #x1000000020
(:methods
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
(dummy-12 () none 12)
(dummy-13 () none 13)
(dummy-14 () none 14)
(dummy-15 () none 15)
(add-spheres! (_type_ (pointer sphere) int) int 9)
(add-point! (_type_ vector3s) int 10)
(set-from-point-offset! (_type_ vector3s vector3s) int 11)
(set-from-point-offset-pad! (_type_ vector3s vector3s float) int 12)
(set-from-sphere! (_type_ sphere) int 13)
(set-from-spheres! (_type_ (pointer sphere) int) int 14)
(add-box! (_type_ bounding-box) int 15)
)
)
@ -1247,6 +1229,7 @@
:flag-assert #x900000020
)
(define-extern matrix-copy! (function matrix matrix matrix))
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -3572,6 +3555,7 @@
:size-assert #x50
:flag-assert #x900000050
)
;; level-h
(deftype level (basic)
((name basic :offset-assert 4)
@ -4225,6 +4209,7 @@
)
(define-extern *dproc* process)
(define-extern *display-strip-lines* int)
;;;;;;;;;;;;;;
;; mspace-h
@ -13638,6 +13623,12 @@
:flag-assert #x900000014
)
;;;; level
(define-extern lookup-level-info (function symbol level-load-info))
(define-extern remap-level-name (function level-load-info object))
; ;; text
; (deftype game-text-info (basic)
; ()
@ -32501,7 +32492,7 @@
;;(define-extern bounding-box4w object) ;; unknown type
;;(define-extern bounding-box-both object) ;; unknown type
;;(define-extern matrix4h object) ;; unknown type
; (define-extern matrix-copy! function)
;;(define-extern matrix3 object) ;; unknown type
;;(define-extern matrix object) ;; unknown type
;;(define-extern *unity-quaternion* object) ;; unknown type
@ -32889,7 +32880,7 @@
;;(define-extern *display-cam-coll-marks* object) ;; unknown type
;;(define-extern *display-ambient-hint-marks* object) ;; unknown type
;;(define-extern *display-cam-los-debug* object) ;; unknown type
;;(define-extern *display-strip-lines* object) ;; unknown type
;;(define-extern *display-process-anim* object) ;; unknown type
;;(define-extern *display-actor-marks* object) ;; unknown type
(define-extern *menu-hook* (function none))
@ -34371,7 +34362,6 @@
;;(define-extern save object) ;; unknown type
;;(define-extern restore object) ;; unknown type
;;(define-extern mc-unformat object) ;; unknown type
(define-extern lookup-level-info function)
;;(define-extern done object) ;; unknown type
(define-extern get-aspect-ratio function)
(define-extern progress-allowed? function)
@ -34802,7 +34792,6 @@
(define-extern load-vis-info function)
(define-extern level-update-after-load function)
(define-extern update-sound-banks function)
(define-extern remap-level-name function)
(define-extern add-bsp-drawable function)
;;(define-extern *print-login* object) ;; unknown type
;;(define-extern link-resume object) ;; unknown type

View File

@ -505,5 +505,5 @@
],
"pair_functions_by_name":["ref", "last", "member", "nmember", "assoc", "assoce", "append!", "delete!", "delete-car!",
"insert-cons!", "sort", "unload-package", "(method 4 pair)", "nassoc", "nassoce"]
"insert-cons!", "sort", "unload-package", "(method 4 pair)", "nassoc", "nassoce", "lookup-level-info", "(method 21 level-group)"]
}

View File

@ -28,6 +28,16 @@
["L35", "float", true]
],
"vector-h": [
["L32", "vector", true],
["L31", "vector", true],
["L30", "vector", true],
["L29", "vector", true],
["L28", "vector", true],
["L27", "vector", true],
["L26", "vector", true]
],
"trigonometry": [
["L143", "float", true],
["L144", "float", true],

View File

@ -109,10 +109,22 @@
[4, "s5", "string"]
],
// GSTATE
"enter-state":[
[68, "s0", "protect-frame"]
],
// MATH
"log2":[
[3, "v1", "int"]
],
// LEVEL
"lookup-level-info":[
[3, "a1", "symbol"],
[4, "a1", "level-load-info"],
[8, "a1", "level-load-info"],
[12, "a1", "level-load-info"]
]
}

View File

@ -617,9 +617,18 @@
},
"(method 11 touching-prims-entry-pool)":{
"vars":{"a1-0":"current", "v1-0":"prev", "a2-0":"next"}
},
// LEVEL
"lookup-level-info":{
"args":["name"],
"vars":{"a1-1":["info", "level-load-info"], "v1-0":"rest", "a1-0":"current-sym"}
},
"(method 21 level-group)":{
"args":["obj", "name", "cmd-idx"],
"vars":{"v1-1":"cmd-lst"}
}
}

View File

@ -8,6 +8,9 @@
;; Types related to bounding boxes.
;; floating point bounding box.
;; min is the corner with lowest x,y,z values.
;; max is the corner with highest x,y,z values.
;; the w value should be 1 in both min and max.
(deftype bounding-box (structure)
((min vector :inline :offset-assert 0)
(max vector :inline :offset-assert 16)
@ -16,14 +19,14 @@
:size-assert #x20
:flag-assert #x1000000020
(:methods
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
(dummy-12 () none 12)
(dummy-13 () none 13)
(dummy-14 () none 14)
(dummy-15 () none 15)
)
(add-spheres! (_type_ (pointer sphere) int) int 9)
(add-point! (_type_ vector3s) int 10)
(set-from-point-offset! (_type_ vector3s vector3s) int 11)
(set-from-point-offset-pad! (_type_ vector3s vector3s float) int 12)
(set-from-sphere! (_type_ sphere) int 13)
(set-from-spheres! (_type_ (pointer sphere) int) int 14)
(add-box! (_type_ bounding-box) int 15)
)
)
;; integer (word) bounding box.
@ -44,4 +47,4 @@
:method-count-assert 9
:size-assert #x40
:flag-assert #x900000040
)
)

View File

@ -28,3 +28,183 @@
(>= (-> box max data 2) (-> pt data 2))
)
)
;; definition for method 11 of type bounding-box
(defmethod set-from-point-offset! bounding-box
((obj bounding-box) (arg0 vector3s) (arg1 vector3s))
"Set box to smallest containing the points arg0, (arg0 + arg1)"
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
(vf5 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf3 arg1)
(.lvf vf4 arg0)
(.add.vf vf5 vf4 vf3)
(.min.vf vf1 vf4 vf5)
(.max.vf vf2 vf4 vf5)
(.mov.vf vf1 vf0 :mask #b1000)
(.mov.vf vf2 vf0 :mask #b1000)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 10 of type bounding-box
(defmethod add-point! bounding-box ((obj bounding-box) (arg0 vector3s))
"Expand the box if needed to contain the given point"
(rlet ((vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf3 arg0)
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf3)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 15 of type bounding-box
(defmethod add-box! bounding-box ((obj bounding-box) (arg0 bounding-box))
"Expand the box if needed to contain the given box"
(rlet ((vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf3 arg0)
(.lvf vf4 arg0 :offset 16)
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf4)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 12 of type bounding-box
(defmethod set-from-point-offset-pad! bounding-box
((obj bounding-box) (arg0 vector3s) (arg1 vector3s) (arg2 float))
"Set the box size to contain pt, pt + offset, with some padding"
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
(vf5 :class vf)
(vf6 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf4 arg1)
(.lvf vf5 arg0)
(.mov vf1 arg2)
(.add.vf vf6 vf5 vf4)
(.min.vf vf2 vf5 vf6)
(.max.vf vf3 vf5 vf6)
(.add.x.vf vf3 vf3 vf1 :mask #b111)
(.sub.x.vf vf2 vf2 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
0
)
)
;; definition for method 13 of type bounding-box
(defmethod set-from-sphere! bounding-box ((obj bounding-box) (arg0 sphere))
"Set the box size to contain the given sphere"
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf1 arg0)
(.sub.w.vf vf2 vf1 vf1 :mask #b111)
(.add.w.vf vf3 vf1 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
0
)
)
(defmethod add-spheres! bounding-box ((obj bounding-box) (spheres (pointer sphere)) (count int))
"Add count spheres."
;; the PS2 implementation is very optimized
;; It is unrolled and 'software pipelined' to do 4 at a time.
;; This is slightly less optimized.
(rlet ((current-min :class vf)
(current-max :class vf)
(sph-min :class vf)
(sph-max :class vf)
(sph :class vf))
(when (nonzero? count)
;; load these outside the loop
(.lvf current-min (-> obj min))
(.lvf current-max (-> obj max))
(dotimes (i count)
(.lvf sph (-> spheres i))
(.sub.w.vf sph-min sph sph :mask #b111)
(.add.w.vf sph-max sph sph :mask #b111)
(.min.vf current-min current-min sph-min :mask #b111)
(.max.vf current-max current-max sph-max :mask #b111)
)
(.svf (-> obj min) current-min)
(.svf (-> obj max) current-max)
)
)
0
)
(defmethod set-from-spheres! bounding-box ((obj bounding-box) (spheres (pointer sphere)) (count int))
"Reset box to hold the given spheres. Note: this implementation could be optimized."
;; This is also unrolled, but does 7 at a time.
(rlet ((vf0 :class vf)
(current-min :class vf)
(current-max :class vf)
(sph-min :class vf)
(sph-max :class vf)
(sph :class vf))
;; init constant
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
;; init min/max. in the case we don't have any spheres, we should return (0,0,0,1) for min/max.
(set! current-min vf0)
(set! current-max vf0)
(dotimes (i count)
(.lvf sph (-> spheres i))
(.sub.w.vf sph-min sph sph :mask #b111)
(.add.w.vf sph-max sph sph :mask #b111)
(cond
((zero? count)
(set! current-min sph-min)
(set! current-max sph-max)
)
(else
(.min.vf current-min current-min sph-min :mask #b111)
(.max.vf current-max current-max sph-max :mask #b111)
)
)
)
(.svf (-> obj min) current-min)
(.svf (-> obj max) current-max)
)
0
)

View File

@ -296,6 +296,6 @@
)
(set! result (shr (logand #xffffffff (shl result 1)) 1))
(set! (-> gen seed) result)
result
(the uint result)
)
)

View File

@ -444,7 +444,6 @@
)
)
;; vector-h
(deftype vertical-planes (structure)
((data uint128 4 :offset-assert 0) ;; probably wrong
)

View File

@ -209,9 +209,8 @@ Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, E
va_check(form, args, {{}, {}}, {});
auto& destination = args.unnamed.at(0);
// todo, I don't know if this is the correct order or not. Right now the value is computed
// and to_reg'd first, then the destination is computed, if the destination requires math to
// compute.
// this is the order I'm using in the decompiler and it seems to be right.
// see StorePlainDeref::push_to_stack for example
auto source = compile_error_guard(args.unnamed.at(1), env);
auto source_reg = source->to_reg(env);
auto dest = compile_error_guard(destination, env);

View File

@ -152,6 +152,7 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
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, casts, {}));
test->func.ir2.env.types_succeeded = true;
test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func));

View File

@ -80,4 +80,11 @@
(define-extern throw (function symbol object int))
(defmacro suspend()
'(none)
)
)
;; math
(define-extern fabs (function float float))
(define-extern abs (function int int))
(define-extern rand-vu-init (function float none))
(define-extern rand-vu (function float))

View File

@ -0,0 +1,72 @@
;;-*-Lisp-*-
(in-package goal)
;; definition of type bounding-box
(deftype bounding-box (structure)
((min vector :inline :offset-assert 0)
(max vector :inline :offset-assert 16)
)
:method-count-assert 16
:size-assert #x20
:flag-assert #x1000000020
(:methods
(add-spheres! (_type_ (pointer sphere) int) int 9)
(add-point! (_type_ vector3s) int 10)
(set-from-point-offset! (_type_ vector3s vector3s) int 11)
(set-from-point-offset-pad! (_type_ vector3s vector3s float) int 12)
(set-from-sphere! (_type_ sphere) int 13)
(set-from-spheres! (_type_ (pointer sphere) int) int 14)
(add-box! (_type_ bounding-box) int 15)
)
)
;; definition for method 3 of type bounding-box
(defmethod inspect bounding-box ((obj bounding-box))
(format #t "[~8x] ~A~%" obj 'bounding-box)
(format #t "~Tmin: ~`vector`P~%" (-> obj min))
(format #t "~Tmax: ~`vector`P~%" (-> obj max))
obj
)
;; definition of type bounding-box4w
(deftype bounding-box4w (structure)
((min vector4w :inline :offset-assert 0)
(max vector4w :inline :offset-assert 16)
)
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
)
;; definition for method 3 of type bounding-box4w
(defmethod inspect bounding-box4w ((obj bounding-box4w))
(format #t "[~8x] ~A~%" obj 'bounding-box4w)
(format #t "~Tmin: ~`vector4w`P~%" (-> obj min))
(format #t "~Tmax: ~`vector4w`P~%" (-> obj max))
obj
)
;; definition of type bounding-box-both
(deftype bounding-box-both (structure)
((box bounding-box :inline :offset-assert 0)
(box4w bounding-box4w :inline :offset-assert 32)
)
:method-count-assert 9
:size-assert #x40
:flag-assert #x900000040
)
;; definition for method 3 of type bounding-box-both
(defmethod inspect bounding-box-both ((obj bounding-box-both))
(format #t "[~8x] ~A~%" obj 'bounding-box-both)
(format #t "~Tbox: #<bounding-box @ #x~X>~%" (-> obj box))
(format #t "~Tbox4w: #<bounding-box4w @ #x~X>~%" (-> obj box4w))
obj
)
;; failed to figure out what this is:
(let ((v0-3 0))
)
;; failed to figure out what this is:
(none)

View File

@ -0,0 +1,146 @@
;;-*-Lisp-*-
(in-package goal)
;; definition for function box-vector-enside?
(defun box-vector-enside? ((box bounding-box) (pt vector))
(and
(< (-> box min data 0) (-> pt data 0))
(< (-> box min data 1) (-> pt data 1))
(< (-> box min data 2) (-> pt data 2))
(< (-> pt data 0) (-> box max data 0))
(< (-> pt data 1) (-> box max data 1))
(< (-> pt data 2) (-> box max data 2))
)
)
;; definition for function box-vector-inside?
(defun box-vector-inside? ((box bounding-box) (pt vector))
(and
(>= (-> pt data 0) (-> box min data 0))
(>= (-> pt data 1) (-> box min data 1))
(>= (-> pt data 2) (-> box min data 2))
(>= (-> box max data 0) (-> pt data 0))
(>= (-> box max data 1) (-> pt data 1))
(>= (-> box max data 2) (-> pt data 2))
)
)
;; definition for method 11 of type bounding-box
(defmethod
set-from-point-offset!
bounding-box
((obj bounding-box) (arg0 vector3s) (arg1 vector3s))
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
(vf5 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf3 arg1)
(.lvf vf4 arg0)
(.add.vf vf5 vf4 vf3)
(.min.vf vf1 vf4 vf5)
(.max.vf vf2 vf4 vf5)
(.mov.vf vf1 vf0 :mask #b1000)
(.mov.vf vf2 vf0 :mask #b1000)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 10 of type bounding-box
(defmethod add-point! bounding-box ((obj bounding-box) (arg0 vector3s))
(rlet ((vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf3 arg0)
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf3)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 15 of type bounding-box
(defmethod add-box! bounding-box ((obj bounding-box) (arg0 bounding-box))
(rlet ((vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
)
(.lvf vf1 obj)
(.lvf vf2 obj :offset 16)
(.lvf vf3 arg0)
(.lvf vf4 arg0 :offset 16)
(.min.vf vf1 vf1 vf3)
(.max.vf vf2 vf2 vf4)
(.svf obj vf1)
(.svf obj vf2 :offset 16)
0
)
)
;; definition for method 12 of type bounding-box
(defmethod
set-from-point-offset-pad!
bounding-box
((obj bounding-box) (arg0 vector3s) (arg1 vector3s) (arg2 float))
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
(vf4 :class vf)
(vf5 :class vf)
(vf6 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf4 arg1)
(.lvf vf5 arg0)
(.mov vf1 arg2)
(.add.vf vf6 vf5 vf4)
(.min.vf vf2 vf5 vf6)
(.max.vf vf3 vf5 vf6)
(.add.x.vf vf3 vf3 vf1 :mask #b111)
(.sub.x.vf vf2 vf2 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
0
)
)
;; definition for method 13 of type bounding-box
(defmethod set-from-sphere! bounding-box ((obj bounding-box) (arg0 sphere))
(rlet ((vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
(vf3 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(.lvf vf1 arg0)
(.sub.w.vf vf2 vf1 vf1 :mask #b111)
(.add.w.vf vf3 vf1 vf1 :mask #b111)
(.mov.vf vf2 vf0 :mask #b1000)
(.mov.vf vf3 vf0 :mask #b1000)
(.svf obj vf2)
(.svf obj vf3 :offset 16)
0
)
)
;; definition for method 14 of type bounding-box
;; ERROR: function was not converted to expressions. Cannot decompile.
;; definition for method 9 of type bounding-box
;; ERROR: function was not converted to expressions. Cannot decompile.
;; failed to figure out what this is:
(none)

View File

@ -208,12 +208,13 @@
;; definition for function remove-exit
(defun remove-exit ()
(local-vars (pp process)) (if (-> pp stack-frame-top)
(let ((v0-0 (-> pp stack-frame-top next)))
(set! (-> pp stack-frame-top) v0-0)
v0-0
)
)
(local-vars (pp process))
(if (-> pp stack-frame-top)
(let ((v0-0 (-> pp stack-frame-top next)))
(set! (-> pp stack-frame-top) v0-0)
v0-0
)
)
)
;; definition (debug) for function stream<-process-mask
@ -1506,11 +1507,9 @@
(while s2-1
(inspect-process-tree (-> s2-1 0) (+ arg1 1) (if (not (-> s2-1 0 brother))
arg2
(let* ((v1-7 1)
(a2-4 (+ arg1 1))
(v1-8 (ash v1-7 a2-4))
)
(logior arg2 v1-8)
(logior
arg2
(ash 1 (+ arg1 1))
)
)
arg3

View File

@ -0,0 +1,247 @@
;;-*-Lisp-*-
(in-package goal)
;; definition for function truncate
(defun truncate ((arg0 float))
(the float (the int arg0))
)
;; definition for function integral?
(defun integral? ((arg0 float))
(= (the float (the int arg0)) arg0)
)
;; definition for function fractional-part
(defun fractional-part ((arg0 float))
(- arg0 (the float (the int arg0)))
)
;; definition of type rgba
(deftype rgba (uint32)
((r uint8 :offset 0 :size 8)
(g uint8 :offset 8 :size 8)
(b uint8 :offset 16 :size 8)
(a uint8 :offset 24 :size 8)
)
:method-count-assert 9
:size-assert #x4
:flag-assert #x900000004
)
;; definition of type xyzw
(deftype xyzw (uint128)
()
:method-count-assert 9
:size-assert #x10
:flag-assert #x900000010
)
;; definition of type xyzwh
(deftype xyzwh (uint128)
()
:method-count-assert 9
:size-assert #x10
:flag-assert #x900000010
)
;; definition for function log2
(defun log2 ((arg0 int))
(let ((arg0 (gpr->fpr arg0)))
(+ (sar (the-as int (the float arg0)) 23) -127)
)
)
;; definition for function seek
(defun seek ((x float) (target float) (diff float))
(let ((err (- target x)))
(cond
((>= diff (fabs err))
target
)
((>= err 0.0)
(+ x diff)
)
(else
(- x diff)
)
)
)
)
;; definition for function lerp
(defun lerp ((minimum float) (maximum float) (amount float))
(+ minimum (* amount (- maximum minimum)))
)
;; definition for function lerp-scale
(defun
lerp-scale
((min-out float) (max-out float) (in float) (min-in float) (max-in float))
(let ((scale (fmax 0.0 (fmin 1.0 (/ (- in min-in) (- max-in min-in))))))
(+ (* (- 1.0 scale) min-out) (* scale max-out))
)
)
;; definition for function lerp-clamp
(defun lerp-clamp ((minimum float) (maximum float) (amount float))
(cond
((>= 0.0 amount)
minimum
)
((>= amount 1.0)
maximum
)
(else
(+ (* (- 1.0 amount) minimum) (* amount maximum))
)
)
)
;; definition for function seekl
(defun seekl ((arg0 int) (arg1 int) (arg2 int))
(let* ((v1-0 (- arg1 arg0))
(a3-0 (abs v1-0))
)
(cond
((>= arg2 a3-0)
arg1
)
((>= v1-0 0)
(+ arg0 arg2)
)
(else
(- arg0 arg2)
)
)
)
)
;; definition for function rand-vu-init
;; INFO: Return type mismatch int vs float.
;; WARN: Unsupported inline assembly instruction kind - [56]
;; WARN: Unsupported inline assembly instruction kind - [57]
(defun rand-vu-init ((arg0 float))
(local-vars (v0-0 int))
(.ctc2.i vi_R arg0)
(.cfc2.i v0-0 vi_R)
(the-as float v0-0)
)
;; failed to figure out what this is:
(rand-vu-init 1.418091)
;; definition for function rand-vu
;; INFO: Return type mismatch int vs float.
;; WARN: Inline assembly instruction marked with TODO - [TODO.VRGET]
;; WARN: Inline assembly instruction marked with TODO - [TODO.VRXOR]
;; WARN: Inline assembly instruction marked with TODO - [TODO.VRNEXT]
(defun rand-vu ()
(local-vars (v0-0 int))
(rlet ((Q :class vf)
(vf0 :class vf)
(vf1 :class vf)
(vf2 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(TODO.VRGET vf1)
(.sqrt.vf Q vf1 :ftf #b0)
(.add.vf vf2 vf0 Q :mask #b1)
(TODO.VRXOR vf2)
(TODO.VRNEXT vf1)
(.sub.w.vf vf1 vf1 vf0)
(.mov v0-0 vf1)
(the-as float v0-0)
)
)
;; definition for function rand-vu-nostep
;; INFO: Return type mismatch int vs float.
;; WARN: Inline assembly instruction marked with TODO - [TODO.VRGET]
(defun rand-vu-nostep ()
(local-vars (v0-0 int))
(rlet ((vf0 :class vf)
(vf1 :class vf)
)
(.lvf vf0 (new 'static 'vector :x 0.0 :y 0.0 :z 0.0 :w 1.0))
(TODO.VRGET vf1)
(.sub.w.vf vf1 vf1 vf0)
(.mov v0-0 vf1)
(the-as float v0-0)
)
)
;; definition for function rand-vu-float-range
(defun rand-vu-float-range ((arg0 float) (arg1 float))
(+ arg0 (* (rand-vu) (- arg1 arg0)))
)
;; definition for function rand-vu-percent?
(defun rand-vu-percent? ((arg0 float))
(>= arg0 (rand-vu))
)
;; definition for function rand-vu-int-range
(defun rand-vu-int-range ((first int) (second int))
(cond
((< first second)
(set! second (+ second 1))
(let ((v1-1 second))
)
)
(else
(set! first (+ first 1))
(let ((v1-3 first))
)
)
)
(let
((float-in-range (rand-vu-float-range (the float first) (the float second))))
(when (< float-in-range 0.0)
(set! float-in-range (+ -1.0 float-in-range))
(let ((v1-5 float-in-range))
)
)
(the int float-in-range)
)
)
;; definition for function rand-vu-int-count
(defun rand-vu-int-count ((arg0 int))
(the int (* (rand-vu) (the float arg0)))
)
;; definition of type random-generator
(deftype random-generator (basic)
((seed uint32 :offset-assert 4)
)
:method-count-assert 9
:size-assert #x8
:flag-assert #x900000008
)
;; definition for method 3 of type random-generator
(defmethod inspect random-generator ((obj random-generator))
(format #t "[~8x] ~A~%" obj (-> obj type))
(format #t "~Tseed: ~D~%" (-> obj seed))
obj
)
;; definition for symbol *random-generator*, type random-generator
(define
*random-generator*
(the-as random-generator (new 'global 'random-generator))
)
;; failed to figure out what this is:
(set! (-> *random-generator* seed) (the-as uint #x666edd1e))
;; definition for function rand-uint31-gen
;; ERROR: function was not converted to expressions. Cannot decompile.
;; failed to figure out what this is:
(let ((v0-6 0))
)
;; failed to figure out what this is:
(none)

View File

@ -0,0 +1,25 @@
;;-*-Lisp-*-
(in-package goal)
;; definition of type time-frame
(deftype time-frame (int64)
()
:method-count-assert 9
:size-assert #x8
:flag-assert #x900000008
)
;; definition of type part-id
(deftype part-id (uint32)
()
:method-count-assert 9
:size-assert #x4
:flag-assert #x900000004
)
;; failed to figure out what this is:
(let ((v0-2 0))
)
;; failed to figure out what this is:
(none)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
;;-*-Lisp-*-
(in-package goal)
;; failed to figure out what this is:
(let ((v0-0 0))
)
;; failed to figure out what this is:
(none)

View File

@ -2822,4 +2822,131 @@ TEST_F(FormRegressionTest, LoopingCode) {
" (the-as symbol #f)\n"
" )";
test_with_expr(func, type, expected);
}
TEST_F(FormRegressionTest, AbsAsSideEffect) {
std::string func =
"sll r0, r0, 0\n"
" dsubu v1, a1, a0\n"
" or a3, v1, r0\n"
" bltzl a3, L14\n"
" dsubu a3, r0, a3\n"
"L14:\n"
" slt a3, a2, a3\n"
" bne a3, r0, L15\n"
" sll r0, r0, 0\n"
" or v0, a1, r0\n"
" beq r0, r0, L17\n"
" sll r0, r0, 0\n"
"L15:\n"
" slt v1, v1, r0\n"
" bne v1, r0, L16\n"
" sll r0, r0, 0\n"
" daddu v0, a0, a2\n"
" beq r0, r0, L17\n"
" sll r0, r0, 0\n"
"L16:\n"
" dsubu v0, a0, a2\n"
"L17:\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function int int int int)";
std::string expected =
"(let* ((v1-0 (- arg1 arg0))\n"
" (a3-0 (abs v1-0))\n" // modified
" )\n"
//" (set! a3-0 (abs a3-0))\n"
" (cond\n"
" ((>= arg2 a3-0)\n"
" arg1\n"
" )\n"
" ((>= v1-0 0)\n"
" (+ arg0 arg2)\n"
" )\n"
" (else\n"
" (- arg0 arg2)\n"
" )\n"
" )\n"
" )";
test_with_expr(func, type, expected);
}
// for github https://github.com/water111/jak-project/issues/332
// method 11 bit-array
TEST_F(FormRegressionTest, AshPropagation) {
// (ash a2-0 a3-0)
std::string func =
"sll r0, r0, 0\n"
" dsra v1, a1, 3\n"
" daddu v1, v1, a0\n"
" lbu v1, 8(v1)\n" // (-> arg0 bytes (sar arg1 3)) [LOAD]
" addiu a2, r0, 1\n"
" andi a3, a1, 7\n"
" bgezl a3, L17\n" // use
" dsllv a2, a2, a3\n" // use, def
" dsubu a3, r0, a3\n" // use
" dsrav a2, a2, a3\n" // (ash 1 (logand arg1 7)
"L17:\n"
" or v1, v1, a2\n" // (logior (-> arg0 bytes (sar arg1 3)) (ash 1 (logand arg1 7)))
" dsra a1, a1, 3\n" // compute source.
" daddu a0, a1, a0\n"
" sb v1, 8(a0)\n"
" or v0, r0, r0\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function bit-array int int)";
std::string expected =
"(begin\n"
" (set!\n"
" (-> arg0 bytes (sar arg1 3))\n"
" (logior (-> arg0 bytes (sar arg1 3)) (the-as uint (ash 1 (logand arg1 7))))\n"
" )\n"
" 0\n"
" )";
test_with_expr(func, type, expected);
}
// for github https://github.com/water111/jak-project/issues/332
// method 9 bit-array
// also checks output prop.
TEST_F(FormRegressionTest, AshPropagation2) {
// (ash a2-0 a3-0)
std::string func =
"sll r0, r0, 0\n"
"L20:\n"
" dsra v1, a1, 3\n"
" daddu v1, v1, a0\n"
" lbu v1, 8(v1)\n"
" daddiu v0, s7, 8\n"
" addiu a0, r0, 1\n"
" andi a1, a1, 7\n"
" bgezl a1, L21\n"
" dsllv a0, a0, a1\n"
" dsubu a1, r0, a1\n"
" dsrav a0, a0, a1\n"
"L21:\n"
" and v1, v1, a0\n"
" movz v0, s7, v1\n"
" jr ra\n"
" daddu sp, sp, r0";
std::string type = "(function bit-array int symbol)";
std::string expected =
"(let ((v1-2 (-> arg0 bytes (sar arg1 3))))\n"
" (nonzero? (logand v1-2 (the-as uint (ash 1 (logand arg1 7)))))\n"
" )";
test_with_expr(func, type, expected);
}

View File

@ -33,4 +33,15 @@ TEST(InstructionDecode, VSQRT) {
LinkedWord vdiv_word(vdiv_test);
auto instr = decode_instruction(vdiv_word, file, 0, 0);
EXPECT_EQ(instr.to_string({}), "vsqrt Q, vf20.z");
}
TEST(Instruction, IntelMaskMove) {
init_opcode_info();
LinkedObjectFile file;
u32 vmove_instr = 0b010010'1'1100'00001'00010'01100111100;
LinkedWord vmove_word(vmove_instr);
auto instr = decode_instruction(vmove_word, file, 0, 0);
EXPECT_EQ(instr.to_string({}), "vmove.xy vf1, vf2");
// this should be flipped from the PS2...
EXPECT_EQ(instr.cop2_dest_mask_intel(), 0b0011);
}

View File

@ -6,18 +6,29 @@
#include "decompiler/config.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "goalc/compiler/Compiler.h"
#include "common/util/Timer.h"
namespace {
// the object files to test
const std::unordered_set<std::string> g_object_files_to_decompile = {
"gcommon", "gstring-h", "gkernel-h", "gkernel", /*"pskernel",*/ "gstring", "dgo-h", "gstate",
};
const std::unordered_set<std::string> g_object_files_to_decompile = {"gcommon",
"gstring-h",
"gkernel-h",
"gkernel",
/*"pskernel",*/ "gstring",
"dgo-h",
"gstate",
"types-h",
"vu1-macros",
"math",
"vector-h",
"bounding-box-h",
/* gap */ "bounding-box"};
// 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", "gkernel-h", "gkernel", "gstring", "dgo-h", "gstate",
};
"gstring-h", "gkernel-h", "gkernel", "gstring", "dgo-h", "gstate",
"types-h", "vu1-macros", "math", "vector-h", "bounding-box-h", /* gap */ "bounding-box"};
// the functions we expect the decompiler to skip
const std::unordered_set<std::string> expected_skip_in_decompiler = {
@ -39,14 +50,16 @@ const std::unordered_set<std::string> expected_skip_in_decompiler = {
"kernel-check-hardwired-addresses", // ps2 ee kernel debug hook
"kernel-read-function", // ps2 ee kernel debug hook
"kernel-write-function", // ps2 ee kernel debug hook
"kernel-copy-function" // ps2 ee kernel debug hook
"kernel-copy-function", // ps2 ee kernel debug hook
// math
"rand-uint31-gen", // weird and terrible random generator
// bounding-box
"(method 9 bounding-box)", // handwritten asm loop
"(method 14 bounding-box)", // handwritten asm loop
};
const std::unordered_set<std::string> skip_in_compiling = {
//////////////////////
// GCOMMON
//////////////////////
/// GCOMMON
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
"abs", "ash", "min", "max", "lognor",
// weird PS2 specific debug registers:
@ -60,23 +73,34 @@ const std::unordered_set<std::string> skip_in_compiling = {
// inline assembly
"valid?",
//////////////////////
// GKERNEL-H
//////////////////////
/// GKERNEL-H
// bitfields, possibly inline assembly
"(method 2 handle)",
//////////////////////
// GKERNEL
//////////////////////
/// GKERNEL
// asm
"(method 10 process)",
//////////////////////
// GSTATE
//////////////////////
/// GSTATE
"enter-state", // stack pointer asm
/// MATH
"rand-vu-init", "rand-vu", "rand-vu-nostep", // random hardware
"log2", // weird tricky int-as-float stuff
/// VECTOR-H
"(method 3 vector4w)", // print quad
"(method 3 vector8h)", // print quad
"(method 3 vector16b)", // print quad
"(method 3 vector)", // print quad
"(method 3 rgbaf)", // print quad
"(method 3 plane)", // print quad
"(method 3 sphere)", // print quad
"(method 3 qword)", // print quad
"vector-zero!", // i128
"vector-copy!", // i128
"vector-dot", // fpu acc
"vector4-dot", // fpu acc
};
// default location for the data. It can be changed with a command line argument.
@ -264,7 +288,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() || !func.ir2.types_succeeded) {
if (!func.ir2.env.has_type_analysis() || !func.ir2.env.types_succeeded) {
lg::error("Function {} failed types", func.guessed_name.to_string());
failed_count++;
}
@ -359,18 +383,36 @@ TEST_F(OfflineDecompilation, Reference) {
}
}
namespace {
int line_count(const std::string& str) {
int result = 0;
for (auto& c : str) {
if (c == '\n') {
result++;
}
}
return result;
}
} // namespace
TEST_F(OfflineDecompilation, Compile) {
Compiler compiler;
compiler.run_front_end_on_string(file_util::read_text_file(file_util::get_file_path(
{"test", "decompiler", "reference", "all_forward_declarations.gc"})));
Timer timer;
int total_lines = 0;
for (auto& file : g_object_files_to_check_against_reference) {
auto& obj_l = db->obj_files_by_name.at(file);
ASSERT_EQ(obj_l.size(), 1);
std::string src = db->ir2_final_out(obj_l.at(0), skip_in_compiling);
total_lines += line_count(src);
compiler.run_full_compiler_on_string_no_save(src);
}
auto time = timer.getSeconds();
lg::info("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
(float)total_lines / time);
}