[decomp] Small fixes (#541)

* fix a few bugs

* fix local vars missing in top level

* more small fixes

* support missing inline array access case

* one more fix
This commit is contained in:
water111 2021-05-30 19:57:11 -04:00 committed by GitHub
parent c910a22c1b
commit b1a76b2291
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 298 additions and 117 deletions

View File

@ -289,15 +289,7 @@ bool TypeSystem::try_reverse_lookup_inline_array(const FieldReverseLookupInput&
assert(di.can_deref);
assert(!di.mem_deref); // if we make integer arrays allowed to be inline-array, this will break.
if (input.stride) {
if (input.stride != di.stride) {
return false;
}
if (input.offset >= di.stride) {
return false;
}
if (input.stride && input.stride == di.stride && input.offset < di.stride) {
// variable lookup.
FieldReverseLookupOutput::Token token;
token.kind = FieldReverseLookupOutput::Token::Kind::VAR_IDX;
@ -315,40 +307,40 @@ bool TypeSystem::try_reverse_lookup_inline_array(const FieldReverseLookupInput&
next_input.offset = input.offset;
next_input.base_type = di.result_type;
return try_reverse_lookup(next_input, path, addr_of, result_type);
} else {
// constant lookup, or accessing within the first one
// which element we are in
int elt_idx = input.offset / di.stride;
// how many bytes into the element we look
int offset_into_elt = input.offset - (elt_idx * di.stride);
// the expected number of bytes into the element we would look to grab a ref to the elt.
int expected_offset_into_elt = lookup_type(di.result_type)->get_offset();
FieldReverseLookupOutput::Token token;
token.kind = FieldReverseLookupOutput::Token::Kind::CONSTANT_IDX;
token.idx = elt_idx;
if (offset_into_elt == expected_offset_into_elt && !input.deref.has_value()) {
// just get an element (possibly zero, and we want to include the 0 if so)
// for the degenerate inline-array case, it seems more likely that we get the zeroth object
// rather than the array? Either way, this code should be compatible with both approaches.
path->push_back(token);
*addr_of = false;
*result_type = di.result_type;
return true;
}
// otherwise access within the element
path->push_back(token);
FieldReverseLookupInput next_input;
next_input.deref = input.deref;
next_input.stride = 0;
// try_reverse_lookup expects "offset_into_field - boxed_offset"
next_input.offset = offset_into_elt - expected_offset_into_elt;
next_input.base_type = di.result_type;
return try_reverse_lookup(next_input, path, addr_of, result_type);
}
// constant lookup, or accessing within the first one
// which element we are in
int elt_idx = input.offset / di.stride;
// how many bytes into the element we look
int offset_into_elt = input.offset - (elt_idx * di.stride);
// the expected number of bytes into the element we would look to grab a ref to the elt.
int expected_offset_into_elt = lookup_type(di.result_type)->get_offset();
FieldReverseLookupOutput::Token token;
token.kind = FieldReverseLookupOutput::Token::Kind::CONSTANT_IDX;
token.idx = elt_idx;
if (offset_into_elt == expected_offset_into_elt && !input.deref.has_value()) {
// just get an element (possibly zero, and we want to include the 0 if so)
// for the degenerate inline-array case, it seems more likely that we get the zeroth object
// rather than the array? Either way, this code should be compatible with both approaches.
path->push_back(token);
*addr_of = false;
*result_type = di.result_type;
return true;
}
// otherwise access within the element
path->push_back(token);
FieldReverseLookupInput next_input;
next_input.deref = input.deref;
next_input.stride = input.stride;
// try_reverse_lookup expects "offset_into_field - boxed_offset"
next_input.offset = offset_into_elt - expected_offset_into_elt;
next_input.base_type = di.result_type;
return try_reverse_lookup(next_input, path, addr_of, result_type);
}
/*!

View File

@ -191,6 +191,9 @@ class Env {
const std::unordered_map<std::string, std::string>& var_remap_map() const { return m_var_remap; }
// hacks:
bool aggressively_reject_cond_to_value_rewrite = false;
private:
RegisterAccess m_end_var;

View File

@ -317,6 +317,14 @@ goos::Object SetVarElement::to_form_internal(const Env& env) const {
return pretty_print::build_list("set!", m_dst.to_form(env), m_src->to_form(env));
}
std::optional<TypeSpec> SetVarElement::required_cast(const Env& env) const {
auto expected_type = env.get_variable_type(m_dst, true);
if (!env.dts->ts.tc(expected_type, m_src_type)) {
return expected_type;
}
return std::nullopt;
}
void SetVarElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
m_src->apply(f);
@ -2439,14 +2447,13 @@ void GetSymbolStringPointer::get_modified_regs(RegSet& regs) const {
// Utilities
////////////////////////////////
std::optional<SimpleAtom> form_as_atom(const Form* f) {
auto as_single = f->try_as_single_element();
auto as_atom = dynamic_cast<SimpleAtomElement*>(as_single);
std::optional<SimpleAtom> form_element_as_atom(const FormElement* f) {
auto as_atom = dynamic_cast<const SimpleAtomElement*>(f);
if (as_atom) {
return as_atom->atom();
}
auto as_se = dynamic_cast<SimpleExpressionElement*>(as_single);
auto as_se = dynamic_cast<const SimpleExpressionElement*>(f);
if (as_se && as_se->expr().is_identity()) {
return as_se->expr().get_arg(0);
}
@ -2454,6 +2461,11 @@ std::optional<SimpleAtom> form_as_atom(const Form* f) {
return {};
}
std::optional<SimpleAtom> form_as_atom(const Form* f) {
auto as_single = f->try_as_single_element();
return form_element_as_atom(as_single);
}
FormElement* make_cast_using_existing(FormElement* elt, const TypeSpec& type, FormPool& pool) {
auto as_cast = dynamic_cast<CastElement*>(elt);
if (as_cast) {

View File

@ -304,6 +304,8 @@ class SetVarElement : public FormElement {
const SetVarInfo& info() const { return m_var_info; }
const TypeSpec src_type() const { return m_src_type; }
std::optional<TypeSpec> required_cast(const Env& env) const;
private:
RegisterAccess m_dst;
Form* m_src = nullptr;
@ -1636,6 +1638,7 @@ class FormPool {
std::vector<FormElement*> m_elements;
};
std::optional<SimpleAtom> form_element_as_atom(const FormElement* f);
std::optional<SimpleAtom> form_as_atom(const Form* f);
FormElement* make_cast_using_existing(Form* form, const TypeSpec& type, FormPool& pool);
FormElement* make_cast_using_existing(FormElement* elt, const TypeSpec& type, FormPool& pool);

View File

@ -1725,7 +1725,7 @@ void StoreArrayAccess::push_to_stack(const Env& env, FormPool& pool, FormStack&
auto fr = pool.alloc_element<SetFormFormElement>(
form_out, make_optional_cast(m_src_cast_type, expr_form, pool, env));
fr->mark_popped();
stack.push_form_element(fr, true);
fr->push_to_stack(env, pool, stack);
}
///////////////////
@ -2178,6 +2178,8 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
// check all to see if they write the value.
std::vector<SetVarElement*> dest_sets;
std::vector<TypeSpec> source_types; // only explicit accesses that aren't move-eliminated
int empty_count = 0;
for (auto form : write_output_forms) {
auto last_in_body = dynamic_cast<SetVarElement*>(form->elts().back());
if (last_in_body) {
@ -2191,9 +2193,11 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac
}
last_var = last_in_body->dst();
}
// For now, I am fine with letting this fail. For example, if the set is eliminated by a
// coloring move. If this makes really ugly code later on, we could use this to disable
// write as set.
empty_count++;
}
if (empty_count > 0 && env.aggressively_reject_cond_to_value_rewrite) {
rewrite_as_set = false;
}
if (!last_var.has_value()) {

View File

@ -2,6 +2,7 @@
#include "FormStack.h"
#include "Form.h"
#include "GenericElementMatcher.h"
#include "decompiler/Function/Function.h"
namespace decompiler {
std::string FormStack::StackEntry::print(const Env& env) const {
@ -285,7 +286,7 @@ FormElement* try_rewrites_in_place(SetVarElement* in, const Env& env, FormPool&
}
} // namespace
std::vector<FormElement*> FormStack::rewrite(FormPool& pool, const Env& env) {
std::vector<FormElement*> FormStack::rewrite(FormPool& pool, const Env& env) const {
std::vector<FormElement*> result;
for (auto& e : m_stack) {
@ -340,7 +341,7 @@ std::vector<FormElement*> FormStack::rewrite(FormPool& pool, const Env& env) {
void rewrite_to_get_var(std::vector<FormElement*>& default_result,
FormPool& pool,
const RegisterAccess& var,
const Env&) {
const Env& env) {
bool keep_going = true;
RegisterAccess var_to_get = var;
@ -361,7 +362,18 @@ void rewrite_to_get_var(std::vector<FormElement*>& default_result,
var_to_get = as_one->expr().var();
}
result = last_op_as_set->src()->elts();
auto cast = last_op_as_set->required_cast(env);
if (cast && cast == TypeSpec("none")) {
env.func->warnings.general_warning(
"rewrite_to_get_var got a none typed variable. Is there unreachable code?");
cast = std::nullopt;
}
if (cast) {
result = {pool.alloc_element<CastElement>(
*cast, pool.alloc_sequence_form(nullptr, last_op_as_set->src()->elts()))};
} else {
result = last_op_as_set->src()->elts();
}
}
first = false;
}

View File

@ -42,7 +42,7 @@ class FormStack {
int begin_idx = -1);
FormElement* pop_back(FormPool& pool);
bool is_single_expression();
std::vector<FormElement*> rewrite(FormPool& pool, const Env& env);
std::vector<FormElement*> rewrite(FormPool& pool, const Env& env) const;
std::string print(const Env& env);
bool is_root() const { return m_is_root_stack; }

View File

@ -349,6 +349,11 @@ void ObjectFileDB::ir2_type_analysis_pass(const Config& config) {
config.hacks.pair_functions_by_name.end()) {
func.ir2.env.set_sloppy_pair_typing();
}
if (config.hacks.reject_cond_to_value.find(func_name) !=
config.hacks.reject_cond_to_value.end()) {
func.ir2.env.aggressively_reject_cond_to_value_rewrite = true;
}
func.ir2.env.set_stack_var_hints(try_lookup(config.stack_var_hints_by_function, func_name));
if (run_type_analysis_ir2(ts, dts, func)) {
successful_functions++;

View File

@ -66,17 +66,20 @@ RegisterAccess make_dst_var(const Instruction& i, int idx) {
// Atom Helpers
////////////////////////
SimpleAtom false_sym() {
return SimpleAtom::make_sym_val("#f");
}
SimpleAtom make_src_atom(Register reg, int idx) {
if (reg == Register(Reg::GPR, Reg::R0)) {
return SimpleAtom::make_int_constant(0);
}
if (reg == Register(Reg::GPR, Reg::S7)) {
return false_sym();
}
return SimpleAtom::make_var(make_src_var(reg, idx));
}
SimpleAtom false_sym() {
return SimpleAtom::make_sym_val("#f");
}
////////////////////////
// Expression Helpers
////////////////////////

View File

@ -67,9 +67,19 @@ bool convert_to_expressions(
if (f.type.last_arg() != TypeSpec("none")) {
auto return_var = f.ir2.atomic_ops->end_op().return_var();
new_entries = rewrite_to_get_var(stack, pool, return_var, f.ir2.env);
auto reg_return_type =
f.ir2.env.get_types_after_op(f.ir2.atomic_ops->ops.size() - 1).get(return_var.reg());
if (!dts.ts.tc(f.type.last_arg(), reg_return_type.typespec())) {
TypeSpec return_type = f.ir2.env.get_types_after_op(f.ir2.atomic_ops->ops.size() - 1)
.get(return_var.reg())
.typespec();
auto back_as_atom = form_element_as_atom(new_entries.back());
if (back_as_atom && back_as_atom->is_var()) {
return_type = f.ir2.env.get_variable_type(back_as_atom->var(), true);
auto var_cast = f.ir2.env.get_variable_and_cast(back_as_atom->var());
if (var_cast.cast) {
return_type = *var_cast.cast;
}
}
if (!dts.ts.tc(f.type.last_arg(), return_type)) {
// we need to cast the final value.
auto to_cast = new_entries.back();
new_entries.pop_back();

View File

@ -173,6 +173,15 @@ std::string write_from_top_level(const Function& top_level,
}
std::string result;
// local vars:
int var_count = 0;
auto var_dec = env.local_var_type_list(top_level.ir2.top_form, 0, &var_count);
if (var_count > 0) {
result += pretty_print::to_string(var_dec);
result += '\n';
result += '\n';
}
// look for the whole thing being in a (when *debug-segment* ....)
bool in_debug_only_file = false;
if (forms.size() == 1) {
@ -341,6 +350,15 @@ std::string write_from_top_level(const Function& top_level,
}
}
if (!something_matched) {
auto empty = dynamic_cast<EmptyElement*>(x);
if (empty) {
something_matched = true;
} else if (!x->active()) {
something_matched = true;
}
}
if (!something_matched) {
result += ";; failed to figure out what this is:\n";
result += pretty_print::to_string(x->to_form(env));

View File

@ -419,7 +419,7 @@ SSA make_rc_ssa(const Function& function, const RegUsageInfo& rui, const Functio
if (succ != -1) {
for (auto reg : end_op_info.live) {
// only update phis for variables that are actually live at the next block.
if (reg.get_kind() != Reg::VF) {
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
ssa.add_source_to_phi(succ, reg, current_regs.at(reg));
}
}

View File

@ -143,6 +143,8 @@ Config read_config_file(const std::string& path_to_config_file) {
hacks_json.at("no_type_analysis_functions_by_name").get<std::unordered_set<std::string>>();
config.hacks.types_with_bad_inspect_methods =
hacks_json.at("types_with_bad_inspect_methods").get<std::unordered_set<std::string>>();
config.hacks.reject_cond_to_value = hacks_json.at("aggressively_reject_cond_to_value_rewrite")
.get<std::unordered_set<std::string>>();
for (auto& entry : hacks_json.at("cond_with_else_max_lengths")) {
auto func_name = entry.at(0).get<std::string>();

View File

@ -53,6 +53,7 @@ struct DecompileHacks {
std::unordered_set<std::string> asm_functions_by_name;
std::unordered_set<std::string> pair_functions_by_name;
std::unordered_map<std::string, CondWithElseLengthHack> cond_with_else_len_by_func_name;
std::unordered_set<std::string> reject_cond_to_value;
};
struct Config {

View File

@ -33697,7 +33697,7 @@
;;(define-extern notice-top object) ;; unknown type
;;(define-extern speed object) ;; unknown type
;;(define-extern cam-notice-dist object) ;; unknown type
(define-extern process-drawable-art-error symbol) ;; unknown type
(define-extern process-drawable-art-error state)
;;(define-extern notice-bottom object) ;; unknown type
;;(define-extern eco-info object) ;; unknown type
;;(define-extern cam-horz object) ;; unknown type

View File

@ -19,7 +19,16 @@
["(method 20 res-lump)", "b0", 2]
],
// if a cond with an else case is being used a value in a place where it looks wrong
// you can add the function name to this list and it will more aggressively reject this rewrite.
"aggressively_reject_cond_to_value_rewrite": [
"(method 10 res-lump)",
"(method 11 res-lump)",
"(method 12 res-lump)"
],
// this provides a hint to the decompiler that these functions will have a lot of inline assembly.
// currently it just leaves pcpyld as an asm op.
"hint_inline_assembly_functions": ["matrix-transpose!"],
"asm_functions_by_name": [

View File

@ -397,7 +397,7 @@
"(method 0 align-control)": [
[[8, 13], "t9", "(function object object)"],
[14, "v0", "align-control"]
[[14,18], "v0", "align-control"]
],
"str-load": [
@ -405,7 +405,8 @@
],
"str-load-status":[
[[18, 28], "v1", "load-chunk-msg"]
[[18, 22], "v1", "load-chunk-msg"],
[26, "v1", "load-chunk-msg"]
],
"str-play-async": [

View File

@ -58,7 +58,7 @@
)
)
(set! (-> obj process) arg0)
(the-as align-control (the-as object obj))
obj
)
)

View File

@ -48,4 +48,4 @@
;; NOTE - I'm guessing there was a define-extern earlier in the build process
;; This is actually set in process-drawable.gc, but it's used by files earlier in the process
(define-extern process-drawable-art-error symbol)
(define-extern process-drawable-art-error state)

View File

@ -0,0 +1,71 @@
;;-*-Lisp-*-
(in-package goal)
;; definition of type align-control
(deftype align-control (basic)
((flags uint32 :offset-assert 4)
(process basic :offset-assert 8)
(frame-group basic :offset-assert 12)
(frame-num float :offset-assert 16)
(matrix matrix 2 :inline :offset-assert 32)
(transform transform 2 :inline :offset-assert 160)
(delta transformq :inline :offset-assert 256)
(last-speed float :offset-assert 304)
(align transformq :inline :offset 160)
)
:method-count-assert 14
:size-assert #x134
:flag-assert #xe00000134
(:methods
(new (symbol type process) _type_ 0)
(dummy-9 () none 9)
(dummy-10 () none 10)
(dummy-11 () none 11)
(dummy-12 () none 12)
(dummy-13 () none 13)
)
)
;; definition for method 3 of type align-control
(defmethod inspect align-control ((obj align-control))
(format #t "[~8x] ~A~%" obj (-> obj type))
(format #t "~Tflags: #x~X~%" (-> obj flags))
(format #t "~Tprocess: ~A~%" (-> obj process))
(format #t "~Tframe-group: ~A~%" (-> obj frame-group))
(format #t "~Tframe-num: ~f~%" (-> obj frame-num))
(format #t "~Tmatrix[2] @ #x~X~%" (-> obj matrix))
(format #t "~Ttransform[2] @ #x~X~%" (-> obj transform))
(format #t "~Tdelta: #<transformq @ #x~X>~%" (-> obj delta))
(format #t "~Tlast-speed: (meters ~m)~%" (-> obj last-speed))
(format #t "~Talign: #<transformq @ #x~X>~%" (-> obj transform))
obj
)
;; definition for method 0 of type align-control
;; INFO: Return type mismatch object vs align-control.
(defmethod
new
align-control
((allocation symbol) (type-to-make type) (arg0 process))
(local-vars (pp process))
(let
((obj
(object-new allocation type-to-make (the-as int (-> type-to-make size)))
)
)
(if (zero? obj)
(return (begin
(let ((t9-1 (the-as (function object object) enter-state))
(a0-1 "memory")
)
(set! (-> pp next-state) process-drawable-art-error)
(t9-1 a0-1)
)
(the-as align-control 0)
)
)
)
(set! (-> obj process) arg0)
obj
)
)

View File

@ -59,8 +59,4 @@
;; definition for symbol *temp-mem-usage*, type symbol
(define *temp-mem-usage* #f)
;; failed to figure out what this is:
(empty-form)
)

View File

@ -1,6 +1,8 @@
;;-*-Lisp-*-
(in-package goal)
(local-vars (gp-0 game-info))
;; definition of type game-bank
(deftype game-bank (basic)
((life-max-default float :offset-assert 4)

View File

@ -106,16 +106,6 @@
(set! (-> *eye-control-array* data v1-5 blink) 0.0)
)
;; failed to figure out what this is:
(empty-form)
;; failed to figure out what this is:
(empty-form)
;; failed to figure out what this is:
(let ((v0-4 0))
)

View File

@ -224,12 +224,6 @@
(set! (-> *sky-upload-data* circle data gp-0 w) 0.0)
)
;; failed to figure out what this is:
(empty-form)
;; failed to figure out what this is:
(empty-form)
;; definition of type sky-tng-data
(deftype sky-tng-data (basic)
((giftag-base qword :inline :offset-assert 16)
@ -317,7 +311,3 @@
;; failed to figure out what this is:
(let ((v0-15 0))
)

View File

@ -116,7 +116,6 @@
)
;; definition for function str-load-status
;; INFO: Return type mismatch structure vs symbol.
(defun str-load-status ((length-out (pointer int32)))
(if (check-busy *load-str-rpc*)
(return 'busy)
@ -127,12 +126,9 @@
(if (= (-> response result) (load-msg-result error))
(return 'error)
)
(set!
(-> length-out 0)
(the-as int (the-as load-chunk-msg (-> response maxlen)))
)
(set! (-> length-out 0) (the-as int (-> response maxlen)))
)
(the-as symbol 'complete)
'complete
)
;; definition for function str-load-cancel

View File

@ -537,7 +537,7 @@
(+
(-> type-to-make size)
(the-as uint (* len (if (type-type? content-type number)
(-> content-type size)
(the-as int (-> content-type size))
4
)
)
@ -801,7 +801,7 @@
(the-as
uint
(* (-> obj allocated-length) (if (type-type? (-> obj content-type) number)
(-> obj content-type size)
(the-as int (-> obj content-type size))
4
)
)

View File

@ -159,7 +159,7 @@
)
(let ((obj (the-as cpu-thread (cond
((-> arg0 top-thread)
(&+ arg3 -7164)
(the-as cpu-thread (&+ arg3 -7164))
)
(else
(let
@ -183,7 +183,7 @@
)
)
)
(+ v1-2 4)
(the-as cpu-thread (+ v1-2 4))
)
)
)
@ -627,7 +627,7 @@
)
(-> obj name)
)
#f
(the-as process #f)
)
)
)
@ -1348,7 +1348,7 @@
)
)
)
(the-as process #f)
(the-as process (the-as process-tree #f))
)
;; definition for function kernel-dispatcher
@ -1553,7 +1553,7 @@
(when parent
(let ((child (-> parent 0 child)))
(if (= child proc)
(return #f)
(return (the-as (pointer process-tree) #f))
)
(while child
(if (= (-> child 0 brother) proc)
@ -1562,7 +1562,7 @@
(set! child (-> child 0 brother))
)
)
#f
(the-as (pointer process-tree) #f)
)
)
)

View File

@ -171,6 +171,7 @@
;; definition for function looping-code
;; INFO: Return type mismatch none vs symbol.
;; WARN: rewrite_to_get_var got a none typed variable. Is there unreachable code?
(defun looping-code ()
(while #t
(suspend)

View File

@ -1739,7 +1739,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod0) {
" int\n"
" (+\n"
" (-> arg1 size)\n"
" (the-as uint (* arg3 (if (type-type? arg2 number) (-> arg2 size) 4)))\n"
" (the-as uint (* arg3 (if (type-type? arg2 number) (the-as int (-> arg2 size)) 4)))\n"
" )\n"
" )\n"
" )\n"
@ -1817,7 +1817,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod5) {
" (-> arg0 allocated-length)\n"
" (if\n"
" (type-type? (-> arg0 content-type) number)\n"
" (-> arg0 content-type size)\n"
" (the-as int (-> arg0 content-type size))\n"
" 4\n"
" )\n"
" )\n"

View File

@ -337,7 +337,7 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
std::string expected =
"(let ((obj (the-as cpu-thread (cond\n"
" ((-> arg2 top-thread)\n"
" (&+ arg5 -7164)\n"
" (the-as cpu-thread (&+ arg5 -7164))\n"
" )\n"
" (else\n"
" (let\n"
@ -355,7 +355,7 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) {
" (+ (+ v1-2 (the-as int (-> arg1 size))) arg4)\n"
" )\n"
" )\n"
" (+ v1-2 4)\n"
" (the-as cpu-thread (+ v1-2 4))\n"
" )\n"
" )\n"
" )\n"
@ -1114,7 +1114,7 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) {
" )\n"
" (-> arg0 name)\n"
" )\n"
" #f\n"
" (the-as process #f)\n"
" )\n"
" )\n"
" )\n"

View File

@ -15,9 +15,8 @@ namespace {
// list of object files to ignore during reference checks
const std::unordered_set<std::string> g_files_to_skip_compiling = {
"timer", // accessing timer regs
"display", // interrupt handlers
"game-info-h", // variable scoped at object file top-level issue.
"timer", // accessing timer regs
"display", // interrupt handlers
};
// the functions we expect the decompiler to skip
@ -107,7 +106,6 @@ const std::unordered_set<std::string> g_functions_to_skip_compiling = {
"rand-vu-init",
"rand-vu",
"rand-vu-nostep", // random hardware
"log2", // weird tricky int-as-float stuff
// trig
"sin-rad", // fpu acc
@ -148,9 +146,6 @@ const std::unordered_set<std::string> g_functions_to_skip_compiling = {
// asm
"invalidate-cache-line",
// capture
"(method 3 gs-store-image-packet)", // print giftag weirdness
// sync-info
"(method 15 sync-info)", // needs display stuff first
"(method 15 sync-info-eased)", // needs display stuff first

View File

@ -40,6 +40,71 @@ TEST(TypeSystem, DefaultMethods) {
ts.assert_method_id("function", "mem-usage", GOAL_MEMUSAGE_METHOD);
}
TEST(TypeSystemReverse, NestedInlineWeird) {
// tests the case where we're accessing nested inline arrays, with a dynamic inner access
// and constant outer access, which will be constant propagated by the GOAL compiler.
TypeSystem ts;
ts.add_builtin_types();
goos::Reader reader;
auto add_type = [&](const std::string& str) {
auto in = reader.read_from_string(str).as_pair()->cdr.as_pair()->car.as_pair()->cdr;
parse_deftype(in, &ts);
};
add_type(
"(deftype rgba (uint32)\n"
" ((r uint8 :offset 0)\n"
" (g uint8 :offset 8)\n"
" (b uint8 :offset 16)\n"
" (a uint8 :offset 24)\n"
" )\n"
" :flag-assert #x900000004\n"
" )");
add_type(
"(deftype char-color (structure)\n"
" ((color rgba 4 :offset-assert 0)\n"
" )\n"
" :method-count-assert 9\n"
" :size-assert #x10\n"
" :flag-assert #x900000010\n"
" )");
add_type(
"(deftype font-work (structure)\n"
" (\n"
" (color-table char-color 64 :inline :offset 1984)\n"
" (last-color uint64 :offset-assert 3008)\n"
" (save-last-color uint64 :offset-assert 3016)\n"
" (buf basic :offset-assert 3024)\n"
" (str-ptr uint32 :offset-assert 3028)\n"
" (flags uint32 :offset-assert 3032)\n"
" (reg-save uint32 5 :offset-assert 3036)\n"
" )\n"
" :method-count-assert 9\n"
" :size-assert #xbf0\n"
" :flag-assert #x900000bf0\n"
" )");
FieldReverseLookupInput input;
input.stride = 4;
input.base_type = ts.make_typespec("font-work");
input.offset = 2496;
DerefKind dk;
dk.size = 4;
dk.sign_extend = false;
dk.is_store = false;
dk.reg_kind = RegClass::GPR_64;
input.deref = dk;
auto result = ts.reverse_field_lookup(input);
EXPECT_TRUE(result.success);
EXPECT_EQ(result.tokens.at(0).print(), "color-table");
EXPECT_EQ(result.tokens.at(1).print(), "32"); // 32 * 16 + 1984 = 2496
EXPECT_EQ(result.tokens.at(2).print(), "color");
EXPECT_EQ(result.tokens.at(3).kind, FieldReverseLookupOutput::Token::Kind::VAR_IDX);
}
TEST(TypeSystem, TypeSpec) {
TypeSystem ts;
ts.add_builtin_types();