From 9867155e7cf11ea6639a377024492c5aebd94421 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sat, 17 Apr 2021 18:35:50 -0400 Subject: [PATCH] [Decompiler] More support for bitfields (#363) * temp * initial support for dynamic bitfields * some more progress on display * dma-buffer good * small fixes --- common/goos/Interpreter.cpp | 1 + decompiler/CMakeLists.txt | 2 +- decompiler/IR2/AtomicOp.cpp | 8 + decompiler/IR2/AtomicOp.h | 2 + decompiler/IR2/AtomicOpForm.cpp | 27 +- decompiler/IR2/AtomicOpTypeAnalysis.cpp | 18 +- decompiler/IR2/Form.h | 121 +-------- decompiler/IR2/FormExpressionAnalysis.cpp | 32 ++- .../IR2/{BitfieldForms.cpp => bitfields.cpp} | 238 +++++++++++++----- decompiler/IR2/bitfields.h | 134 ++++++++++ decompiler/config/all-types.gc | 8 +- .../jak1_ntsc_black_label/label_types.jsonc | 5 +- .../jak1_ntsc_black_label/type_casts.jsonc | 25 ++ .../jak1_ntsc_black_label/var_names.jsonc | 30 ++- decompiler/util/data_decompile.cpp | 88 +++++++ decompiler/util/data_decompile.h | 24 ++ goal_src/engine/dma/dma-bucket.gc | 53 ++-- goal_src/engine/dma/dma-buffer.gc | 184 ++++++-------- goal_src/engine/dma/dma-h.gc | 39 ++- goal_src/engine/dma/dma.gc | 101 ++++---- goal_src/kernel-defs.gc | 2 +- goalc/compiler/Compiler.cpp | 16 +- test/decompiler/reference/dma-buffer_REF.gc | 212 ++++++++++++++++ test/decompiler/reference/dma-h_REF.gc | 8 +- test/decompiler/reference/timer-h_REF.gc | 6 +- test/decompiler/test_DataParser.cpp | 15 ++ test/decompiler/test_FormExpressionBuild2.cpp | 210 +++++++++++++++- test/offline/offline_test_main.cpp | 3 +- 28 files changed, 1215 insertions(+), 397 deletions(-) rename decompiler/IR2/{BitfieldForms.cpp => bitfields.cpp} (58%) create mode 100644 decompiler/IR2/bitfields.h create mode 100644 test/decompiler/reference/dma-buffer_REF.gc diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index c672ac445d..3bc52f8fe1 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -126,6 +126,7 @@ Object Interpreter::intern(const std::string& name) { * Display the REPL, which will run until the user executes exit. */ void Interpreter::execute_repl(ReplWrapper& repl) { + want_exit = false; while (!want_exit) { try { // read something from the user diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 8f056e300f..8064c30ae0 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -36,7 +36,7 @@ add_library( IR2/AtomicOp.cpp IR2/AtomicOpForm.cpp IR2/AtomicOpTypeAnalysis.cpp - IR2/BitfieldForms.cpp + IR2/bitfields.cpp IR2/Env.cpp IR2/Form.cpp IR2/FormExpressionAnalysis.cpp diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index 891f90b408..dce7877acb 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -160,6 +160,14 @@ goos::Object SimpleAtom::to_form(const std::vector& labels, con } } +goos::Object SimpleAtom::to_form(const Env& env) const { + return to_form(env.file->labels, env); +} + +std::string SimpleAtom::to_string(const Env& env) const { + return to_form(env).print(); +} + void SimpleAtom::collect_vars(RegAccessSet& vars) const { if (is_var()) { vars.insert(var()); diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index 013baefa9a..dbc38408c8 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -129,6 +129,8 @@ class SimpleAtom { static SimpleAtom make_int_constant(s64 value); static SimpleAtom make_static_address(int static_label_id); goos::Object to_form(const std::vector& labels, const Env& env) const; + goos::Object to_form(const Env& env) const; + std::string to_string(const Env& env) const; void collect_vars(RegAccessSet& vars) const; bool is_var() const { return m_kind == Kind::VARIABLE; } diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 393fad694d..9aa14b400a 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -3,6 +3,8 @@ #include "common/type_system/TypeSystem.h" #include "decompiler/util/DecompilerTypeSystem.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/util/data_decompile.h" +#include "decompiler/IR2/bitfields.h" namespace decompiler { @@ -605,12 +607,35 @@ Form* LoadVarOp::get_load_src(FormPool& pool, const Env& env) const { assert(word0.kind == LinkedWord::PLAIN_DATA); assert(word1.kind == LinkedWord::PLAIN_DATA); u64 value; - memcpy(&value, &word0.data, 4); memcpy(((u8*)&value) + 4, &word1.data, 4); return pool.alloc_single_element_form(nullptr, fmt::format("#x{:x}", value)); } + + // is it a constant bitfield? + auto& ts = env.dts->ts; + auto as_bitfield = dynamic_cast(ts.lookup_type(hint->second.type_name)); + if (as_bitfield && m_kind != Kind::FLOAT && m_size == 8) { + // get the data + assert((label.offset % 8) == 0); + auto word0 = env.file->words_by_seg.at(label.target_segment).at(label.offset / 4); + auto word1 = env.file->words_by_seg.at(label.target_segment).at(1 + (label.offset / 4)); + assert(word0.kind == LinkedWord::PLAIN_DATA); + assert(word1.kind == LinkedWord::PLAIN_DATA); + u64 value; + memcpy(&value, &word0.data, 4); + memcpy(((u8*)&value) + 4, &word1.data, 4); + // for some reason, GOAL would use a 64-bit constant for all bitfields, even if they are + // smaller. We should check that the higher bits are all zero. + int bits = as_bitfield->get_size_in_memory() * 8; + assert(bits <= 64); + assert((value >> bits) == 0); + TypeSpec typespec(hint->second.type_name); + auto defs = decompile_bitfield_from_int(typespec, ts, value); + return pool.alloc_single_element_form(nullptr, typespec, defs, + pool); + } } } } diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 3b189747e7..2a5040c9d8 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -286,6 +286,11 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, assert(m_args[1].get_int() < 64); return TP_Type::make_from_product(1ull << m_args[1].get_int(), is_signed(dts, arg0_type)); } + + if (m_args[1].is_int() && dts.ts.tc(TypeSpec("pointer"), arg0_type.typespec())) { + // allow shifting a pointer to put it in a bitfield. + return TP_Type::make_from_ts(TypeSpec("uint")); + } break; case Kind::MUL_SIGNED: { @@ -600,17 +605,18 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, return TP_Type::make_from_ts("float"); } + auto label_name = env.file->labels.at(src.label()).name; + auto hint = env.label_types().find(label_name); + if (hint != env.label_types().end()) { + return TP_Type::make_from_ts( + coerce_to_reg_type(env.dts->parse_type_spec(hint->second.type_name))); + } + if (m_size == 8) { // 8 byte integer constants are always loaded from a static pool // this could technically hide loading a different type from inside of a static basic. return TP_Type::make_from_ts(dts.ts.make_typespec("uint")); } - - auto label_name = env.file->labels.at(src.label()).name; - auto hint = env.label_types().find(label_name); - if (hint != env.label_types().end()) { - return TP_Type::make_from_ts(env.dts->parse_type_spec(hint->second.type_name)); - } } } diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 5bea8e8fb0..675541c585 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -907,6 +907,12 @@ class GenericOperator { return m_function; } + bool is_fixed(FixedOperatorKind kind) const { + return m_kind == Kind::FIXED_OPERATOR && m_fixed_kind == kind; + } + + bool is_fixed() const { return m_kind == Kind::FIXED_OPERATOR; } + private: friend class GenericElement; Kind m_kind = Kind::INVALID; @@ -1310,115 +1316,6 @@ class VectorFloatLoadStoreElement : public FormElement { bool m_is_load = false; }; -struct BitfieldManip { - enum class Kind { - LEFT_SHIFT, - RIGHT_SHIFT_LOGICAL, - RIGHT_SHIFT_LOGICAL_32BIT, - RIGHT_SHIFT_ARITH, - LOGAND, - LOGIOR_WITH_CONSTANT_INT, - NONZERO_COMPARE, - INVALID - } kind = Kind::INVALID; - s64 amount = -1; - - bool is_right_shift() const { - return kind == Kind::RIGHT_SHIFT_ARITH || kind == Kind::RIGHT_SHIFT_LOGICAL || - kind == Kind::RIGHT_SHIFT_LOGICAL_32BIT; - } - - bool right_shift_unsigned() const { - assert(is_right_shift()); - return kind == Kind::RIGHT_SHIFT_LOGICAL || kind == Kind::RIGHT_SHIFT_LOGICAL_32BIT; - } - - bool is_64bit_shift() const { - return kind == Kind::RIGHT_SHIFT_LOGICAL || kind == Kind::RIGHT_SHIFT_ARITH || - kind == Kind::LEFT_SHIFT; - } - - int get_shift_start_bit() const { - if (is_64bit_shift()) { - return 64; - } else { - return 32; - } - } - - BitfieldManip(Kind k, s64 imm) : kind(k), amount(imm) {} -}; - -class BitfieldReadElement : public FormElement { - public: - BitfieldReadElement(Form* base_value, const TypeSpec& ts); - goos::Object to_form_internal(const Env& env) const override; - void apply(const std::function& f) override; - void apply_form(const std::function& f) override; - void collect_vars(RegAccessSet& vars, bool recursive) const override; - void get_modified_regs(RegSet& regs) const override; - FormElement* push_step(const BitfieldManip step, const TypeSystem& ts, FormPool& pool); - - private: - Form* m_base = nullptr; - TypeSpec m_type; - std::vector m_steps; -}; - -struct BitFieldDef { - bool is_signed = false; - u64 value = -1; - std::string field_name; -}; - -std::vector decompile_static_bitfield(const TypeSpec& type, - const TypeSystem& ts, - u64 value); - -class BitfieldStaticDefElement : public FormElement { - public: - BitfieldStaticDefElement(const TypeSpec& type, const std::vector& field_defs); - goos::Object to_form_internal(const Env& env) const override; - void apply(const std::function& f) override; - void apply_form(const std::function& f) override; - void collect_vars(RegAccessSet& vars, bool recursive) const override; - void get_modified_regs(RegSet& regs) const override; - - private: - TypeSpec m_type; - std::vector m_field_defs; -}; - -struct BitfieldFormDef { - Form* value; - std::string field_name; -}; - -/*! - * This represents copying a bitfield object, then modifying the type. - * It's an intermediate step to modifying a bitfield in place and it's not expected to appear - * in the final output. - */ -class ModifiedCopyBitfieldElement : public FormElement { - public: - ModifiedCopyBitfieldElement(const TypeSpec& type, - Form* base, - const std::vector& field_modifications); - goos::Object to_form_internal(const Env& env) const override; - void apply(const std::function& f) override; - void apply_form(const std::function& f) override; - void collect_vars(RegAccessSet& vars, bool recursive) const override; - void get_modified_regs(RegSet& regs) const override; - - Form* base() const { return m_base; } - const std::vector mods() const { return m_field_modifications; } - - private: - TypeSpec m_type; - Form* m_base = nullptr; - std::vector m_field_modifications; -}; - /*! * A Form is a wrapper around one or more FormElements. * This is done for two reasons: @@ -1446,6 +1343,12 @@ class Form { } return nullptr; } + + template + T* try_as_element() const { + return dynamic_cast(try_as_single_element()); + } + bool is_single_element() const { return m_elements.size() == 1; } FormElement* operator[](int idx) { return m_elements.at(idx); } FormElement*& at(int idx) { return m_elements.at(idx); } diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index b0f519cffc..a7186ee97b 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -5,6 +5,7 @@ #include "decompiler/util/DecompilerTypeSystem.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" #include "decompiler/util/data_decompile.h" +#include "decompiler/IR2/bitfields.h" /* * TODO @@ -209,18 +210,10 @@ Form* cast_form(Form* in, const TypeSpec& new_type, FormPool& pool, const Env& e return in; } - auto in_as_atom = form_as_atom(in); - if (in_as_atom && in_as_atom->is_int()) { - auto type_info = env.dts->ts.lookup_type(new_type); - auto bitfield_info = dynamic_cast(type_info); - if (bitfield_info) { - // GOT BITFIELD: - // fmt::print("Integer constant {} is likely a static bitfield of type {}\n", - // in_as_atom->get_int(), bitfield_info->get_name()); - - auto fields = decompile_static_bitfield(new_type, env.dts->ts, in_as_atom->get_int()); - return pool.alloc_single_element_form(nullptr, new_type, fields); - } + auto type_info = env.dts->ts.lookup_type(new_type); + auto bitfield_info = dynamic_cast(type_info); + if (bitfield_info) { + return cast_to_bitfield(bitfield_info, new_type, pool, env, in); } return pool.alloc_single_element_form(nullptr, new_type, in); @@ -304,9 +297,11 @@ bool is_uint_type(const Env& env, int my_idx, RegisterAccess var) { return type == TypeSpec("uint"); } -bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool as_var) { - auto type = as_var ? env.get_variable_type(var, true).base_type() - : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); +bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool) { + // Now that decompiler types are synced up properly, we don't want this. + // auto type = as_var ? env.get_variable_type(var, true).base_type() + // : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); + auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); return type == "pointer"; } @@ -2473,8 +2468,11 @@ void ConditionElement::push_to_stack(const Env& env, FormPool& pool, FormStack& } else { source_types.push_back(TypeSpec("int")); } + } else if (m_src[i]->is_sym_val() && m_src[i]->get_str() == "#f") { + source_types.push_back(TypeSpec("symbol")); } else { - throw std::runtime_error("Unsupported atom in ConditionElement::push_to_stack"); + throw std::runtime_error(fmt::format( + "Unsupported atom in ConditionElement::push_to_stack: {}", m_src[i]->to_string(env))); } } if (m_flipped) { @@ -2524,7 +2522,7 @@ void ConditionElement::update_from_stack(const Env& env, source_types.push_back(TypeSpec("int")); } } else { - throw std::runtime_error("Unsupported atom in ConditionElement::push_to_stack"); + throw std::runtime_error("Unsupported atom in ConditionElement::update_from_stack"); } } if (m_flipped) { diff --git a/decompiler/IR2/BitfieldForms.cpp b/decompiler/IR2/bitfields.cpp similarity index 58% rename from decompiler/IR2/BitfieldForms.cpp rename to decompiler/IR2/bitfields.cpp index 180d84e46f..2c5738a628 100644 --- a/decompiler/IR2/BitfieldForms.cpp +++ b/decompiler/IR2/bitfields.cpp @@ -1,7 +1,11 @@ -#include "Form.h" +#include "bitfields.h" + +#include "decompiler/IR2/Form.h" #include "common/goos/PrettyPrinter.h" #include "common/util/Range.h" #include "common/util/BitUtils.h" +#include "decompiler/util/DecompilerTypeSystem.h" +#include "decompiler/IR2/GenericElementMatcher.h" namespace decompiler { @@ -13,18 +17,34 @@ BitfieldStaticDefElement::BitfieldStaticDefElement(const TypeSpec& type, const std::vector& field_defs) : m_type(type), m_field_defs(field_defs) {} -goos::Object BitfieldStaticDefElement::to_form_internal(const Env&) const { +BitfieldStaticDefElement::BitfieldStaticDefElement( + const TypeSpec& type, + const std::vector& field_defs, + FormPool& pool) + : m_type(type) { + for (auto& x : field_defs) { + m_field_defs.push_back(BitFieldDef::from_constant(x, pool)); + } +} + +goos::Object BitfieldStaticDefElement::to_form_internal(const Env& env) const { std::vector result; result.push_back(pretty_print::to_symbol(fmt::format("new 'static '{}", m_type.print()))); for (auto& def : m_field_defs) { - if (def.is_signed) { - result.push_back( - pretty_print::to_symbol(fmt::format(":{} {}", def.field_name, (s64)def.value))); + auto def_as_atom = form_as_atom(def.value); + if (def_as_atom && def_as_atom->is_int()) { + u64 v = def_as_atom->get_int(); + if (def.is_signed) { + result.push_back(pretty_print::to_symbol(fmt::format(":{} {}", def.field_name, (s64)v))); + } else { + result.push_back(pretty_print::to_symbol(fmt::format(":{} #x{:x}", def.field_name, v))); + } } else { - result.push_back( - pretty_print::to_symbol(fmt::format(":{} #x{:x}", def.field_name, def.value))); + // TODO: will this make ugly massive lines? + result.push_back(pretty_print::to_symbol( + fmt::format(":{} {}", def.field_name, def.value->to_string(env)))); } } @@ -160,12 +180,6 @@ std::optional find_field_from_mask(const TypeSystem& ts, return find_field(ts, type, mask_range->first(), mask_range->size(), {}); } -template -T extract_bitfield(T input, int start_bit, int size) { - int end_bit = start_bit + size; - T left_shifted = input << (64 - end_bit); - return left_shifted >> (64 - size); -} } // namespace /*! @@ -191,8 +205,10 @@ FormElement* BitfieldReadElement::push_step(const BitfieldManip step, auto as_bitfield = dynamic_cast(type); assert(as_bitfield); auto field = find_field(ts, as_bitfield, start_bit, size, is_unsigned); - return pool.alloc_element(m_base, false, - DerefToken::make_field_name(field.name())); + auto result = + pool.alloc_element(m_base, false, DerefToken::make_field_name(field.name())); + result->inline_nested(); + return result; } if (m_steps.size() == 1 && m_steps.at(0).kind == BitfieldManip::Kind::LEFT_SHIFT) { @@ -205,14 +221,18 @@ FormElement* BitfieldReadElement::push_step(const BitfieldManip step, int size = 64 - step.amount; int start_bit = end_bit - size; - assert(start_bit >= 0); + if (start_bit < 0) { + throw std::runtime_error("Bad bitfield start bit"); + } auto type = ts.lookup_type(m_type); auto as_bitfield = dynamic_cast(type); assert(as_bitfield); auto field = find_field(ts, as_bitfield, start_bit, size, is_unsigned); - return pool.alloc_element(m_base, false, - DerefToken::make_field_name(field.name())); + auto result = + pool.alloc_element(m_base, false, DerefToken::make_field_name(field.name())); + result->inline_nested(); + return result; } if (m_steps.empty() && step.kind == BitfieldManip::Kind::LOGAND) { @@ -275,49 +295,155 @@ FormElement* BitfieldReadElement::push_step(const BitfieldManip step, throw std::runtime_error("Unknown state in BitfieldReadElement"); } -std::vector decompile_static_bitfield(const TypeSpec& type, - const TypeSystem& ts, - u64 value) { - u64 touched_bits = 0; - std::vector result; +namespace { +/*! + * Nested on the left, will reverse the order. + */ +std::vector compact_nested_logiors(GenericElement* input, const Env&) { + std::vector result; + GenericElement* next = input; - auto type_info = dynamic_cast(ts.lookup_type(type)); - assert(type_info); - - for (auto& field : type_info->fields()) { - u64 bitfield_value; - bool is_signed = ts.tc(TypeSpec("int"), field.type()) && !ts.tc(TypeSpec("uint"), field.type()); - if (is_signed) { - // signed - s64 signed_value = value; - bitfield_value = extract_bitfield(signed_value, field.offset(), field.size()); - } else { - // unsigned - bitfield_value = extract_bitfield(value, field.offset(), field.size()); - } - - if (bitfield_value != 0) { - BitFieldDef def; - def.value = bitfield_value; - def.field_name = field.name(); - def.is_signed = is_signed; - result.push_back(def); - } - - for (int i = field.offset(); i < field.offset() + field.size(); i++) { - touched_bits |= (u64(1) << i); + while (next) { + assert(next->elts().size() == 2); + result.push_back(next->elts().at(1)); + auto next_next = next->elts().at(0); + next = next_next->try_as_element(); + if (!next || !next->op().is_fixed(FixedOperatorKind::LOGIOR)) { + result.push_back(next_next); + break; } } - u64 untouched_but_set = value & (~touched_bits); - - if (untouched_but_set) { - throw std::runtime_error( - fmt::format("Failed to decompile static bitfield of type {}. Original value is 0x{:x} but " - "we didn't touch", - type.print(), value, untouched_but_set)); - } return result; } +/*! + * If this could be an integer constant, figure out what the value is. + * TODO move this somewhere more general. + */ +std::optional get_goal_integer_constant(Form* in, const Env&) { + auto as_atom = form_as_atom(in); + if (as_atom && as_atom->is_int()) { + return as_atom->get_int(); + } + + // also (shl 32) + auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL), + {Matcher::any(1), Matcher::integer(32)}); + auto mr = match(matcher, in); + if (mr.matched) { + auto arg_as_atom = form_as_atom(mr.maps.forms.at(1)); + if (arg_as_atom && arg_as_atom->is_int()) { + u64 result = arg_as_atom->get_int(); + result <<= 32ull; + return result; + } + } + return {}; +} + +Form* strip_int_or_uint_cast(Form* in) { + auto as_cast = in->try_as_element(); + if (as_cast && (as_cast->type() == TypeSpec("int") || as_cast->type() == TypeSpec("uint"))) { + return as_cast->source(); + } + return in; +} + +std::optional get_bitfield_initial_set(Form* form, + const BitFieldType* type, + const TypeSystem& ts, + const Env&) { + // (shr (shl arg1 59) 44) for example + auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHR), + {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL), + {Matcher::any(0), Matcher::any_integer(1)}), + Matcher::any_integer(2)}); + auto mr = match(matcher, strip_int_or_uint_cast(form)); + if (mr.matched) { + auto value = mr.maps.forms.at(0); + int left = mr.maps.ints.at(1); + int right = mr.maps.ints.at(2); + int size = 64 - left; + int offset = left - right; + auto& f = find_field(ts, type, offset, size, {}); + BitFieldDef def; + def.value = value; + def.field_name = f.name(); + def.is_signed = false; // we don't know. + return def; + } + + return {}; +} + +} // namespace + +BitFieldDef BitFieldDef::from_constant(const BitFieldConstantDef& constant, FormPool& pool) { + BitFieldDef bfd; + bfd.field_name = constant.field_name; + bfd.is_signed = constant.is_signed; + bfd.value = pool.alloc_single_element_form( + nullptr, SimpleAtom::make_int_constant(constant.value)); + return bfd; +} + +/*! + * Cast the given form to a bitfield. + * If the form could have been a (new 'static 'bitfieldtype ...) it will attempt to generate this. + */ +Form* cast_to_bitfield(const BitFieldType* type_info, + const TypeSpec& typespec, + FormPool& pool, + const Env& env, + Form* in) { + in = strip_int_or_uint_cast(in); + // check if it's just a constant: + auto in_as_atom = form_as_atom(in); + if (in_as_atom) { + auto fields = decompile_bitfield_from_int(typespec, env.dts->ts, in_as_atom->get_int()); + return pool.alloc_single_element_form(nullptr, typespec, fields, + pool); + } + + auto in_as_generic = strip_int_or_uint_cast(in)->try_as_element(); + std::vector args; + if (in_as_generic && in_as_generic->op().is_fixed(FixedOperatorKind::LOGIOR)) { + args = compact_nested_logiors(in_as_generic, env); + } else { + args = {strip_int_or_uint_cast(in)}; + } + + if (!args.empty()) { + std::vector field_defs; + + for (auto it = args.begin(); it != args.end(); it++) { + auto constant = get_goal_integer_constant(*it, env); + if (constant) { + auto constant_defs = decompile_bitfield_from_int(typespec, env.dts->ts, *constant); + for (auto& x : constant_defs) { + field_defs.push_back(BitFieldDef::from_constant(x, pool)); + } + + args.erase(it); + break; + } + } + + // now variables + for (auto& arg : args) { + auto maybe_field = get_bitfield_initial_set(arg, type_info, env.dts->ts, env); + if (!maybe_field) { + // failed, just return cast. + return pool.alloc_single_element_form(nullptr, typespec, in); + } + field_defs.push_back(*maybe_field); + } + return pool.alloc_single_element_form(nullptr, typespec, field_defs); + } + + // all failed, just return whatever. + return pool.alloc_single_element_form(nullptr, typespec, in); +} + } // namespace decompiler \ No newline at end of file diff --git a/decompiler/IR2/bitfields.h b/decompiler/IR2/bitfields.h new file mode 100644 index 0000000000..58574c8f13 --- /dev/null +++ b/decompiler/IR2/bitfields.h @@ -0,0 +1,134 @@ +#pragma once + +#include + +#include "common/common_types.h" +#include "decompiler/IR2/Form.h" +#include "decompiler/util/data_decompile.h" + +namespace decompiler { +struct BitfieldManip { + enum class Kind { + LEFT_SHIFT, + RIGHT_SHIFT_LOGICAL, + RIGHT_SHIFT_LOGICAL_32BIT, + RIGHT_SHIFT_ARITH, + LOGAND, + LOGIOR_WITH_CONSTANT_INT, + NONZERO_COMPARE, + INVALID + } kind = Kind::INVALID; + s64 amount = -1; + + bool is_right_shift() const { + return kind == Kind::RIGHT_SHIFT_ARITH || kind == Kind::RIGHT_SHIFT_LOGICAL || + kind == Kind::RIGHT_SHIFT_LOGICAL_32BIT; + } + + bool right_shift_unsigned() const { + assert(is_right_shift()); + return kind == Kind::RIGHT_SHIFT_LOGICAL || kind == Kind::RIGHT_SHIFT_LOGICAL_32BIT; + } + + bool is_64bit_shift() const { + return kind == Kind::RIGHT_SHIFT_LOGICAL || kind == Kind::RIGHT_SHIFT_ARITH || + kind == Kind::LEFT_SHIFT; + } + + int get_shift_start_bit() const { + if (is_64bit_shift()) { + return 64; + } else { + return 32; + } + } + + BitfieldManip(Kind k, s64 imm) : kind(k), amount(imm) {} +}; + +class BitfieldReadElement : public FormElement { + public: + BitfieldReadElement(Form* base_value, const TypeSpec& ts); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + FormElement* push_step(const BitfieldManip step, const TypeSystem& ts, FormPool& pool); + + private: + Form* m_base = nullptr; + TypeSpec m_type; + std::vector m_steps; +}; + +struct BitFieldDef { + bool is_signed = false; + Form* value = nullptr; + std::string field_name; + + static BitFieldDef from_constant(const BitFieldConstantDef& constant, FormPool& pool); +}; + +class BitfieldStaticDefElement : public FormElement { + public: + BitfieldStaticDefElement(const TypeSpec& type, const std::vector& field_defs); + BitfieldStaticDefElement(const TypeSpec& type, + const std::vector& field_defs, + FormPool& pool); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + void update_from_stack(const Env&, + FormPool&, + FormStack&, + std::vector* result, + bool) override { + mark_popped(); + result->push_back(this); + } + + private: + TypeSpec m_type; + std::vector m_field_defs; +}; + +struct BitfieldFormDef { + Form* value; + std::string field_name; +}; + +/*! + * This represents copying a bitfield object, then modifying the type. + * It's an intermediate step to modifying a bitfield in place and it's not expected to appear + * in the final output. + */ +class ModifiedCopyBitfieldElement : public FormElement { + public: + ModifiedCopyBitfieldElement(const TypeSpec& type, + Form* base, + const std::vector& field_modifications); + goos::Object to_form_internal(const Env& env) const override; + void apply(const std::function& f) override; + void apply_form(const std::function& f) override; + void collect_vars(RegAccessSet& vars, bool recursive) const override; + void get_modified_regs(RegSet& regs) const override; + + Form* base() const { return m_base; } + const std::vector mods() const { return m_field_modifications; } + + private: + TypeSpec m_type; + Form* m_base = nullptr; + std::vector m_field_modifications; +}; + +Form* cast_to_bitfield(const BitFieldType* type_info, + const TypeSpec& typespec, + FormPool& pool, + const Env& env, + Form* in); + +} // namespace decompiler diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 5db033d503..1fe487f4bc 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -55,7 +55,7 @@ (define-extern *listener-function* (function object)) (define-extern *enable-method-set* int) (define-extern install-debug-handler (function int object symbol)) -(define-extern install-handler (function int (function int) none)) +(define-extern install-handler (function int (function int) int)) ;; GOAL thinks it returns something. (define-extern file-stream-open (function file-stream basic basic file-stream)) (define-extern file-stream-length (function file-stream int)) @@ -2351,7 +2351,7 @@ ; ;; dma-h (deftype dma-bucket (structure) ((tag dma-tag :offset-assert 0) ;; the DMA tag to transfer the bucket's data - (last (pointer uint64) :offset-assert 8) ;; the last tag of this bucket. + (last (pointer dma-tag) :offset-assert 8) ;; the last tag of this bucket. (dummy uint32 :offset-assert 12) ;; empty space. (next uint32 :offset 4) ;; this overlaps with the addr bit-field of the dma-tag ) @@ -3433,7 +3433,7 @@ (define-extern set-display-env (function display-env int int int int int int display-env)) (define-extern set-draw-env (function draw-env int int int int int int draw-env)) (define-extern set-draw-env-offset (function draw-env int int int draw-env)) -(define-extern put-display-alpha-env (function draw-env none)) +(define-extern put-display-alpha-env (function display-env none)) (define-extern set-display (function display int int int int int display)) (define-extern set-display2 (function display int int int int int display)) (define-extern allocate-dma-buffers (function display display)) @@ -4265,7 +4265,7 @@ :size-assert #x58 :flag-assert #x1400000058 (:methods - (new (symbol type int int int float int int) _type_ 0) + (new (symbol type matrix int int float int int) _type_ 0) (set-mat! (font-context matrix) font-context 9) (set-origin! (font-context int int) font-context 10) (set-depth! (font-context int) font-context 11) diff --git a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc index 558a815dfe..6c4f8130b7 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -112,6 +112,8 @@ "dma": [["L56", "uint64", true]], + "dma-bucket": [["L10", "uint64", true]], + "video-h": [["L1", "video-parms", true]], "pad": [ @@ -229,7 +231,8 @@ ["L55", "uint64", true], ["L56", "uint64", true], ["L57", "uint64", true], - ["L58", "uint64", true] + ["L58", "uint64", true], + ["L80", "rgba", true] ], "text-h": [["L2", "_auto_", true]], diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 136b63fb68..cc2538bcdc 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -145,6 +145,31 @@ [[0, 54], "s3", "dma-bank-spr"] ], + // dma-buffer + "dma-buffer-add-vu-function": [ + [[9, 33], "t2", "dma-packet"] + ], + + // dma-bucket + "dma-buffer-add-buckets":[ + [[1, 4], "v1", "dma-bucket"], + [5, "v1", "pointer"], + [[9, 11], "v1", "dma-bucket"], + [11, "v1", "pointer"] + //[[6, 15], "v1", "dma-bucket"] + ], + + "dma-buffer-patch-buckets": [ + [3, "a0", "dma-bucket"], + [[3, 5], "a2", "dma-bucket"], + [7, "a0", "pointer"], + [11, "a0", "dma-bucket"], + //[12, "a2", "uint"], + [13, "a0", "dma-bucket"], + [14, "a0", "pointer"], + [[15, 17], "a0", "dma-bucket"] + ], + // LEVEL "lookup-level-info": [ [3, "a1", "symbol"], diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index eeafc6e322..6a6bf4fb26 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -663,13 +663,13 @@ "a3-0": "qlen", "a1-1": "origin", "t0-1": "qwc-now", - "t2-0": "buf-ptr" + "t2-0": ["buf-ptr", "dma-packet"] } }, "dma-buffer-add-buckets": { "args": ["dma-buf", "count"], - "vars": { "a2-0": "i", "v1-0": "current-bucket" } + "vars": { "a2-0": "i", "v1-0": ["current-bucket", "dma-bucket"] } }, "dma-buffer-patch-buckets": { @@ -761,13 +761,21 @@ "(method 9 display)": { "args": ["obj", "delta-seconds"], - "vars": { "gp-0": "obj", "s5-0":"delta" } + "vars": { "gp-0": "obj", "s5-0": "delta" } }, "set-draw-env-offset": { "args": ["env", "x", "y"] }, + "set-display-env": { + "args": ["env", "psm", "width", "height", "dx", "dy", "fbp"] + }, + + "set-draw-env": { + "args": ["env", "psm", "width", "height", "ztest", "zpsm", "fbp"] + }, + "set-display": { "args": ["display", "psm", "w", "h", "ztest", "zpsm"] }, @@ -778,12 +786,12 @@ "(method 11 profile-bar)": { "args": ["obj", "name", "color"], - "vars": { "s5-0":"new-frame" } + "vars": { "s5-0": "new-frame" } }, "(method 12 profile-bar)": { "args": ["obj", "name", "color"], - "vars": { "v0-0":"new-frame" } + "vars": { "v0-0": "new-frame" } }, "gs-set-default-store-image": { @@ -791,8 +799,16 @@ }, "(method 0 draw-context)": { - "args": ["allocation", "type-to-make", "org-x", "org-y", "width", "height", "color-0"], - "vars": { "v0-0": "obj"} + "args": [ + "allocation", + "type-to-make", + "org-x", + "org-y", + "width", + "height", + "color-0" + ], + "vars": { "v0-0": "obj" } }, "draw-context-set-xy": { diff --git a/decompiler/util/data_decompile.cpp b/decompiler/util/data_decompile.cpp index 18d8402035..5614ab6bf3 100644 --- a/decompiler/util/data_decompile.cpp +++ b/decompiler/util/data_decompile.cpp @@ -761,4 +761,92 @@ goos::Object decompile_pair(const DecompilerLabel& label, } } +goos::Object decompile_bitfield(const TypeSpec& type, + const BitFieldType* type_info, + const DecompilerLabel& label, + const std::vector&, + const std::vector>& words, + const TypeSystem& ts) { + // read memory + int start_byte = label.offset; + int end_byte = start_byte + type_info->get_size_in_memory(); + std::vector elt_bytes; + for (int j = start_byte; j < end_byte; j++) { + auto& word = words.at(label.target_segment).at(j / 4); + if (word.kind != LinkedWord::PLAIN_DATA) { + throw std::runtime_error("Got bad word in static bitfield"); + } + elt_bytes.push_back(word.get_byte(j % 4)); + } + + // pad bytes array to 64-bits: + while (elt_bytes.size() < 8) { + elt_bytes.push_back(0); + } + assert(elt_bytes.size() == 8); + + // read as u64 + u64 value = *(u64*)(elt_bytes.data()); + auto defs = decompile_bitfield_from_int(type, ts, value); + + std::vector result; + result.push_back(pretty_print::to_symbol(fmt::format("new 'static '{}", type.print()))); + for (auto& def : defs) { + if (def.is_signed) { + result.push_back( + pretty_print::to_symbol(fmt::format(":{} {}", def.field_name, (s64)def.value))); + } else { + result.push_back( + pretty_print::to_symbol(fmt::format(":{} #x{:x}", def.field_name, def.value))); + } + } + + return pretty_print::build_list(result); +} + +std::vector decompile_bitfield_from_int(const TypeSpec& type, + const TypeSystem& ts, + u64 value) { + u64 touched_bits = 0; + std::vector result; + + auto type_info = dynamic_cast(ts.lookup_type(type)); + assert(type_info); + + for (auto& field : type_info->fields()) { + u64 bitfield_value; + bool is_signed = ts.tc(TypeSpec("int"), field.type()) && !ts.tc(TypeSpec("uint"), field.type()); + if (is_signed) { + // signed + s64 signed_value = value; + bitfield_value = extract_bitfield(signed_value, field.offset(), field.size()); + } else { + // unsigned + bitfield_value = extract_bitfield(value, field.offset(), field.size()); + } + + if (bitfield_value != 0) { + BitFieldConstantDef def; + def.value = bitfield_value; + def.field_name = field.name(); + def.is_signed = is_signed; + result.push_back(def); + } + + for (int i = field.offset(); i < field.offset() + field.size(); i++) { + touched_bits |= (u64(1) << i); + } + } + + u64 untouched_but_set = value & (~touched_bits); + + if (untouched_but_set) { + throw std::runtime_error( + fmt::format("Failed to decompile static bitfield of type {}. Original value is 0x{:x} but " + "we didn't touch", + type.print(), value, untouched_but_set)); + } + return result; +} + } // namespace decompiler \ No newline at end of file diff --git a/decompiler/util/data_decompile.h b/decompiler/util/data_decompile.h index b7942631a7..f3c25657fb 100644 --- a/decompiler/util/data_decompile.h +++ b/decompiler/util/data_decompile.h @@ -52,4 +52,28 @@ goos::Object decompile_value_array(const TypeSpec& elt_type, int offset, const std::vector& obj_words, const TypeSystem& ts); +goos::Object decompile_bitfield(const TypeSpec& type, + const BitFieldType* type_info, + const DecompilerLabel& label, + const std::vector& labels, + const std::vector>& words, + const TypeSystem& ts); + +struct BitFieldConstantDef { + bool is_signed = false; + u64 value = -1; + std::string field_name; +}; + +template +T extract_bitfield(T input, int start_bit, int size) { + int end_bit = start_bit + size; + T left_shifted = input << (64 - end_bit); + return left_shifted >> (64 - size); +} + +std::vector decompile_bitfield_from_int(const TypeSpec& type, + const TypeSystem& ts, + u64 value); + } // namespace decompiler diff --git a/goal_src/engine/dma/dma-bucket.gc b/goal_src/engine/dma/dma-bucket.gc index f690dc579d..23dbf61e9d 100644 --- a/goal_src/engine/dma/dma-bucket.gc +++ b/goal_src/engine/dma/dma-bucket.gc @@ -5,6 +5,10 @@ ;; name in dgo: dma-bucket ;; dgos: GAME, ENGINE +;; A dma-bucket is used to organize dma data. +;; When an object is drawn, it may add data to multiple buckets. +;; When the dma data is transferred, it is transferred bucket by bucket. + ;; A dma-bucket is a 16 byte thing that lives in the dma-buffer. ;; buckets live consecutively in the dma-buffer, and can mark the start of a DMA chain ;; location anywhere. @@ -12,7 +16,7 @@ ;; The typical process is: ;; - empty buckets are allocated with add-buckets ;; - tags are put somewhere and added to the appropriate bucket with insert-tag, updating last as needed. -;; - buckets are patched to link to each other with dma-buffer-add-buckets. +;; - buckets are patched to link to each other with dma-buffer-patch-buckets. ;; the idea here is that you can build the buckets in whatever order you want, but the buckets ;; will be DMAd in the bucket allocation order. @@ -22,32 +26,31 @@ ;; last, a pointer to the last tag of this bucket, so that the bucket can be patched to point to the next. (defun dma-buffer-add-buckets ((dma-buf dma-buffer) (count int)) - "Add count buckets to the dma buffer. Each bucket is initialized empty." - (local-vars (current-bucket dma-bucket) (i int)) - ;; grab the first free memory - (set! current-bucket (the dma-bucket (-> dma-buf base))) - (set! i 0) - (while (< i count) - ;; also sets next, which points to the memory after this bucket - (set! (-> current-bucket tag) - (the dma-tag (logior - #x20000000 - (the-as int (shr (shl (+ (the-as uint current-bucket) 16) 33) 1)) - ) - ) - ) - - ;; nothing in the bucket chain, so our last tag is the bucket tag. - (set! (-> current-bucket last) (the (pointer uint64) current-bucket)) - ;; advance to next bucket - (set! current-bucket (&+ current-bucket 16)) - (set! i (+ i 1)) + "Add count buckets. Each bucket is initialized as empty and won't transfer anything." + (let ((current-bucket (the-as dma-bucket (-> dma-buf base)))) + (dotimes (i count) + ;; set the DMA tag to next, with a qwc of zero. + ;; the address is set to the next bucket. + ;; By default, this will do no transfer and just move on in the dma-buf. + ;; Data will be added to the bucket later. + (set! (-> current-bucket tag) + (new 'static 'dma-tag + :id (dma-tag-id next) + :addr (the-as int (&+ (the-as pointer current-bucket) 16)) + ) + ) + ;; Set the last pointer to point to this tag (this lives in the 8 byte gap) + (set! (-> current-bucket last) (the-as (pointer dma-tag) current-bucket)) + ;; Advance to next bucket. + (&+! current-bucket 16) + ) + ;; update base ptr of dma-buffer to point after the buckets. + (set! (-> dma-buf base) (the-as pointer current-bucket)) ) - ;; update base ptr. - (set! (-> dma-buf base) (the pointer current-bucket)) (none) ) + (defun dma-buffer-patch-buckets ((bucket dma-bucket) (count int)) "Patch last pointers in a sequence of buckets. Call this after you have added everything to buckets." @@ -62,7 +65,7 @@ ) ) ;; clear last, and move on to the next bucket. - (set! (-> bucket last) (the (pointer uint64) 0)) + (set! (-> bucket last) (the (pointer dma-tag) 0)) (set! bucket (&+ bucket 16)) (set! i (+ i 1)) ) @@ -78,6 +81,6 @@ ;; append to last tag (kind of a hack here with the types) (set! (-> (the dma-bucket (-> bucket last)) next) (the uint tag-start)) ;; make prev = end of tag so we can add more. - (set! (-> bucket last) tag-end) + (set! (-> bucket last) (the (pointer dma-tag) tag-end)) tag-start ) diff --git a/goal_src/engine/dma/dma-buffer.gc b/goal_src/engine/dma/dma-buffer.gc index 76ba1a06f3..a84c66a76d 100644 --- a/goal_src/engine/dma/dma-buffer.gc +++ b/goal_src/engine/dma/dma-buffer.gc @@ -5,11 +5,16 @@ ;; name in dgo: dma-buffer ;; dgos: GAME, ENGINE -;; DMA buffers are used per frame to store DMA data. -;; The dma-buffer manages the memory and dma-bucket organizes the stuff within. +;; DMA buffers store data to be sent over DMA. +;; They are a very simple wrapper around the data. +;; Typically a dma-buffer will store dma-buckets or other more complicated data structures. -;; The DMA system reads dma-tags. A common trick is to set the dma in tte mode, which transfers -;; the upper 64-bits of the 128-bit "packet" to the VIF, and you can put your vifcode there: +;; The main display list uses a "chain transfer". In this mode, the DMA system reads dma-tags which +;; tell it what to transfer next. This can be used to construct linked lists of DMA data. + +;; If the DMA is configured correctly, it is possible to make the first quadword contain +;; both a dma-tag and a tag for the peripheral. This allows you to have a single quadword +;; tag that controls both the DMA and peripheral. We call these a "dma-packet" for some reason. ;; Ex: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -20,7 +25,9 @@ ;; note that the second vifcode can also hold other stuff depending on the mode. -;; first quadword of a DMA with vif packet +;; A dma-tag plus two vif-tags in a single quadword. +;; Most DMA stuff goes directly to the VIF, so this is the +;; most common. (deftype dma-packet (structure) ((dma dma-tag :offset-assert 0) (vif0 vif-tag :offset-assert 8) @@ -32,7 +39,7 @@ :flag-assert #x900000010 ) -;; seems to be unused? +;; seems to be unused? Also, it seems to be broken. Do not use this. (deftype dma-packet-array (inline-array-class) () :method-count-assert 9 @@ -52,16 +59,16 @@ :flag-assert #x900000020 ) -;; dma-buffer holds per-frame data for dma. -;; the allocated-length is in bytes. -;; the base points to the first unused memory in the buffer -;; it is a dynamically sized type. +;; dma-buffer is a dynamically sized container for storing DMA data. +;; It seems like this was not actually implemented as a proper dynamic type. +;; I added a data-buffer field that overlaps with data to get at the array of +;; bytes more easily. (deftype dma-buffer (basic) - ((allocated-length int32 :offset-assert 4) - (base pointer :offset-assert 8) - (end pointer :offset-assert 12) - (data uint64 1 :offset-assert 16) ;; weird, I guess this aligns the data? - (data-buffer uint8 :dynamic :offset 16) + ((allocated-length int32 :offset-assert 4) ;; number of bytes. + (base pointer :offset-assert 8) ;; first unused memory. + (end pointer :offset-assert 12) ;; ?? unused ?? + (data uint64 1 :offset-assert 16) ;; start of memory. + (data-buffer uint8 :dynamic :offset 16) ;; the actual dynamic array backing it. ) (:methods (new (symbol type int) _type_ 0) @@ -71,29 +78,29 @@ :flag-assert #x900000018 ) -(defmethod new dma-buffer ((allocation symbol) (type-to-make type) (size int)) - "Allocate a new dma-buffer with size bytes of space" - (local-vars (v0-0 dma-buffer)) - ;; we really get size + 4 bytes I think... - (set! v0-0 - (object-new allocation type-to-make - (+ (+ size -4) (the-as int (-> type-to-make size))) +(defmethod new dma-buffer ((allocation symbol) (type-to-make type) (arg0 int)) + "Create a new dma-buffer with enough room to store arg0 bytes. + Note that this does not set the end field." + (let ((v0-0 (object-new allocation type-to-make + (+ (+ arg0 -4) (the-as int (-> type-to-make size))) + ) + ) + ) + (set! (-> v0-0 base) (-> v0-0 data)) + (set! (-> v0-0 allocated-length) arg0) + v0-0 ) - ) - (set! (-> v0-0 base) (-> v0-0 data)) - (set! (-> v0-0 allocated-length) size) - v0-0 ) (defun dma-buffer-inplace-new ((obj dma-buffer) (size int)) - "Initialize a DMA buffer in place. Doesn't set the type of the memory" + "Create a dma-buffer in-place. Does not set the type of the dma-buffer object." (set! (-> obj base) (-> obj data)) (set! (-> obj allocated-length) size) obj ) (defmethod length dma-buffer ((obj dma-buffer)) - "Get the size of the buffer" + "Get the amount of data the buffer can hold, in bytes." (-> obj allocated-length) ) @@ -108,80 +115,53 @@ ) (defun dma-buffer-free ((arg0 dma-buffer)) - "Get the number of free quadwords, rounded down." - (the-as int - (shr (+ (&- (-> arg0 end) (-> arg0 base)) 15) 4) + "Get the number of free quadwords, rounded down, between base and end pointers." + (shr (+ (&- (-> arg0 end) (-> arg0 base)) 15) 4) + ) + +(defun dma-buffer-add-vu-function ((dma-buf dma-buffer) (vu-func vu-function) (flush-path-3 int)) + "Add DMA tags to load the given VU function. The destination in vu instruction memory + is specific inside the vu-function. This does NOT copy the vu-function into the buffer, + but creates a reference to the existing VU function." + + ;; The first 4 bytes of a vu-function object's data are discarded because they aren't aligned. + (let ((func-ptr (the-as pointer (&-> vu-func data 4))) + (qlen (-> vu-func qlength)) ;; number of quadwords + (origin (-> vu-func origin)) ;; destination address in VU instruction memory. + ) + ;; loop until whole program is transferred. + (while (> qlen 0) + ;; transfer up to 127 quadwords at a single time. + (let ((qwc-now (min 127 qlen))) + ;; grab the next dma-packet: + (let* ((dma-buf-2 dma-buf) + (buf-ptr (the-as dma-packet (-> dma-buf-2 base))) + ) + ;; Set up DMA to transfer the data from the vu-function + ;; ref id = reference to data outside of the buffer. + (set! (-> buf-ptr dma) + (new 'static 'dma-tag :id (dma-tag-id ref) :addr (the-as int func-ptr) :qwc qwc-now) + ) + ;; Set up first vifcode as a flush. + (set! (-> buf-ptr vif0) + (new 'static 'vif-tag :cmd (if (zero? flush-path-3) (vif-cmd flushe) (vif-cmd flusha))) + ) + ;; next vifcode, transfer microprogram. This is in 64-bit units (VU instructions) + (set! (-> buf-ptr vif1) + (new 'static 'vif-tag :cmd (vif-cmd mpg) :num (shl qwc-now 1) :imm origin) + ) + (set! (-> dma-buf-2 base) (&+ (the-as pointer buf-ptr) 16)) ) + ;; increment by qwc-now quadwords. + (&+! func-ptr (shl qwc-now 4)) + (set! qlen (- qlen qwc-now)) + (+! origin (shl qwc-now 1)) + ) + ) + ) + #f ) -(defun dma-buffer-add-vu-function ((dma-buf dma-buffer) (vu-func vu-function) (arg2 int)) - "Add DMA chain instructions to load the given VU function to VU program memory. - The destination address in VU memory is specified inside of the vu-function. - if arg2 = 0, then use FLUSHE, otherwise use FLUSHA, I think it runs _before_ the program upload. - Should run with TTE." - (local-vars - (func-ptr uint) - (origin int) - (qlen int) - (qwc-now int) - (dma-buf-2 dma-buffer) - (buf-ptr pointer) - ) - ;; our strategy is to upload the program in chunks, as we can only upload 127 or 128 quadwords at a time. - ;; it is not clear why they did 127 quadword chunks, it seems like 128 would have worked too. - - ;; the actual data to upload. - (set! func-ptr (the uint (&-> vu-func data 4))) - ;; how long it is - (set! qlen (-> vu-func qlength)) - ;; where to upload to. In 64-bit units because the PS2 is annoying. - (set! origin (-> vu-func origin)) - - ;; while we still have stuff to transfer... - (while (> qlen 0) - ;; get up to 127 quadwords. maybe they thought they lost one for the dmatag + 2 vif tags? - (set! qwc-now (min 127 qlen)) - (set! dma-buf-2 dma-buf) - ;; grab a pointer to the next free memory - (set! buf-ptr (-> dma-buf-2 base)) - - ;; We will add the following tags - ;; | 32-bit MPG VIFCode | 32-bit FLUSHX VIFCode | 64-bit DMATag | - ;; DMA Tag will the REF - indicating to transfer some other data (the program), then advance to - ;; the next quadword in this buffer for the next steps. - ;; FLUSHX makes sure the VU isn't running while we upload a program. - ;; MPG tells VIF to upload the program to the VU. - - ;; put in a DMA Tag, REF - (set! (-> (the-as (pointer int64) buf-ptr)) - (logior (logior #x30000000 ;; tag id = ref - (shr (shl qwc-now 48) 48)) ;; qwc = qwc-now - (the-as int (shr (shl (the-as uint func-ptr) 33) 1)) ;; addr = func-ptr - ) - ) - ;; VIFCODE - FLUSHE or FLUSHA, wait for previous program to finish? - (set! (-> (the-as (pointer int32) buf-ptr) 2) - (shr (shl (if (zero? arg2) 16 19) 57) 33) - ) - ;; MPG - Transfers the microgram! - ;; sets num to 0, which means 256x 64 bit units, or 128 quadwords. - ;; I think this transfers one quadword too many, but it's okay. - (set! (-> (the-as (pointer int32) buf-ptr) 3) - (logior (logior #x4a000000 (shr (shl origin 48) 48)) - (shr (shl (shl qwc-now 1) 56) 40) - ) - ) - ;; advance buffer pointer to next quadword. - (set! (-> dma-buf-2 base) (&-> (the (pointer uint32) buf-ptr) 4)) - ;; advance data pointer - (set! func-ptr (+ func-ptr (the-as uint (shl qwc-now 4)))) - ;; count down transfered quads - (set! qlen (- qlen qwc-now)) - ;; source pointer advances by doublewords... - (set! origin (+ origin (shl qwc-now 1))) - ) - '#f - ) (defun dma-buffer-send ((chan dma-bank) (buf dma-buffer)) "Send the DMA buffer! DOES NOT TRANSFER TAG." @@ -191,8 +171,7 @@ ;; oops. we overflowed the DMA buffer. die. (segfault) ) - (dma-send - chan + (dma-send chan (the-as uint (-> buf data)) (the-as uint (dma-buffer-length buf)) ) @@ -206,8 +185,7 @@ ;; oops. we overflowed the DMA buffer. die. (segfault) ) - (dma-send-chain - chan + (dma-send-chain chan (the-as uint (-> buf data)) ) ) diff --git a/goal_src/engine/dma/dma-h.gc b/goal_src/engine/dma/dma-h.gc index ea9fff6c31..3055a0e3fc 100644 --- a/goal_src/engine/dma/dma-h.gc +++ b/goal_src/engine/dma/dma-h.gc @@ -185,13 +185,25 @@ :flag-assert #x900000008 ) +(defenum dma-tag-id + :bitfield #f + (refe 0) ;; addr=ADDR, ends after this transfer + (cnt 1) ;; addr=after tag, next-tag=after data + (next 2) ;; addr=after tag, next-tag=ADDR + (ref 3) ;; addr=ADDR, next-tag=after tag + (refs 4) ;; ref, but stall controled + (call 5) ;; + (ret 6) ;; + (end 7) ;; next, but ends. + ) + ;; A DMA bucket is a way of organizing data within a dma buffer. ;; The buckets themselves live inside in the dma buffer. ;; the addr field of their tag should point to the next bucket. ;; This is not a PS2 hardware thing (deftype dma-bucket (structure) ((tag dma-tag :offset-assert 0) ;; the DMA tag to transfer the bucket's data - (last (pointer uint64) :offset-assert 8) ;; the last tag of this bucket. + (last (pointer dma-tag) :offset-assert 8) ;; the last tag of this bucket. (dummy uint32 :offset-assert 12) ;; empty space. (next uint32 :offset 4) ;; this overlaps with the addr bit-field of the dma-tag ) @@ -239,6 +251,31 @@ :flag-assert #x900000002 ) + +(defenum vif-cmd + :bitfield #f + (nop 0) ;; no-op, can still have irq set. + (stcycl 1) ;; set write recycle register + (offset 2) ;; set offset register + (base 3) ;; set base register + (itop 4) ;; set data pointer register (itops) + (stmod 5) ;; set mode register + (mskpath3 6) ;; set path 3 mask + (mark 7) ;; set mark register + (flushe 16) ;; wait for end of microprogram + (flush 17) ;; wait for end of microprogram and transfer (path1/path2) + (flusha 19) ;; wait for end of microprogram and transfer (path1/path2/path3) + (mscal 20) ;; activate microprogram (call) + (mscalf 21) ;; flushe and activate (call) + (mscnt 23) ;; activate microprogram (continue) + (stmask 32) ;; set MASK register. + (strow 48) ;; set filling data + (strow 49) ;; set filling data + (mpg 74) ;; transfer microprogram + ;; there's more... + ) + + ;; The VIF also has tags to control it. ;; Different VIF commands (called VIFcode) have different tag layouts. (deftype vif-tag (uint32) diff --git a/goal_src/engine/dma/dma.gc b/goal_src/engine/dma/dma.gc index cc3c4d5289..e4350ae3a3 100644 --- a/goal_src/engine/dma/dma.gc +++ b/goal_src/engine/dma/dma.gc @@ -324,59 +324,64 @@ ) (defun ultimate-memcpy ((dst pointer) (src pointer) (size-bytes uint)) - "Fast memcpy using the scratchpad. Will blow away everything in scratch. - Rounds to nearest quadword for size, dst and source should be aligned 16-bytes." - (local-vars - (qwc-transferred-now uint) - (qwc-remaining uint) - (spr-from-bank dma-bank-spr) - (spr-to-bank dma-bank-spr) - ) - (set! spr-to-bank (the dma-bank-spr #x1000d400)) - (set! spr-from-bank (the dma-bank-spr #x1000d000)) - (set! qwc-remaining (shr size-bytes 4)) - (flush-cache 0) - (dma-sync (the-as pointer spr-to-bank) 0 0) - (dma-sync (the-as pointer spr-from-bank) 0 0) - (while (> qwc-remaining 0) - ;; figure out how much to transfer, limited by 1024 quadword size of spad. - (set! qwc-transferred-now qwc-remaining) - (when (< (the-as uint 1024) qwc-transferred-now) - (set! qwc-transferred-now (the uint 1024)) - ) - (set! qwc-remaining (- qwc-remaining (the-as uint qwc-transferred-now))) - - (.sync.l) - ;; set up dma - (set! (-> spr-to-bank madr) (the uint src)) - (set! (-> spr-to-bank sadr) 0) - (set! (-> spr-to-bank qwc) qwc-transferred-now) - (.sync.l) - ;; start dma - (set! (-> spr-to-bank chcr) (new 'static 'dma-chcr :str 1)) - (.sync.l) - ;; sync + "The Fastest Memory Copy, for larger transfers. + Memory is copied in ascending order, in 4 kB blocks. + The size should be a multiple of 16 bytes." + + ;; ultimate-memcpy works by DMAing to the scratchpad and back. + ;; surprisingly this seems to be the fastest memcpy on larger + ;; transfers. This is a nice example of how DMA is used from GOAL. + (let ((spr-to-bank (the-as dma-bank-spr #x1000d400)) + (spr-from-bank (the-as dma-bank-spr #x1000d000)) + (qwc-remaining (shr size-bytes 4)) + ) + + ;; Flush all data in the dcache to main memory. DMA bypasses the dcache. + (flush-cache 0) + ;; Complete all pending DMA transfers using the spad. + ;; (this uses the Sony library DMA sync, which is bad) (dma-sync (the-as pointer spr-to-bank) 0 0) - - ;; advance source pointer. - (set! src (&+ src (the-as uint (shl qwc-transferred-now 4)))) - - ;; setup transfer back - (set! (-> spr-from-bank madr) (the uint dst)) - (set! (-> spr-from-bank sadr) 0) - (set! (-> spr-from-bank qwc) qwc-transferred-now) - ;; transfer! - (.sync.l) - (set! (-> spr-from-bank chcr) (new 'static 'dma-chcr :str 1)) - (.sync.l) - ;; sync! (dma-sync (the-as pointer spr-from-bank) 0 0) - ;; advance dst. - (set! dst (&+ dst (the-as uint (shl qwc-transferred-now 4)))) + + ;; transfer loop + (while (> qwc-remaining 0) + ;; copy up to 1024 quadwords - limited by the 4kB spad size. + (let ((qwc-transferred-now (the-as int qwc-remaining))) + (if (< (the-as uint 1024) (the-as uint qwc-transferred-now)) + (set! qwc-transferred-now 1024) + ) + (set! qwc-remaining (- qwc-remaining (the-as uint qwc-transferred-now))) + ;; set up the "to spad" transfer + (.sync.l) + (set! (-> spr-to-bank madr) (the-as uint src)) + (set! (-> spr-to-bank sadr) (the-as uint 0)) + (set! (-> spr-to-bank qwc) (the-as uint qwc-transferred-now)) + (.sync.l) + ;; activate! + (set! (-> spr-to-bank chcr) (new 'static 'dma-chcr :str #x1)) + (.sync.l) + ;; wait for it to finish... + (dma-sync (the-as pointer spr-to-bank) 0 0) + (&+! src (shl qwc-transferred-now 4)) + ;; and copy back... + (set! (-> spr-from-bank madr) (the-as uint dst)) + (set! (-> spr-from-bank sadr) (the-as uint 0)) + (set! (-> spr-from-bank qwc) (the-as uint qwc-transferred-now)) + (.sync.l) + (set! (-> spr-from-bank chcr) (new 'static 'dma-chcr :str #x1)) + (.sync.l) + (dma-sync (the-as pointer spr-from-bank) 0 0) + (&+! dst (shl qwc-transferred-now 4)) + ) + ) + ) + (let ((v0-4 0)) ) (none) ) + + (defun symlink2 () "symlink2 is a handwritten assembly version of the v2 linking routine. it is not ported because the OpenGOAL linker has its own implementation already." @@ -392,6 +397,6 @@ (none) ) -;; configuration required to work around hardware bug. +;; configuration required to work around hardware bug on the PS2. ;; doesn't do anything important (dma-initialize) diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index a47978c045..8f880067fe 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -126,7 +126,7 @@ (declare-type cpad-info structure) (define-extern cpad-open (function cpad-info int cpad-info)) (define-extern cpad-get-data (function cpad-info cpad-info)) -(define-extern install-handler (function int (function int) none)) ;; check return val +(define-extern install-handler (function int (function int) int)) ;; check return val ;; install-debug-handler ;; file-stream-open (define-extern file-stream-open (function file-stream basic basic file-stream)) diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 96e6b4a4ff..3a810026e9 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -28,16 +28,16 @@ Compiler::Compiler(std::unique_ptr repl) ReplStatus Compiler::execute_repl() { // init repl - m_repl.get()->print_welcome_message(); - auto examples = m_repl.get()->examples; - auto regex_colors = m_repl.get()->regex_colors; - m_repl.get()->init_default_settings(); + m_repl->print_welcome_message(); + auto examples = m_repl->examples; + auto regex_colors = m_repl->regex_colors; + m_repl->init_default_settings(); using namespace std::placeholders; - m_repl.get()->get_repl().set_completion_callback( + m_repl->get_repl().set_completion_callback( std::bind(&Compiler::find_symbols_by_prefix, this, _1, _2, std::cref(examples))); - m_repl.get()->get_repl().set_hint_callback( + m_repl->get_repl().set_hint_callback( std::bind(&Compiler::find_hints_by_prefix, this, _1, _2, _3, std::cref(examples))); - m_repl.get()->get_repl().set_highlighter_callback( + m_repl->get_repl().set_highlighter_callback( std::bind(&Compiler::repl_coloring, this, _1, _2, std::cref(regex_colors))); while (!m_want_exit && !m_want_reload) { @@ -53,7 +53,7 @@ ReplStatus Compiler::execute_repl() { prompt = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), "gr> "); } - auto code = m_goos.reader.read_from_stdin(prompt, *m_repl.get()); + auto code = m_goos.reader.read_from_stdin(prompt, *m_repl); if (!code) { continue; } diff --git a/test/decompiler/reference/dma-buffer_REF.gc b/test/decompiler/reference/dma-buffer_REF.gc new file mode 100644 index 0000000000..93471ed017 --- /dev/null +++ b/test/decompiler/reference/dma-buffer_REF.gc @@ -0,0 +1,212 @@ +;;-*-Lisp-*- +(in-package goal) + +;; definition of type dma-packet +(deftype dma-packet (structure) + ((dma dma-tag :offset-assert 0) + (vif0 vif-tag :offset-assert 8) + (vif1 vif-tag :offset-assert 12) + (quad uint128 :offset 0) + ) + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + ) + +;; definition for method 3 of type dma-packet +;; Used lq/sq +(defmethod inspect dma-packet ((obj dma-packet)) + (format #t "[~8x] ~A~%" obj 'dma-packet) + (format #t "~Tdma: #x~X~%" (-> obj dma)) + (format #t "~Tvif0: #x~X~%" (-> obj vif0)) + (format #t "~Tvif1: #x~X~%" (-> obj vif1)) + (format #t "~Tquad: ~D~%" (-> obj quad)) + obj + ) + +;; definition of type dma-packet-array +(deftype dma-packet-array (inline-array-class) + () + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + ) + +;; definition for method 3 of type dma-packet-array +(defmethod inspect dma-packet-array ((obj dma-packet-array)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tlength: ~D~%" (-> obj length)) + (format #t "~Tallocated-length: ~D~%" (-> obj allocated-length)) + (format #t "~Tdata[0] @ #x~X~%" (&-> obj data 4)) + obj + ) + +;; failed to figure out what this is: +(set! (-> dma-packet-array heap-base) (the-as uint 16)) + +;; definition of type dma-gif-packet +(deftype dma-gif-packet (structure) + ((dma-vif dma-packet :inline :offset-assert 0) + (gif uint64 2 :offset-assert 16) + (quad uint128 2 :offset 0) + ) + :method-count-assert 9 + :size-assert #x20 + :flag-assert #x900000020 + ) + +;; definition for method 3 of type dma-gif-packet +(defmethod inspect dma-gif-packet ((obj dma-gif-packet)) + (format #t "[~8x] ~A~%" obj 'dma-gif-packet) + (format #t "~Tdma-vif: #~%" (-> obj dma-vif)) + (format #t "~Tgif[2] @ #x~X~%" (-> obj gif)) + (format #t "~Tquad[2] @ #x~X~%" (-> obj dma-vif)) + obj + ) + +;; definition of type dma-buffer +(deftype dma-buffer (basic) + ((allocated-length int32 :offset-assert 4) + (base pointer :offset-assert 8) + (end pointer :offset-assert 12) + (data uint64 1 :offset-assert 16) + (data-buffer uint8 :dynamic :offset 16) + ) + :method-count-assert 9 + :size-assert #x18 + :flag-assert #x900000018 + (:methods + (new (symbol type int) _type_ 0) + ) + ) + +;; definition for method 3 of type dma-buffer +(defmethod inspect dma-buffer ((obj dma-buffer)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tallocated-length: ~D~%" (-> obj allocated-length)) + (format #t "~Tbase: #x~X~%" (-> obj base)) + (format #t "~Tend: #x~X~%" (-> obj end)) + (format #t "~Tdata[1] @ #x~X~%" (-> obj data)) + obj + ) + +;; definition for method 0 of type dma-buffer +(defmethod new dma-buffer ((allocation symbol) (type-to-make type) (arg0 int)) + (let + ((v0-0 + (object-new + allocation + type-to-make + (+ (+ arg0 -4) (the-as int (-> type-to-make size))) + ) + ) + ) + (set! (-> v0-0 base) (-> v0-0 data)) + (set! (-> v0-0 allocated-length) arg0) + v0-0 + ) + ) + +;; definition for function dma-buffer-inplace-new +(defun dma-buffer-inplace-new ((arg0 dma-buffer) (arg1 int)) + (set! (-> arg0 base) (-> arg0 data)) + (set! (-> arg0 allocated-length) arg1) + arg0 + ) + +;; definition for method 4 of type dma-buffer +(defmethod length dma-buffer ((obj dma-buffer)) + (-> obj allocated-length) + ) + +;; definition for method 5 of type dma-buffer +(defmethod asize-of dma-buffer ((obj dma-buffer)) + (+ (+ (-> obj allocated-length) -4) (the-as int (-> dma-buffer size))) + ) + +;; definition for function dma-buffer-length +(defun dma-buffer-length ((arg0 dma-buffer)) + (shr (+ (&- (-> arg0 base) (the-as uint (-> arg0 data))) 15) 4) + ) + +;; definition for function dma-buffer-free +(defun dma-buffer-free ((arg0 dma-buffer)) + (shr (+ (&- (-> arg0 end) (the-as uint (-> arg0 base))) 15) 4) + ) + +;; definition for function dma-buffer-add-vu-function +(defun + dma-buffer-add-vu-function + ((dma-buf dma-buffer) (vu-func vu-function) (arg2 int)) + (let ((func-ptr (the-as pointer (&-> vu-func data 4))) + (qlen (-> vu-func qlength)) + (origin (-> vu-func origin)) + ) + (while (> qlen 0) + (let ((qwc-now (min 127 qlen))) + (let* ((dma-buf-2 dma-buf) + (buf-ptr (the-as dma-packet (-> dma-buf-2 base))) + ) + (set! + (-> buf-ptr dma) + (new 'static 'dma-tag :id #x3 :addr (the-as int func-ptr) :qwc qwc-now) + ) + (set! + (-> buf-ptr vif0) + (new 'static 'vif-tag :cmd (if (zero? arg2) 16 19)) + ) + (set! + (-> buf-ptr vif1) + (new 'static 'vif-tag :cmd #x4a :num (shl qwc-now 1) :imm origin) + ) + (set! (-> dma-buf-2 base) (&+ (the-as pointer buf-ptr) 16)) + ) + (&+! func-ptr (shl qwc-now 4)) + (set! qlen (- qlen qwc-now)) + (+! origin (shl qwc-now 1)) + ) + ) + ) + #f + ) + +;; definition for function dma-buffer-send +(defun dma-buffer-send ((arg0 dma-bank) (arg1 dma-buffer)) + (when + (< + (-> arg1 allocated-length) + (&- (-> arg1 base) (the-as uint (-> arg1 data))) + ) + (crash!) + (let ((v1-2 0)) + ) + ) + (dma-send + arg0 + (the-as uint (-> arg1 data)) + (the-as uint (dma-buffer-length arg1)) + ) + (none) + ) + +;; definition for function dma-buffer-send-chain +(defun dma-buffer-send-chain ((arg0 dma-bank-source) (arg1 dma-buffer)) + (when + (< + (-> arg1 allocated-length) + (&- (-> arg1 base) (the-as uint (-> arg1 data))) + ) + (crash!) + (let ((v1-2 0)) + ) + ) + (dma-send-chain arg0 (the-as uint (-> arg1 data))) + (none) + ) + +;; failed to figure out what this is: +(none) + + + + diff --git a/test/decompiler/reference/dma-h_REF.gc b/test/decompiler/reference/dma-h_REF.gc index 805c3b1f08..b17f22e05f 100644 --- a/test/decompiler/reference/dma-h_REF.gc +++ b/test/decompiler/reference/dma-h_REF.gc @@ -233,10 +233,10 @@ ;; definition of type dma-bucket (deftype dma-bucket (structure) - ((tag dma-tag :offset-assert 0) - (last (pointer uint64) :offset-assert 8) - (dummy uint32 :offset-assert 12) - (next uint32 :offset 4) + ((tag dma-tag :offset-assert 0) + (last (pointer dma-tag) :offset-assert 8) + (dummy uint32 :offset-assert 12) + (next uint32 :offset 4) ) :method-count-assert 9 :size-assert #x10 diff --git a/test/decompiler/reference/timer-h_REF.gc b/test/decompiler/reference/timer-h_REF.gc index cf921b1323..2f674438d2 100644 --- a/test/decompiler/reference/timer-h_REF.gc +++ b/test/decompiler/reference/timer-h_REF.gc @@ -121,9 +121,9 @@ (format #t "~Tcolor: ~D ~D ~D~%" - (-> (-> obj color) r) - (-> (-> obj color) g) - (-> (-> obj color) b) + (-> obj color r) + (-> obj color g) + (-> obj color b) ) obj ) diff --git a/test/decompiler/test_DataParser.cpp b/test/decompiler/test_DataParser.cpp index 882487f9a4..0806e85478 100644 --- a/test/decompiler/test_DataParser.cpp +++ b/test/decompiler/test_DataParser.cpp @@ -342,3 +342,18 @@ TEST_F(DataDecompTest, FloatArray) { "(new 'static 'array 'float 7\n" "1.0 0.0 1.0 0.0 1.0 0.0 1.0)"); } + +TEST_F(DataDecompTest, Bitfield) { + // this is for testing bitfields from a 64-bit static constant. + std::string input = + "L80:\n" + " .word 0x80400040\n" + " .word 0x0"; + auto parsed = parse_data(input); + auto& ts = dts->ts; + auto typespec = ts.make_typespec("rgba"); + auto info = dynamic_cast(ts.lookup_type(typespec)); + auto decomp = + decompile_bitfield(typespec, info, parsed.label("L80"), parsed.labels, {parsed.words}, ts); + check_forms_equal(decomp.print(), "(new 'static 'rgba :r #x40 :b #x40 :a #x80)"); +} \ No newline at end of file diff --git a/test/decompiler/test_FormExpressionBuild2.cpp b/test/decompiler/test_FormExpressionBuild2.cpp index 445ddbae72..3a47e52710 100644 --- a/test/decompiler/test_FormExpressionBuild2.cpp +++ b/test/decompiler/test_FormExpressionBuild2.cpp @@ -788,4 +788,212 @@ TEST_F(FormRegressionTest, DmaInitialize) { test_with_expr(func, type, expected, false, "", {}, "[[1, \"v1\", \"vif-bank\"], [8, \"v1\", \"vif-bank\"], [6, \"a0\", " "\"vif-bank\"], [13, \"a0\", \"vif-bank\"]]"); -} \ No newline at end of file +} + +// Dynamic bitfield stuff. +TEST_F(FormRegressionTest, SetDisplayEnv) { + std::string func = + "sll r0, r0, 0\n" + " ori v1, r0, 65441\n" + " sd v1, 0(a0)\n" + " addiu v1, r0, 3\n" + " sd v1, 8(a0)\n" + " dsll32 v1, t2, 23\n" + " dsrl32 v1, v1, 23\n" + " dsra t2, a2, 6\n" + " dsll32 t2, t2, 26\n" + " dsrl32 t2, t2, 17\n" + " or v1, v1, t2\n" + " dsll32 a1, a1, 27\n" + " dsrl32 a1, a1, 12\n" + " or v1, v1, a1\n" + " sd v1, 16(a0)\n" + " addiu v1, r0, 2559\n" + " dsll32 v1, v1, 0\n" + " daddiu a1, a2, 2559\n" + " div a1, a2\n" + " mflo a1\n" + " daddiu a1, a1, -1\n" + " dsll32 a1, a1, 28\n" + " dsrl32 a1, a1, 5\n" + " or v1, v1, a1\n" + " dsll a1, a3, 1\n" + " daddiu a1, a1, -1\n" + " dsll32 a1, a1, 21\n" + " dsrl a1, a1, 9\n" + " or v1, v1, a1\n" + " addiu a1, r0, 2560\n" + " div a1, a2\n" + " mflo a1\n" + " mult3 a1, t0, a1\n" + " daddiu a1, a1, 652\n" + " dsll32 a1, a1, 20\n" + " dsrl32 a1, a1, 20\n" + " or v1, v1, a1\n" + " daddiu a1, t1, 50\n" + " dsll32 a1, a1, 21\n" + " dsrl32 a1, a1, 9\n" + " or v1, v1, a1\n" + " sd v1, 24(a0)\n" + " sd r0, 32(a0)\n" + " or v0, a0, r0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function display-env int int int int int int display-env)"; + std::string expected = + "(begin\n" + " (set!\n" + " (-> arg0 pmode)\n" + " (new (quote static) (quote gs-pmode) :en1 1 :mmod 1 :slbg 1 :alp 255)\n" + " )\n" + " (set! (-> arg0 smode2) (new (quote static) (quote gs-smode2) :int 1 :ffmd 1))\n" + " (set!\n" + " (-> arg0 dspfb)\n" + " (new\n" + " (quote static)\n" + " (quote gs-display-fb)\n" + " :psm\n" + " arg1\n" + " :fbw\n" + " (sar arg2 6)\n" + " :fbp\n" + " arg6\n" + " )\n" + " )\n" + " (set!\n" + " (-> arg0 display)\n" + " (new\n" + " (quote static)\n" + " (quote gs-display)\n" + " :dw\n" + " 2559\n" + " :dy\n" + " (+ arg5 50)\n" + " :dx\n" + " (+ (* arg4 (/ 2560 arg2)) 652)\n" + " :dh\n" + " (+ (shl arg3 1) -1)\n" + " :magh\n" + " (+ (/ (+ arg2 2559) arg2) -1)\n" + " )\n" + " )\n" + " (set! (-> arg0 bgcolor) (new (quote static) (quote gs-bgcolor)))\n" + " arg0\n" + " )"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, DmaBufferAddVuFunction) { + std::string func = + "sll r0, r0, 0\n" + " daddiu v1, a1, 16\n" + " lw a3, 8(a1)\n" + " lw a1, 4(a1)\n" + " beq r0, r0, L9\n" + " sll r0, r0, 0\n" + + "L6:\n" + " addiu t0, r0, 127\n" + " or t1, a3, r0\n" + " slt t2, t0, t1\n" + " movz t0, t1, t2\n" + " or t1, a0, r0\n" + " lwu t2, 4(t1)\n" + " lui t3, 12288\n" + " dsll32 t4, t0, 16\n" + " dsrl32 t4, t4, 16\n" + " or t3, t3, t4\n" + " dsll32 t4, v1, 1\n" + " dsrl t4, t4, 1\n" + " or t3, t3, t4\n" + " sd t3, 0(t2)\n" + " bne a2, r0, L7\n" + " sll r0, r0, 0\n" + + " addiu t3, r0, 16\n" + " beq r0, r0, L8\n" + " sll r0, r0, 0\n" + + "L7:\n" + " addiu t3, r0, 19\n" + + "L8:\n" + " dsll32 t3, t3, 25\n" + " dsrl32 t3, t3, 1\n" + " sw t3, 8(t2)\n" + " lui t3, 18944\n" + " dsll32 t4, a1, 16\n" + " dsrl32 t4, t4, 16\n" + " or t3, t3, t4\n" + " dsll t4, t0, 1\n" + " dsll32 t4, t4, 24\n" + " dsrl32 t4, t4, 8\n" + " or t3, t3, t4\n" + " sw t3, 12(t2)\n" + " daddiu t2, t2, 16\n" + " sw t2, 4(t1)\n" + " dsll t1, t0, 4\n" + " daddu v1, v1, t1\n" + + " dsubu a3, a3, t0\n" + " dsll t0, t0, 1\n" + " daddu a1, a1, t0\n" + " or t0, a1, r0\n" + + "L9:\n" + " slt t0, r0, a3\n" + " bne t0, r0, L6\n" + " sll r0, r0, 0\n" + + " or v0, s7, r0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function dma-buffer vu-function int symbol)"; + std::string expected = + "(begin\n" + " (let ((v1-0 (the-as pointer (&-> arg1 data 4)))\n" + " (a3-0 (-> arg1 qlength))\n" + " (a1-1 (-> arg1 origin))\n" + " )\n" + " (while (> a3-0 0)\n" + " (let ((t0-1 (min 127 a3-0)))\n" + " (let* ((t1-1 arg0)\n" + " (t2-0 (the-as dma-packet (-> t1-1 base)))\n" + " )\n" + " (set!\n" + " (-> (the-as dma-packet t2-0) dma)\n" + " (new\n" + " (quote static)\n" + " (quote dma-tag)\n" + " :id\n" + " 3\n" + " :addr\n" + " (the-as int v1-0)\n" + " :qwc\n" + " t0-1\n" + " )\n" + " )\n" + " (set!\n" + " (-> (the-as dma-packet t2-0) vif0)\n" + " (new (quote static) (quote vif-tag) :cmd (if (zero? arg2)\n" + " 16\n" + " 19\n" + " )\n" + " )\n" + " )\n" + " (set!\n" + " (-> (the-as dma-packet t2-0) vif1)\n" + " (new (quote static) (quote vif-tag) :cmd 74 :num (shl t0-1 1) :imm a1-1)\n" + " )\n" + " (set! (-> t1-1 base) (&+ (the-as pointer t2-0) 16))\n" + " )\n" + " (&+! v1-0 (shl t0-1 4))\n" + " (set! a3-0 (- a3-0 t0-1))\n" + " (+! a1-1 (shl t0-1 1))\n" + " )\n" + " )\n" + " )\n" + " #f\n" + " )"; + test_with_expr(func, type, expected, false, "", {}, "[[[9, 33], \"t2\", \"dma-packet\"]]"); +} diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp index 83634af28f..6e00647f55 100644 --- a/test/offline/offline_test_main.cpp +++ b/test/offline/offline_test_main.cpp @@ -16,7 +16,7 @@ const std::unordered_set g_object_files_to_decompile = { "bounding-box-h", "matrix-h", "quaternion-h", "euler-h", "transform-h", "geometry-h", "trigonometry-h", /* transformq-h */ "matrix", "transform", "quaternion", "euler", /* geometry, trigonometry, */ - "gsound-h", "timer-h", "timer", "vif-h", "dma-h", "video-h", "vu1-user-h", "dma", + "gsound-h", "timer-h", "timer", "vif-h", "dma-h", "video-h", "vu1-user-h", "dma", "dma-buffer", /* gap */ "bounding-box", /* gap */ @@ -31,6 +31,7 @@ const std::vector g_object_files_to_check_against_reference = { /* transformq-h, */ "matrix", "transform", "quaternion", "euler", /* geometry, trigonometry */ "gsound-h", "timer-h", /* timer, */ "vif-h", "dma-h", "video-h", "vu1-user-h", "dma", + "dma-buffer", /* gap */ "bounding-box", /* gap */ "sync-info-h", "sync-info"};