Merge remote-tracking branch 'upstream/master' into logging

This commit is contained in:
Shay
2020-10-01 09:58:41 -06:00
252 changed files with 14079 additions and 2989 deletions
+18 -11
View File
@@ -9,11 +9,7 @@
"name": "Run Tests - Summary",
"args": [
"--gtest_brief=1"
],
"env": {
"NEXT_DIR": "${projectDir}",
"FAKE_ISO_PATH": "/game/fake_iso.txt"
}
]
},
{
"type": "default",
@@ -22,17 +18,28 @@
"name": "Run Tests - Verbose",
"args": [
"--gtest_brief=0"
],
"env": {
"NEXT_DIR": "${projectDir}",
"FAKE_ISO_PATH": "/game/fake_iso.txt"
}
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run Game"
"name": "Run Runtime (no kernel)",
"args": [
"-fakeiso",
"-debug",
"-nokernel"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run Runtime (with kernel)",
"args": [
"-fakeiso",
"-debug"
]
},
{
"type": "default",
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
# Directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
$DIR/build/game/gk -fakeiso -debug
+1 -1
View File
@@ -1,2 +1,2 @@
add_library(goos SHARED Object.cpp TextDB.cpp Reader.cpp Interpreter.cpp InterpreterEval.cpp)
target_link_libraries(goos common_util)
target_link_libraries(goos common_util fmt)
+30 -30
View File
@@ -38,36 +38,36 @@ Interpreter::Interpreter() {
{"while", &Interpreter::eval_while},
};
builtin_forms = {
{"top-level", &Interpreter::eval_begin},
{"begin", &Interpreter::eval_begin},
{"exit", &Interpreter::eval_exit},
{"read", &Interpreter::eval_read},
{"read-file", &Interpreter::eval_read_file},
{"print", &Interpreter::eval_print},
{"inspect", &Interpreter::eval_inspect},
{"load-file", &Interpreter::eval_load_file},
{"eq?", &Interpreter::eval_equals},
{"gensym", &Interpreter::eval_gensym},
{"eval", &Interpreter::eval_eval},
{"cons", &Interpreter::eval_cons},
{"car", &Interpreter::eval_car},
{"cdr", &Interpreter::eval_cdr},
{"set-car!", &Interpreter::eval_set_car},
{"set-cdr!", &Interpreter::eval_set_cdr},
{"+", &Interpreter::eval_plus},
{"-", &Interpreter::eval_minus},
{"*", &Interpreter::eval_times},
{"/", &Interpreter::eval_divide},
{"=", &Interpreter::eval_numequals},
{"<", &Interpreter::eval_lt},
{">", &Interpreter::eval_gt},
{"<=", &Interpreter::eval_leq},
{">=", &Interpreter::eval_geq},
{"null?", &Interpreter::eval_null},
{"type?", &Interpreter::eval_type},
{"current-method-type", &Interpreter::eval_current_method_type},
};
builtin_forms = {{"top-level", &Interpreter::eval_begin},
{"begin", &Interpreter::eval_begin},
{"exit", &Interpreter::eval_exit},
{"read", &Interpreter::eval_read},
{"read-file", &Interpreter::eval_read_file},
{"print", &Interpreter::eval_print},
{"inspect", &Interpreter::eval_inspect},
{"load-file", &Interpreter::eval_load_file},
{"eq?", &Interpreter::eval_equals},
{"gensym", &Interpreter::eval_gensym},
{"eval", &Interpreter::eval_eval},
{"cons", &Interpreter::eval_cons},
{"car", &Interpreter::eval_car},
{"cdr", &Interpreter::eval_cdr},
{"set-car!", &Interpreter::eval_set_car},
{"set-cdr!", &Interpreter::eval_set_cdr},
{"+", &Interpreter::eval_plus},
{"-", &Interpreter::eval_minus},
{"*", &Interpreter::eval_times},
{"/", &Interpreter::eval_divide},
{"=", &Interpreter::eval_numequals},
{"<", &Interpreter::eval_lt},
{">", &Interpreter::eval_gt},
{"<=", &Interpreter::eval_leq},
{">=", &Interpreter::eval_geq},
{"null?", &Interpreter::eval_null},
{"type?", &Interpreter::eval_type},
{"current-method-type", &Interpreter::eval_current_method_type},
{"fmt", &Interpreter::eval_format},
{"error", &Interpreter::eval_error}};
string_to_type = {{"empty-list", ObjectType::EMPTY_LIST},
{"integer", ObjectType::INTEGER},
+6
View File
@@ -180,6 +180,12 @@ class Interpreter {
Object eval_current_method_type(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_format(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_error(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
// specials
Object eval_define(const Object& form,
+51
View File
@@ -3,6 +3,7 @@
* Implementation of built-in GOOS functions.
*/
#include <third-party/fmt/format.h>
#include "Interpreter.h"
namespace goos {
@@ -586,4 +587,54 @@ Object Interpreter::eval_current_method_type(const Object& form,
vararg_check(form, args, {}, {});
return SymbolObject::make_new(reader.symbolTable, goal_to_goos.enclosing_method_type);
}
Object Interpreter::eval_format(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
if (args.unnamed.size() < 2) {
throw_eval_error(form, "format must get at least two arguments");
}
auto dest = args.unnamed.at(0);
auto format_str = args.unnamed.at(1);
if (!format_str.is_string()) {
throw_eval_error(form, "format string must be a string");
}
// Note: this might be relying on internal implementation details of libfmt to work properly
// and isn't a great solution.
std::vector<fmt::basic_format_arg<fmt::format_context>> args2;
std::vector<std::string> strings;
for (size_t i = 2; i < args.unnamed.size(); i++) {
if (args.unnamed.at(i).is_string()) {
strings.push_back(args.unnamed.at(i).as_string()->data);
} else {
strings.push_back(args.unnamed.at(i).print());
}
}
for (auto& x : strings) {
args2.push_back(fmt::detail::make_arg<fmt::format_context>(x));
}
auto formatted =
fmt::vformat(format_str.as_string()->data,
fmt::format_args(args2.data(), static_cast<unsigned>(args2.size())));
if (truthy(dest)) {
printf("%s", formatted.c_str());
}
return StringObject::make_new(formatted);
}
Object Interpreter::eval_error(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
vararg_check(form, args, {ObjectType::STRING}, {});
throw_eval_error(form, "Error: " + args.unnamed.at(0).as_string()->data);
return EmptyListObject::make_new();
}
} // namespace goos
+46 -14
View File
@@ -9,6 +9,7 @@ TypeSystem::TypeSystem() {
// the "none" and "_type_" types are included by default.
add_type("none", std::make_unique<NullType>("none"));
add_type("_type_", std::make_unique<NullType>("_type_"));
add_type("_varargs_", std::make_unique<NullType>("_varargs_"));
}
/*!
@@ -41,7 +42,7 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr<Type> type)
// newly defined!
// none/object get to skip these checks because they are roots.
if (name != "object" && name != "none" && name != "_type_") {
if (name != "object" && name != "none" && name != "_type_" && name != "_varargs_") {
if (m_forward_declared_types.find(type->get_parent()) != m_forward_declared_types.end()) {
fmt::print("[TypeSystem] Type {} has incompletely defined parent {}\n", type->get_name(),
type->get_parent());
@@ -155,6 +156,10 @@ TypeSpec TypeSystem::make_typespec(const std::string& name) const {
}
}
bool TypeSystem::fully_defined_type_exists(const std::string& name) const {
return m_types.find(name) != m_types.end();
}
/*!
* Create a typespec for a function. If the function doesn't return anything, use "none" as the
* return type.
@@ -230,8 +235,9 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
MethodInfo TypeSystem::add_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts) {
return add_method(lookup_type(make_typespec(type_name)), method_name, ts);
const TypeSpec& ts,
bool allow_new_method) {
return add_method(lookup_type(make_typespec(type_name)), method_name, ts, allow_new_method);
}
/*!
@@ -243,7 +249,10 @@ MethodInfo TypeSystem::add_method(const std::string& type_name,
* is overriding the "new" method - the TypeSystem will track that because overridden new methods
* may have different arguments.
*/
MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, const TypeSpec& ts) {
MethodInfo TypeSystem::add_method(Type* type,
const std::string& method_name,
const TypeSpec& ts,
bool allow_new_method) {
if (method_name == "new") {
return add_new_method(type, ts);
}
@@ -281,6 +290,11 @@ MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, co
return existing_info;
} else {
if (!allow_new_method) {
fmt::print("[TypeSystem] Attempted to add method {} to type {} but it was not declared.\n",
method_name, type->get_name());
throw std::runtime_error("illegal method definition");
}
// add a new method!
return type->add_method({get_next_method_id(type), method_name, ts, type->get_name()});
}
@@ -295,7 +309,7 @@ MethodInfo TypeSystem::add_new_method(Type* type, const TypeSpec& ts) {
MethodInfo existing;
if (type->get_my_new_method(&existing)) {
// it exists!
if (existing.type != ts) {
if (!existing.type.is_compatible_child_method(ts, type->get_name())) {
fmt::print(
"[TypeSystem] The new method of {} was originally defined as {}, but has been redefined "
"as {}\n",
@@ -483,7 +497,7 @@ int TypeSystem::add_field_to_type(StructureType* type,
// we need to compute the offset ourself!
offset = align(type->get_size_in_memory(), field_alignment);
} else {
int aligned_offset = align(type->get_size_in_memory(), field_alignment);
int aligned_offset = align(offset, field_alignment);
if (offset != aligned_offset) {
fmt::print(
"[TypeSystem] Tried to overwrite offset of field to be {}, but it is not aligned "
@@ -563,13 +577,13 @@ void TypeSystem::add_builtin_types() {
// Methods and Fields
// OBJECT
add_method(obj_type, "new", make_function_typespec({"symbol", "type", "int32"}, "_type_"));
add_method(obj_type, "new", make_function_typespec({"symbol", "type", "int"}, "_type_"));
add_method(obj_type, "delete", make_function_typespec({"_type_"}, "none"));
add_method(obj_type, "print", make_function_typespec({"_type_"}, "_type_"));
add_method(obj_type, "inspect", make_function_typespec({"_type_"}, "_type_"));
add_method(obj_type, "length",
make_function_typespec({"_type_"}, "int32")); // todo - this integer type?
add_method(obj_type, "asize-of", make_function_typespec({"_type_"}, "int32"));
make_function_typespec({"_type_"}, "int")); // todo - this integer type?
add_method(obj_type, "asize-of", make_function_typespec({"_type_"}, "int"));
add_method(obj_type, "copy", make_function_typespec({"_type_", "symbol"}, "_type_"));
add_method(obj_type, "relocate", make_function_typespec({"_type_", "int32"}, "_type_"));
add_method(obj_type, "mem-usage",
@@ -580,7 +594,7 @@ void TypeSystem::add_builtin_types() {
// the type. Dynamic structures use new-dynamic-structure, which is used exactly once ever.
add_method(structure_type, "new", make_function_typespec({"symbol", "type"}, "structure"));
// structure_type is a field-less StructureType, so we have to do this to match the runtime.
structure_type->override_size_in_memory(4);
// structure_type->override_size_in_memory(4);
// BASIC
// we intentionally don't inherit from structure because structure's size is weird.
@@ -600,12 +614,12 @@ void TypeSystem::add_builtin_types() {
add_method(type_type, "new", make_function_typespec({"symbol", "type", "int"}, "_type_"));
add_field_to_type(type_type, "symbol", make_typespec("symbol"));
add_field_to_type(type_type, "parent", make_typespec("type"));
add_field_to_type(type_type, "allocated-size", make_typespec("uint16")); // todo, u16 or s16?
add_field_to_type(type_type, "size", make_typespec("uint16")); // actually u16
add_field_to_type(type_type, "psize",
make_typespec("uint16")); // todo, u16 or s16. what really is this?
add_field_to_type(type_type, "heap-base", make_typespec("uint16")); // todo
add_field_to_type(type_type, "method-count", make_typespec("uint16")); // todo
add_field_to_type(type_type, "vtable", make_typespec("function"), false, true);
add_field_to_type(type_type, "heap-base", make_typespec("uint16")); // todo
add_field_to_type(type_type, "allocated-length", make_typespec("uint16")); // todo
add_field_to_type(type_type, "method-table", make_typespec("function"), false, true);
// STRING
builtin_structure_inherit(string_type);
@@ -640,6 +654,8 @@ void TypeSystem::add_builtin_types() {
// pair
pair_type->override_offset(2);
add_method(pair_type, "new",
make_function_typespec({"symbol", "type", "object", "object"}, "_type_"));
add_field_to_type(pair_type, "car", make_typespec("object"));
add_field_to_type(pair_type, "cdr", make_typespec("object"));
@@ -970,4 +986,20 @@ TypeSpec TypeSystem::lowest_common_ancestor(const std::vector<TypeSpec>& types)
result = lowest_common_ancestor(result, types.at(i));
}
return result;
}
TypeSpec coerce_to_reg_type(const TypeSpec& in) {
if (in.arg_count() == 0) {
if (in.base_type() == "int8" || in.base_type() == "int16" || in.base_type() == "int32" ||
in.base_type() == "int16") {
return TypeSpec("int");
}
if (in.base_type() == "uint8" || in.base_type() == "uint16" || in.base_type() == "uint32" ||
in.base_type() == "uint16") {
return TypeSpec("uint");
}
}
return in;
}
+9 -2
View File
@@ -39,6 +39,7 @@ class TypeSystem {
DerefInfo get_deref_info(const TypeSpec& ts);
bool fully_defined_type_exists(const std::string& name) const;
TypeSpec make_typespec(const std::string& name) const;
TypeSpec make_function_typespec(const std::vector<std::string>& arg_types,
const std::string& return_type);
@@ -53,8 +54,12 @@ class TypeSystem {
MethodInfo add_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts);
MethodInfo add_method(Type* type, const std::string& method_name, const TypeSpec& ts);
const TypeSpec& ts,
bool allow_new_method = true);
MethodInfo add_method(Type* type,
const std::string& method_name,
const TypeSpec& ts,
bool allow_new_method = true);
MethodInfo add_new_method(Type* type, const TypeSpec& ts);
MethodInfo lookup_method(const std::string& type_name, const std::string& method_name);
MethodInfo lookup_new_method(const std::string& type_name);
@@ -122,4 +127,6 @@ class TypeSystem {
bool m_allow_redefinition = false;
};
TypeSpec coerce_to_reg_type(const TypeSpec& in);
#endif // JAK_TYPESYSTEM_H
+18 -18
View File
@@ -78,24 +78,6 @@ int64_t get_int(const goos::Object& obj) {
throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't");
}
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
if (src.is_symbol()) {
return type_system->make_typespec(symbol_string(src));
} else if (src.is_pair()) {
TypeSpec ts = type_system->make_typespec(symbol_string(car(&src)));
const auto& rest = *cdr(&src);
for_each_in_list(rest,
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
return ts;
} else {
throw std::runtime_error("invalid typespec: " + src.print());
}
assert(false);
return {};
}
void add_field(StructureType* structure, TypeSystem* ts, const goos::Object& def) {
auto rest = &def;
@@ -278,6 +260,24 @@ TypeFlags parse_structure_def(StructureType* type,
} // namespace
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) {
if (src.is_symbol()) {
return type_system->make_typespec(symbol_string(src));
} else if (src.is_pair()) {
TypeSpec ts = type_system->make_typespec(symbol_string(car(&src)));
const auto& rest = *cdr(&src);
for_each_in_list(rest,
[&](const goos::Object& o) { ts.add_arg(parse_typespec(type_system, o)); });
return ts;
} else {
throw std::runtime_error("invalid typespec: " + src.print());
}
assert(false);
return {};
}
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
auto iter = &deftype;
+1
View File
@@ -22,3 +22,4 @@ struct DeftypeResult {
};
DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts);
TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src);
+2 -2
View File
@@ -12,8 +12,8 @@
namespace versions {
// language version
constexpr s32 GOAL_VERSION_MAJOR = 2;
constexpr s32 GOAL_VERSION_MINOR = 6;
constexpr s32 GOAL_VERSION_MAJOR = 0;
constexpr s32 GOAL_VERSION_MINOR = 1;
} // namespace versions
// GOAL kernel version
+1 -1
View File
@@ -3,4 +3,4 @@
# Directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out
$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out
+7 -6
View File
@@ -12,14 +12,15 @@ add_executable(decompiler
util/FileIO.cpp
config.cpp
util/LispPrint.cpp
util/DecompilerTypeSystem.cpp
Function/BasicBlocks.cpp
Disasm/InstructionMatching.cpp
TypeSystem/GoalType.cpp
TypeSystem/GoalFunction.cpp
TypeSystem/GoalSymbol.cpp
TypeSystem/TypeInfo.cpp
TypeSystem/TypeSpec.cpp Function/CfgVtx.cpp Function/CfgVtx.h)
Function/CfgVtx.cpp Function/CfgVtx.h
IR/BasicOpBuilder.cpp
IR/IR.cpp)
target_link_libraries(decompiler
minilzo
common_util)
common_util
type_system
fmt)
+6
View File
@@ -44,6 +44,12 @@ struct InstructionAtom {
std::string to_string(const LinkedObjectFile& file) const;
bool is_link_or_label() const;
bool is_reg() const { return kind == REGISTER; }
bool is_imm() const { return kind == IMM; }
bool is_label() const { return kind == LABEL; }
bool is_sym() const { return kind == IMM_SYM; }
bool is_reg(Register r) const { return kind == REGISTER && reg == r; }
private:
int32_t imm;
+5
View File
@@ -1,5 +1,10 @@
#pragma once
/*!
* @file InstructionMatching.h
* Utilities for checking if an instruction matches some criteria.
*/
#ifndef JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
#define JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
+5
View File
@@ -1,3 +1,8 @@
/*!
* @file OpcodeInfo.cpp
* Decoding info for each opcode.
*/
#include "OpcodeInfo.h"
#include <cassert>
-5
View File
@@ -1,8 +1,5 @@
#pragma once
#ifndef JAK_DISASSEMBLER_BASICBLOCKS_H
#define JAK_DISASSEMBLER_BASICBLOCKS_H
#include <vector>
#include <memory>
@@ -21,5 +18,3 @@ struct BasicBlock {
std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
int seg,
const Function& func);
#endif // JAK_DISASSEMBLER_BASICBLOCKS_H
+2 -12
View File
@@ -1637,7 +1637,6 @@ void ControlFlowGraph::flag_early_exit(const std::vector<BasicBlock>& blocks) {
* Build and resolve a Control Flow Graph as much as possible.
*/
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int seg, Function& func) {
printf("build cfg : %s\n", func.guessed_name.to_string().c_str());
auto cfg = std::make_shared<ControlFlowGraph>();
const auto& blocks = cfg->create_blocks(func.basic_blocks.size());
@@ -1716,13 +1715,6 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
cfg->flag_early_exit(func.basic_blocks);
// if(func.guessed_name.to_string() == "(method 9 thread)")
// cfg->find_cond_w_else();
// if (func.guessed_name.to_string() != "looping-code") {
// return cfg;
// }
bool changed = true;
while (changed) {
changed = false;
@@ -1730,11 +1722,9 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
// printf("%s\n", cfg->to_dot().c_str());
// printf("%s\n", cfg->to_form()->toStringPretty().c_str());
changed = changed | cfg->find_cond_w_else();
changed = changed | cfg->find_cond_n_else();
changed = changed || cfg->find_cond_w_else();
changed = changed || cfg->find_cond_n_else();
changed = changed || cfg->find_while_loop_top_level();
// //// printf("while loops? %d\n", changed);
//// changed = changed || cfg->find_if_else_top_level();
changed = changed || cfg->find_seq_top_level();
changed = changed || cfg->find_short_circuits();
+42 -3
View File
@@ -3,7 +3,7 @@
#include "Function.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/TypeSystem/TypeInfo.h"
#include "decompiler/util/DecompilerTypeSystem.h"
namespace {
std::vector<Register> gpr_backups = {make_gpr(Reg::GP), make_gpr(Reg::S5), make_gpr(Reg::S4),
@@ -418,7 +418,7 @@ void Function::check_epilogue(const LinkedObjectFile& file) {
*
* Updates the guessed_name of the function and updates type_info
*/
void Function::find_global_function_defs(LinkedObjectFile& file) {
void Function::find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts) {
int state = 0;
int label_id = -1;
Register reg;
@@ -457,7 +457,8 @@ void Function::find_global_function_defs(LinkedObjectFile& file) {
auto& func = file.get_function_at_label(label_id);
assert(func.guessed_name.empty());
func.guessed_name.set_as_global(name);
get_type_info().inform_symbol(name, TypeSpec("function"));
dts.add_symbol(name, "function");
;
// todo - inform function.
}
@@ -549,4 +550,42 @@ void Function::find_method_defs(LinkedObjectFile& file) {
}
}
}
}
void Function::add_basic_op(std::shared_ptr<IR> op, int start_instr, int end_instr) {
op->is_basic_op = true;
assert(end_instr > start_instr);
for (int i = start_instr; i < end_instr; i++) {
instruction_to_basic_op[i] = basic_ops.size();
}
basic_op_to_instruction[basic_ops.size()] = start_instr;
basic_ops.push_back(op);
}
bool Function::instr_starts_basic_op(int idx) {
auto op = instruction_to_basic_op.find(idx);
if (op != instruction_to_basic_op.end()) {
auto start_instr = basic_op_to_instruction.at(op->second);
return start_instr == idx;
}
return false;
}
IR* Function::get_basic_op_at_instr(int idx) {
return basic_ops.at(instruction_to_basic_op.at(idx)).get();
}
int Function::get_basic_op_count() {
return basic_ops.size();
}
int Function::get_failed_basic_op_count() {
int count = 0;
for (auto& x : basic_ops) {
if (dynamic_cast<IR_Failed*>(x.get())) {
count++;
}
}
return count;
}
+13 -1
View File
@@ -8,6 +8,9 @@
#include "decompiler/Disasm/Instruction.h"
#include "BasicBlocks.h"
#include "CfgVtx.h"
#include "decompiler/IR/IR.h"
class DecompilerTypeSystem;
struct FunctionName {
enum class FunctionKind {
@@ -60,8 +63,14 @@ class Function {
public:
Function(int _start_word, int _end_word);
void analyze_prologue(const LinkedObjectFile& file);
void find_global_function_defs(LinkedObjectFile& file);
void find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
void find_method_defs(LinkedObjectFile& file);
void add_basic_op(std::shared_ptr<IR> op, int start_instr, int end_instr);
bool has_basic_ops() { return !basic_ops.empty(); }
bool instr_starts_basic_op(int idx);
IR* get_basic_op_at_instr(int idx);
int get_basic_op_count();
int get_failed_basic_op_count();
int segment = -1;
int start_word = -1;
@@ -115,6 +124,9 @@ class Function {
private:
void check_epilogue(const LinkedObjectFile& file);
std::vector<std::shared_ptr<IR>> basic_ops;
std::unordered_map<int, int> instruction_to_basic_op;
std::unordered_map<int, int> basic_op_to_instruction;
};
#endif // NEXT_FUNCTION_H
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
/*!
* @file BasicOpBuilder.h
* Analyzes a basic block and converts instructions to BasicOps.
* These will be used later to convert the Cfg into the nested IR format.
*/
#pragma once
class Function;
struct BasicBlock;
class LinkedObjectFile;
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file);
+391
View File
@@ -0,0 +1,391 @@
#include "IR.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
std::string IR::print(const LinkedObjectFile& file) const {
return to_form(file)->toStringPretty();
}
std::shared_ptr<Form> IR_Register::to_form(const LinkedObjectFile& file) const {
(void)file;
return toForm(reg.to_charp());
}
std::shared_ptr<Form> IR_Set::to_form(const LinkedObjectFile& file) const {
return buildList(toForm("set!"), dst->to_form(file), src->to_form(file));
}
std::shared_ptr<Form> IR_Store::to_form(const LinkedObjectFile& file) const {
std::string store_operator;
switch (kind) {
case FLOAT:
store_operator = "s.f";
break;
case INTEGER:
switch (size) {
case 1:
store_operator = "s.b";
break;
case 2:
store_operator = "s.h";
break;
case 4:
store_operator = "s.w";
break;
case 8:
store_operator = "s.d";
break;
case 16:
store_operator = "s.q";
break;
default:
assert(false);
}
break;
default:
assert(false);
}
return buildList(toForm(store_operator), dst->to_form(file), src->to_form(file));
}
std::shared_ptr<Form> IR_Failed::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("INVALID-OPERATION");
}
std::shared_ptr<Form> IR_Symbol::to_form(const LinkedObjectFile& file) const {
(void)file;
return toForm("'" + name);
}
std::shared_ptr<Form> IR_SymbolValue::to_form(const LinkedObjectFile& file) const {
(void)file;
return toForm(name);
}
std::shared_ptr<Form> IR_StaticAddress::to_form(const LinkedObjectFile& file) const {
// return buildList(toForm("&"), file.get_label_name(label_id));
return toForm(file.get_label_name(label_id));
}
std::shared_ptr<Form> IR_Load::to_form(const LinkedObjectFile& file) const {
std::string load_operator;
switch (kind) {
case FLOAT:
load_operator = "l.f";
break;
case UNSIGNED:
switch (size) {
case 1:
load_operator = "l.bu";
break;
case 2:
load_operator = "l.hu";
break;
case 4:
load_operator = "l.wu";
break;
case 8:
load_operator = "l.d";
break;
case 16:
load_operator = "l.q";
break;
default:
assert(false);
}
break;
case SIGNED:
switch (size) {
case 1:
load_operator = "l.bs";
break;
case 2:
load_operator = "l.hs";
break;
case 4:
load_operator = "l.ws";
break;
default:
assert(false);
}
break;
default:
assert(false);
}
return buildList(toForm(load_operator), location->to_form(file));
}
std::shared_ptr<Form> IR_FloatMath2::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
case DIV:
math_operator = "/.f";
break;
case MUL:
math_operator = "*.f";
break;
case ADD:
math_operator = "+.f";
break;
case SUB:
math_operator = "-.f";
break;
case MIN:
math_operator = "min.f";
break;
case MAX:
math_operator = "max.f";
break;
default:
assert(false);
}
return buildList(toForm(math_operator), arg0->to_form(file), arg1->to_form(file));
}
std::shared_ptr<Form> IR_IntMath2::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
case ADD:
math_operator = "+.i";
break;
case SUB:
math_operator = "-.i";
break;
case MUL_SIGNED:
math_operator = "*.si";
break;
case MUL_UNSIGNED:
math_operator = "*.ui";
break;
case DIV_SIGNED:
math_operator = "/.si";
break;
case MOD_SIGNED:
math_operator = "mod.si";
break;
case DIV_UNSIGNED:
math_operator = "/.ui";
break;
case MOD_UNSIGNED:
math_operator = "mod.ui";
break;
case OR:
math_operator = "logior";
break;
case AND:
math_operator = "logand";
break;
case NOR:
math_operator = "lognor";
break;
case XOR:
math_operator = "logxor";
break;
case LEFT_SHIFT:
math_operator = "shl";
break;
case RIGHT_SHIFT_ARITH:
math_operator = "sar";
break;
case RIGHT_SHIFT_LOGIC:
math_operator = "shr";
break;
default:
assert(false);
}
return buildList(toForm(math_operator), arg0->to_form(file), arg1->to_form(file));
}
std::shared_ptr<Form> IR_IntMath1::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
case NOT:
math_operator = "lognot";
break;
default:
assert(false);
}
return buildList(toForm(math_operator), arg->to_form(file));
}
std::shared_ptr<Form> IR_FloatMath1::to_form(const LinkedObjectFile& file) const {
std::string math_operator;
switch (kind) {
case FLOAT_TO_INT:
math_operator = "int<-float";
break;
case INT_TO_FLOAT:
math_operator = "float<-int";
break;
case ABS:
math_operator = "abs.f";
break;
case NEG:
math_operator = "neg.f";
break;
case SQRT:
math_operator = "sqrt.f";
break;
default:
assert(false);
}
return buildList(toForm(math_operator), arg->to_form(file));
}
std::shared_ptr<Form> IR_Call::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("call!");
}
std::shared_ptr<Form> IR_IntegerConstant::to_form(const LinkedObjectFile& file) const {
(void)file;
return toForm(std::to_string(value));
}
std::shared_ptr<Form> BranchDelay::to_form(const LinkedObjectFile& file) const {
(void)file;
switch (kind) {
case NOP:
return buildList("nop");
case SET_REG_FALSE:
return buildList(toForm("set!"), destination->to_form(file), "'#f");
case SET_REG_TRUE:
return buildList(toForm("set!"), destination->to_form(file), "'#t");
case SET_REG_REG:
return buildList(toForm("set!"), destination->to_form(file), source->to_form(file));
case UNKNOWN:
return buildList("unknown-branch-delay");
default:
assert(false);
}
}
std::shared_ptr<Form> IR_Nop::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("nop!");
}
int Condition::num_args() const {
switch (kind) {
case NOT_EQUAL:
case EQUAL:
case LESS_THAN_SIGNED:
case LESS_THAN_UNSIGNED:
case GREATER_THAN_SIGNED:
case GREATER_THAN_UNSIGNED:
case LEQ_SIGNED:
case GEQ_SIGNED:
case LEQ_UNSIGNED:
case GEQ_UNSIGNED:
case FLOAT_EQUAL:
case FLOAT_NOT_EQUAL:
case FLOAT_LESS_THAN:
case FLOAT_GEQ:
return 2;
case ZERO:
case NONZERO:
case FALSE:
case TRUTHY:
return 1;
case ALWAYS:
return 0;
default:
assert(false);
}
}
std::shared_ptr<Form> Condition::to_form(const LinkedObjectFile& file) const {
int nargs = num_args();
std::string condtion_operator;
switch (kind) {
case NOT_EQUAL:
condtion_operator = "!=";
break;
case EQUAL:
condtion_operator = "=";
break;
case LESS_THAN_SIGNED:
condtion_operator = "<.si";
break;
case LESS_THAN_UNSIGNED:
condtion_operator = "<.ui";
break;
case GREATER_THAN_SIGNED:
condtion_operator = ">.si";
break;
case GREATER_THAN_UNSIGNED:
condtion_operator = ">.ui";
break;
case LEQ_SIGNED:
condtion_operator = "<=.si";
break;
case GEQ_SIGNED:
condtion_operator = ">=.si";
break;
case LEQ_UNSIGNED:
condtion_operator = "<=.ui";
break;
case GEQ_UNSIGNED:
condtion_operator = ">=.ui";
break;
case ZERO:
condtion_operator = "zero?";
break;
case NONZERO:
condtion_operator = "nonzero?";
break;
case FALSE:
condtion_operator = "not";
break;
case TRUTHY:
condtion_operator = "";
break;
case ALWAYS:
condtion_operator = "'#t";
break;
case FLOAT_EQUAL:
condtion_operator = "=.f";
break;
case FLOAT_NOT_EQUAL:
condtion_operator = "!=.f";
break;
case FLOAT_LESS_THAN:
condtion_operator = "<.f";
break;
case FLOAT_GEQ:
condtion_operator = ">=.f";
break;
default:
assert(false);
}
if (nargs == 2) {
return buildList(toForm(condtion_operator), src0->to_form(file), src1->to_form(file));
} else if (nargs == 1) {
if (condtion_operator.empty()) {
return src0->to_form(file);
} else {
return buildList(toForm(condtion_operator), src0->to_form(file));
}
} else if (nargs == 0) {
return toForm(condtion_operator);
} else {
assert(false);
}
}
std::shared_ptr<Form> IR_Branch::to_form(const LinkedObjectFile& file) const {
return buildList(toForm(likely ? "bl!" : "b!"), condition.to_form(file),
toForm(file.get_label_name(dest_label_idx)), branch_delay.to_form(file));
}
std::shared_ptr<Form> IR_Compare::to_form(const LinkedObjectFile& file) const {
return condition.to_form(file);
}
std::shared_ptr<Form> IR_Suspend::to_form(const LinkedObjectFile& file) const {
(void)file;
return buildList("suspend!");
}
+243
View File
@@ -0,0 +1,243 @@
#ifndef JAK_IR_H
#define JAK_IR_H
#include <cassert>
#include "decompiler/Disasm/Register.h"
#include "decompiler/util/LispPrint.h"
class LinkedObjectFile;
class IR {
public:
virtual std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const = 0;
std::string print(const LinkedObjectFile& file) const;
bool is_basic_op = false;
};
class IR_Failed : public IR {
public:
IR_Failed() = default;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Register : public IR {
public:
IR_Register(Register _reg, int _instr_idx) : reg(_reg), instr_idx(_instr_idx) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
Register reg;
int instr_idx = -1;
};
class IR_Set : public IR {
public:
enum Kind {
REG_64,
LOAD,
STORE,
SYM_LOAD,
SYM_STORE,
FPR_TO_GPR64,
GPR_TO_FPR,
REG_FLT,
REG_I128
} kind;
IR_Set(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src)
: kind(_kind), dst(std::move(_dst)), src(std::move(_src)) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
std::shared_ptr<IR> dst, src;
std::shared_ptr<IR> clobber = nullptr;
};
class IR_Store : public IR_Set {
public:
enum Kind { INTEGER, FLOAT } kind;
IR_Store(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src, int _size)
: IR_Set(IR_Set::LOAD, std::move(_dst), std::move(_src)), kind(_kind), size(_size) {}
int size;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Symbol : public IR {
public:
IR_Symbol(std::string _name) : name(std::move(_name)) {}
std::string name;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_SymbolValue : public IR {
public:
IR_SymbolValue(std::string _name) : name(std::move(_name)) {}
std::string name;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_StaticAddress : public IR {
public:
IR_StaticAddress(int _label_id) : label_id(_label_id) {}
int label_id = -1;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Load : public IR {
public:
enum Kind { UNSIGNED, SIGNED, FLOAT } kind;
IR_Load(Kind _kind, int _size, const std::shared_ptr<IR>& _location)
: kind(_kind), size(_size), location(_location) {}
int size;
std::shared_ptr<IR> location;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_FloatMath2 : public IR {
public:
enum Kind { DIV, MUL, ADD, SUB, MIN, MAX } kind;
IR_FloatMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
std::shared_ptr<IR> arg0, arg1;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_FloatMath1 : public IR {
public:
enum Kind { FLOAT_TO_INT, INT_TO_FLOAT, ABS, NEG, SQRT } kind;
IR_FloatMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
std::shared_ptr<IR> arg;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_IntMath2 : public IR {
public:
enum Kind {
ADD,
SUB,
MUL_SIGNED,
DIV_SIGNED,
MOD_SIGNED,
DIV_UNSIGNED,
MOD_UNSIGNED,
OR,
AND,
NOR,
XOR,
LEFT_SHIFT,
RIGHT_SHIFT_ARITH,
RIGHT_SHIFT_LOGIC,
MUL_UNSIGNED
} kind;
IR_IntMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
std::shared_ptr<IR> arg0, arg1;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_IntMath1 : public IR {
public:
enum Kind { NOT } kind;
IR_IntMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
std::shared_ptr<IR> arg;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Call : public IR {
public:
IR_Call() = default;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_IntegerConstant : public IR {
public:
int64_t value;
explicit IR_IntegerConstant(int64_t _value) : value(_value) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
struct BranchDelay {
enum Kind { NOP, SET_REG_FALSE, SET_REG_TRUE, SET_REG_REG, UNKNOWN } kind;
std::shared_ptr<IR> destination = nullptr, source = nullptr;
BranchDelay(Kind _kind) : kind(_kind) {}
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const;
};
struct Condition {
enum Kind {
NOT_EQUAL,
EQUAL,
LESS_THAN_SIGNED,
GREATER_THAN_SIGNED,
LEQ_SIGNED,
GEQ_SIGNED,
LESS_THAN_UNSIGNED,
GREATER_THAN_UNSIGNED,
LEQ_UNSIGNED,
GEQ_UNSIGNED,
ZERO,
NONZERO,
FALSE,
TRUTHY,
ALWAYS,
FLOAT_EQUAL,
FLOAT_NOT_EQUAL,
FLOAT_LESS_THAN,
FLOAT_GEQ
} kind;
Condition(Kind _kind,
std::shared_ptr<IR> _src0,
std::shared_ptr<IR> _src1,
std::shared_ptr<IR> _clobber)
: kind(_kind), src0(std::move(_src0)), src1(std::move(_src1)), clobber(std::move(_clobber)) {
int nargs = num_args();
if (nargs == 2) {
assert(src0 && src1);
} else if (nargs == 1) {
assert(src0 && !src1);
} else if (nargs == 0) {
assert(!src0 && !src1);
}
}
int num_args() const;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const;
std::shared_ptr<IR> src0, src1, clobber;
};
class IR_Branch : public IR {
public:
IR_Branch(Condition _condition, int _dest_label_idx, BranchDelay _branch_delay, bool _likely)
: condition(std::move(_condition)),
dest_label_idx(_dest_label_idx),
branch_delay(std::move(_branch_delay)),
likely(_likely) {}
Condition condition;
int dest_label_idx;
BranchDelay branch_delay;
bool likely;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Compare : public IR {
public:
explicit IR_Compare(Condition _condition) : condition(std::move(_condition)) {}
Condition condition;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Nop : public IR {
public:
IR_Nop() = default;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
class IR_Suspend : public IR {
public:
IR_Suspend() = default;
std::shared_ptr<Form> to_form(const LinkedObjectFile& file) const override;
};
#endif // JAK_IR_H
@@ -483,6 +483,8 @@ void LinkedObjectFile::process_fp_relative_links() {
if (pprev_instr && pprev_instr->kind == InstructionKind::LUI) {
assert(pprev_instr->get_dst(0).get_reg() == offset_reg);
additional_offset = (1 << 16) * pprev_instr->get_imm_src().get_imm();
pprev_instr->get_imm_src().set_label(
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
}
atom.set_label(
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
@@ -554,6 +556,12 @@ std::string LinkedObjectFile::print_disassembly() {
auto& word = words_by_seg[seg].at(func.start_word + i);
append_word_to_string(result, word);
} else {
if (func.has_basic_ops() && func.instr_starts_basic_op(i)) {
if (line.length() < 40) {
line.append(40 - line.length(), ' ');
}
line += ";; " + func.get_basic_op_at_instr(i)->print(*this);
}
result += line + "\n";
}
@@ -8,7 +8,7 @@
#include <cstring>
#include "LinkedObjectFileCreation.h"
#include "decompiler/config.h"
#include "decompiler/TypeSystem/TypeInfo.h"
#include "decompiler/util/DecompilerTypeSystem.h"
// There are three link versions:
// V2 - not really in use anymore, but V4 will resue logic from it (and the game didn't rename the
@@ -86,8 +86,9 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
uint32_t link_ptr_offset,
SymbolLinkKind kind,
const char* name,
int seg_id) {
get_type_info().inform_symbol_with_no_type_info(name);
int seg_id,
DecompilerTypeSystem& dts) {
dts.add_symbol(name);
auto initial_offset = code_ptr_offset;
do {
auto table_value = data.at(link_ptr_offset);
@@ -130,7 +131,7 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
word_kind = LinkedWord::EMPTY_PTR;
break;
case SymbolLinkKind::TYPE:
get_type_info().inform_type(name);
dts.add_symbol(name, "type");
word_kind = LinkedWord::TYPE_PTR;
break;
default:
@@ -162,8 +163,9 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
uint32_t link_ptr,
SymbolLinkKind kind,
const char* name,
int seg) {
get_type_info().inform_symbol_with_no_type_info(name);
int seg,
DecompilerTypeSystem& dts) {
dts.add_symbol(name);
auto initial_offset = code_ptr;
do {
// seek, with a variable length encoding that sucks.
@@ -187,7 +189,7 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
word_kind = LinkedWord::EMPTY_PTR;
break;
case SymbolLinkKind::TYPE:
get_type_info().inform_type(name);
dts.add_symbol(name, "type");
word_kind = LinkedWord::TYPE_PTR;
break;
default:
@@ -223,7 +225,8 @@ static uint32_t align16(uint32_t in) {
*/
static void link_v4(LinkedObjectFile& f,
const std::vector<uint8_t>& data,
const std::string& name) {
const std::string& name,
DecompilerTypeSystem& dts) {
// read the V4 header to find where the link data really is
const auto* header = (const LinkHeaderV4*)&data.at(0);
uint32_t link_data_offset = header->code_size + sizeof(LinkHeaderV4); // no basic offset
@@ -358,7 +361,7 @@ static void link_v4(LinkedObjectFile& f,
link_ptr_offset += strlen(s_name) + 1;
f.stats.total_v2_symbol_count++;
link_ptr_offset = c_symlink2(f, data, code_offset, link_ptr_offset, kind, s_name, 0);
link_ptr_offset = c_symlink2(f, data, code_offset, link_ptr_offset, kind, s_name, 0, dts);
if (data.at(link_ptr_offset) == 0)
break;
}
@@ -384,7 +387,8 @@ static void assert_string_empty_after(const char* str, int size) {
static void link_v5(LinkedObjectFile& f,
const std::vector<uint8_t>& data,
const std::string& name) {
const std::string& name,
DecompilerTypeSystem& dts) {
auto header = (const LinkHeaderV5*)(&data.at(0));
if (header->n_segments == 1) {
printf("abandon %s!\n", name.c_str());
@@ -539,10 +543,10 @@ static void link_v5(LinkedObjectFile& f,
if (std::string("_empty_") == sname) {
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
SymbolLinkKind::EMPTY_LIST, sname, seg_id);
SymbolLinkKind::EMPTY_LIST, sname, seg_id, dts);
} else {
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
SymbolLinkKind::SYMBOL, sname, seg_id);
SymbolLinkKind::SYMBOL, sname, seg_id, dts);
}
} else if ((reloc & 0x3f) == 0x3f) {
assert(false); // todo, does this ever get hit?
@@ -556,7 +560,7 @@ static void link_v5(LinkedObjectFile& f,
const char* sname = (const char*)(&data.at(link_ptr));
link_ptr += strlen(sname) + 1;
link_ptr = c_symlink2(f, data, segment_data_offsets[seg_id], link_ptr,
SymbolLinkKind::TYPE, sname, seg_id);
SymbolLinkKind::TYPE, sname, seg_id, dts);
}
sub_link_ptr = link_ptr;
@@ -586,7 +590,8 @@ static void link_v5(LinkedObjectFile& f,
static void link_v3(LinkedObjectFile& f,
const std::vector<uint8_t>& data,
const std::string& name) {
const std::string& name,
DecompilerTypeSystem& dts) {
auto header = (const LinkHeaderV3*)(&data.at(0));
assert(name == header->name);
assert(header->segments == 3);
@@ -739,7 +744,7 @@ static void link_v3(LinkedObjectFile& f,
// methods todo
s_name = (const char*)(&data.at(link_ptr));
get_type_info().inform_type_method_count(s_name, reloc & 0x7f);
// get_type_info().inform_type_method_count(s_name, reloc & 0x7f); todo
kind = SymbolLinkKind::TYPE;
}
@@ -750,7 +755,7 @@ static void link_v3(LinkedObjectFile& f,
link_ptr += strlen(s_name) + 1;
f.stats.v3_symbol_count++;
link_ptr = c_symlink3(f, data, base_ptr, link_ptr, kind, s_name, seg_id);
link_ptr = c_symlink3(f, data, base_ptr, link_ptr, kind, s_name, seg_id, dts);
}
segment_link_ends[seg_id] = link_ptr;
}
@@ -775,19 +780,21 @@ static void link_v3(LinkedObjectFile& f,
/*!
* Main function to generate LinkedObjectFiles from raw object data.
*/
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data, const std::string& name) {
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
const std::string& name,
DecompilerTypeSystem& dts) {
LinkedObjectFile result;
const auto* header = (const LinkHeaderCommon*)&data.at(0);
// use appropriate linker
if (header->version == 3) {
assert(header->type_tag == 0);
link_v3(result, data, name);
link_v3(result, data, name, dts);
} else if (header->version == 4) {
assert(header->type_tag == 0xffffffff);
link_v4(result, data, name);
link_v4(result, data, name, dts);
} else if (header->version == 5) {
link_v5(result, data, name);
link_v5(result, data, name, dts);
} else {
assert(false);
}
@@ -11,6 +11,9 @@
#include "LinkedObjectFile.h"
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data, const std::string& name);
class DecompilerTypeSystem;
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
const std::string& name,
DecompilerTypeSystem& dts);
#endif // NEXT_LINKEDOBJECTFILECREATION_H
+27 -10
View File
@@ -18,6 +18,7 @@
#include "common/util/Timer.h"
#include "common/util/FileUtil.h"
#include "decompiler/Function/BasicBlocks.h"
#include "decompiler/IR/BasicOpBuilder.h"
/*!
* Get a unique name for this object file.
@@ -64,6 +65,8 @@ ObjectFileData& ObjectFileDB::lookup_record(ObjectFileRecord rec) {
*/
ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos) {
Timer timer;
printf("- Loading Types...\n");
dts.parse_type_defs({"decompiler", "config", "all-types.gc"});
printf("- Initializing ObjectFileDB...\n");
for (auto& dgo : _dgos) {
@@ -71,7 +74,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos) {
}
printf("ObjectFileDB Initialized:\n");
printf(" total dgos: %lld\n", _dgos.size());
printf(" total dgos: %d\n", int(_dgos.size()));
printf(" total data: %d bytes\n", stats.total_dgo_bytes);
printf(" total objs: %d\n", stats.total_obj_files);
printf(" unique objs: %d\n", stats.unique_obj_files);
@@ -356,7 +359,7 @@ void ObjectFileDB::process_link_data() {
LinkedObjectFile::Stats combined_stats;
for_each_obj([&](ObjectFileData& obj) {
obj.linked_data = to_linked_object_file(obj.data, obj.record.name);
obj.linked_data = to_linked_object_file(obj.data, obj.record.name, dts);
combined_stats.add(obj.linked_data.stats);
});
@@ -543,7 +546,7 @@ void ObjectFileDB::analyze_functions() {
auto& func = data.linked_data.functions_by_seg.at(2).front();
assert(func.guessed_name.empty());
func.guessed_name.set_as_top_level();
func.find_global_function_defs(data.linked_data);
func.find_global_function_defs(data.linked_data, dts);
func.find_method_defs(data.linked_data);
}
});
@@ -591,6 +594,8 @@ void ObjectFileDB::analyze_functions() {
int total_nontrivial_functions = 0;
int total_resolved_nontrivial_functions = 0;
int total_named_functions = 0;
int total_basic_ops = 0;
int total_failed_basic_ops = 0;
std::map<int, std::vector<std::string>> unresolved_by_length;
if (get_config().find_basic_blocks) {
@@ -601,10 +606,18 @@ void ObjectFileDB::analyze_functions() {
total_basic_blocks += blocks.size();
func.basic_blocks = blocks;
total_functions++;
if (!func.suspected_asm) {
func.analyze_prologue(data.linked_data);
func.cfg = build_cfg(data.linked_data, segment_id, func);
total_functions++;
for (auto& block : func.basic_blocks) {
if (block.end_word > block.start_word) {
add_basic_ops_to_block(&func, block, &data.linked_data);
}
}
total_basic_ops += func.get_basic_op_count();
total_failed_basic_ops += func.get_failed_basic_op_count();
if (func.cfg->is_fully_resolved()) {
resolved_cfg_functions++;
}
@@ -640,11 +653,15 @@ void ObjectFileDB::analyze_functions() {
total_nontrivial_functions,
100.f * float(total_resolved_nontrivial_functions) / float(total_nontrivial_functions));
for (auto& kv : unresolved_by_length) {
printf("LEN %d\n", kv.first);
for (auto& x : kv.second) {
printf(" %s\n", x.c_str());
}
}
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
printf(" %d/%d basic ops converted successfully (%.2f%%)\n", successful_basic_ops,
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
// for (auto& kv : unresolved_by_length) {
// printf("LEN %d\n", kv.first);
// for (auto& x : kv.second) {
// printf(" %s\n", x.c_str());
// }
// }
}
}
+2
View File
@@ -15,6 +15,7 @@
#include <unordered_map>
#include <vector>
#include "LinkedObjectFile.h"
#include "decompiler/util/DecompilerTypeSystem.h"
/*!
* A "record" which can be used to identify an object file.
@@ -55,6 +56,7 @@ class ObjectFileDB {
void write_disassembly(const std::string& output_dir, bool disassemble_objects_without_functions);
void analyze_functions();
ObjectFileData& lookup_record(ObjectFileRecord rec);
DecompilerTypeSystem dts;
private:
void get_objs_from_dgo(const std::string& filename);
-1
View File
@@ -1 +0,0 @@
#include "GoalFunction.h"
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#ifndef JAK_DISASSEMBLER_GOALFUNCTION_H
#define JAK_DISASSEMBLER_GOALFUNCTION_H
class GoalFunction {
public:
// enum Kind {
// GLOBAL_FUNCTION,
// ANON_FUNCTION,
// METHOD,
// BEHAVIOR,
// UNKNOWN
// };
};
#endif // JAK_DISASSEMBLER_GOALFUNCTION_H
-1
View File
@@ -1 +0,0 @@
#include "GoalSymbol.h"
-39
View File
@@ -1,39 +0,0 @@
#pragma once
#ifndef JAK_DISASSEMBLER_GOALSYMBOL_H
#define JAK_DISASSEMBLER_GOALSYMBOL_H
#include <cassert>
#include <string>
#include "TypeSpec.h"
class GoalSymbol {
public:
GoalSymbol() = default;
explicit GoalSymbol(std::string name) : m_name(std::move(name)) {}
GoalSymbol(std::string name, TypeSpec ts) : m_name(std::move(name)), m_type(std::move(ts)) {
m_has_type_info = true;
}
bool has_type_info() const { return m_has_type_info; }
void set_type(TypeSpec ts) {
if (m_has_type_info) {
if (ts != m_type) {
printf("symbol %s %s -> %s", m_name.c_str(), m_type.to_string().c_str(),
ts.to_string().c_str());
assert(false);
}
}
m_has_type_info = true;
m_type = std::move(ts);
}
private:
std::string m_name;
TypeSpec m_type;
bool m_has_type_info = false;
};
#endif // JAK_DISASSEMBLER_GOALSYMBOL_H
-13
View File
@@ -1,13 +0,0 @@
#include "GoalType.h"
void GoalType::set_methods(int n) {
if (m_method_count_set) {
if (m_method_count != n) {
printf("Type %s had %d methods, set_methods tried to change it to %d\n", m_name.c_str(),
m_method_count, n);
}
} else {
m_method_count = n;
m_method_count_set = true;
}
}
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#ifndef JAK_DISASSEMBLER_GOALTYPE_H
#define JAK_DISASSEMBLER_GOALTYPE_H
#include <string>
class GoalType {
public:
GoalType() = default;
GoalType(std::string name) : m_name(std::move(name)) {}
bool has_info() const { return m_has_info; }
bool has_method_count() const { return m_method_count_set; }
void set_methods(int n);
private:
std::string m_name;
bool m_has_info = false;
bool m_method_count_set = false;
int m_method_count = -1;
};
#endif // JAK_DISASSEMBLER_GOALTYPE_H
-99
View File
@@ -1,99 +0,0 @@
#include "TypeInfo.h"
#include <utility>
namespace {
TypeInfo gTypeInfo;
}
TypeInfo::TypeInfo() {
GoalType type_type("type");
m_types["type"] = type_type;
GoalSymbol type_symbol("type");
m_symbols["type"] = type_symbol;
}
TypeInfo& get_type_info() {
return gTypeInfo;
}
std::string TypeInfo::get_summary() {
int total_symbols = 0;
int syms_with_type_info = 0;
for (const auto& kv : m_symbols) {
total_symbols++;
if (kv.second.has_type_info()) {
syms_with_type_info++;
}
}
int total_types = 0;
int types_with_info = 0;
int types_with_method_count = 0;
for (const auto& kv : m_types) {
total_types++;
if (kv.second.has_info()) {
types_with_info++;
}
if (kv.second.has_method_count()) {
types_with_method_count++;
}
}
char buffer[1024];
sprintf(buffer,
"TypeInfo Summary\n"
" Total Symbols: %d\n"
" with type info: %d (%.2f%%)\n"
" Total Types: %d\n"
" with info: %d (%.2f%%)\n"
" with method count: %d (%.2f%%)\n",
total_symbols, syms_with_type_info,
100.f * float(syms_with_type_info) / float(total_symbols), total_types, types_with_info,
100.f * float(types_with_info) / float(total_types), types_with_method_count,
100.f * float(types_with_method_count) / float(total_types));
return {buffer};
}
/*!
* inform TypeInfo that there is a symbol with this name.
* Provides no type info - if some is already known there is no change.
*/
void TypeInfo::inform_symbol_with_no_type_info(const std::string& name) {
if (m_symbols.find(name) == m_symbols.end()) {
// only add it if we haven't seen this already.
GoalSymbol sym(name);
m_symbols[name] = sym;
}
}
void TypeInfo::inform_symbol(const std::string& name, TypeSpec type) {
inform_symbol_with_no_type_info(name);
m_symbols.at(name).set_type(std::move(type));
}
void TypeInfo::inform_type(const std::string& name) {
if (m_types.find(name) == m_types.end()) {
GoalType typ(name);
m_types[name] = typ;
}
inform_symbol(name, TypeSpec("type"));
}
void TypeInfo::inform_type_method_count(const std::string& name, int methods) {
// create type and symbol
inform_type(name);
m_types.at(name).set_methods(methods);
}
std::string TypeInfo::get_all_symbols_debug() {
std::string result = "const char* all_syms[" + std::to_string(m_symbols.size()) + "] = {";
for (auto& x : m_symbols) {
result += "\"" + x.first + "\",";
}
if (!result.empty()) {
result.pop_back();
}
return result + "};";
}
-32
View File
@@ -1,32 +0,0 @@
#pragma once
#ifndef JAK_DISASSEMBLER_TYPEINFO_H
#define JAK_DISASSEMBLER_TYPEINFO_H
#include <unordered_map>
#include "GoalType.h"
#include "GoalFunction.h"
#include "GoalSymbol.h"
class TypeInfo {
public:
TypeInfo();
void inform_symbol(const std::string& name, TypeSpec type);
void inform_symbol_with_no_type_info(const std::string& name);
void inform_type(const std::string& name);
void inform_type_method_count(const std::string& name, int methods);
std::string get_summary();
std::string get_all_symbols_debug();
private:
std::unordered_map<std::string, GoalType> m_types;
std::unordered_map<std::string, GoalFunction> m_global_functions;
std::unordered_map<std::string, GoalSymbol> m_symbols;
};
TypeInfo& get_type_info();
void init_type_info();
#endif // JAK_DISASSEMBLER_TYPEINFO_H
-51
View File
@@ -1,51 +0,0 @@
#include "TypeSpec.h"
std::string TypeSpec::to_string() const {
if (m_args.empty()) {
return m_base_type;
} else {
std::string result = "(";
result += m_base_type;
for (const auto& x : m_args) {
result += " ";
result += x.to_string();
}
result += ")";
return result;
}
}
std::shared_ptr<Form> TypeSpec::to_form() const {
if (m_args.empty()) {
return toForm(m_base_type);
} else {
std::vector<std::shared_ptr<Form>> all;
all.push_back(toForm(m_base_type));
for (const auto& x : m_args) {
all.push_back(x.to_form());
}
return buildList(all);
}
}
bool TypeSpec::operator==(const TypeSpec& other) const {
if (m_base_type != other.m_base_type) {
return false;
}
if (m_args.size() != other.m_args.size()) {
return false;
}
for (size_t i = 0; i < m_args.size(); i++) {
if (m_args[i] != other.m_args[i]) {
return false;
}
}
return true;
}
bool TypeSpec::operator!=(const TypeSpec& other) const {
return !(*this == other);
}
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#ifndef JAK_DISASSEMBLER_TYPESPEC_H
#define JAK_DISASSEMBLER_TYPESPEC_H
#include <string>
#include <vector>
#include "decompiler/util/LispPrint.h"
class TypeSpec {
public:
TypeSpec() = default;
explicit TypeSpec(std::string base_type) : m_base_type(std::move(base_type)) {}
TypeSpec(std::string base_type, std::vector<TypeSpec> args)
: m_base_type(std::move(base_type)), m_args(std::move(args)) {}
std::string to_string() const;
std::shared_ptr<Form> to_form() const;
bool operator==(const TypeSpec& other) const;
bool operator!=(const TypeSpec& other) const;
private:
std::string m_base_type;
std::vector<TypeSpec> m_args;
};
#endif // JAK_DISASSEMBLER_TYPESPEC_H
File diff suppressed because it is too large Load Diff
+13 -5
View File
@@ -3,13 +3,14 @@
{
"game_version":1,
// the order here matters. KERNEL and GAME should go first
"dgo_names":["CGO/KERNEL.CGO"
, "CGO/GAME.CGO", "CGO/ENGINE.CGO"
"dgo_names":["CGO/KERNEL.CGO", "CGO/GAME.CGO"],
/*, "CGO/ENGINE.CGO"
, "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO", "DGO/DEM.DGO",
"DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO", "CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO",
"DGO/LAV.DGO", "DGO/MAI.DGO", "CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO", "DGO/ROL.DGO",
"DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO", "DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO",
"DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"],
"DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO"
],*/
"write_disassembly":true,
"write_hex_near_instructions":false,
@@ -29,9 +30,16 @@
"asm_functions_by_name":[
// gcommon
"ash", "abs", "min", "max", "collide-do-primitives", "draw-bones-check-longest-edge-asm",
"ash", "abs", "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!",
// pskernel
"resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception",
"kernel-read", "kernel-read-function", "kernel-write", "kernel-write-function", "kernel-copy-to-kernel-ram",
"collide-do-primitives", "draw-bones-check-longest-edge-asm",
"sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)",
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc", "dma-sync-fast", "bsp-camera-asm",
"(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc",
"dma-sync-fast", "bsp-camera-asm",
"generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture", "(method 10 collide-edge-hold-list)"
]
}
+5 -4
View File
@@ -4,7 +4,7 @@
#include "ObjectFile/ObjectFileDB.h"
#include "config.h"
#include "util/FileIO.h"
#include "TypeSystem/TypeInfo.h"
#include "common/util/FileUtil.h"
int main(int argc, char** argv) {
@@ -48,8 +48,9 @@ int main(int argc, char** argv) {
db.write_disassembly(out_folder, get_config().disassemble_objects_without_functions);
}
printf("%s\n", get_type_info().get_summary().c_str());
// printf("%d\n", InstructionKind::EE_OP_MAX);
// printf("%s\n", get_type_info().get_all_symbols_debug().c_str());
// todo print type summary
// printf("%s\n", get_type_info().get_summary().c_str());
file_util::write_text_file(combine_path(out_folder, "all-syms.gc"), db.dts.dump_symbol_types());
return 0;
}
+78
View File
@@ -0,0 +1,78 @@
#include "DecompilerTypeSystem.h"
#include "common/goos/Reader.h"
#include "common/type_system/deftype.h"
DecompilerTypeSystem::DecompilerTypeSystem() {
ts.add_builtin_types();
}
namespace {
// some utilities for parsing the type def file
goos::Object& car(goos::Object& pair) {
if (pair.is_pair()) {
return pair.as_pair()->car;
} else {
throw std::runtime_error("car called on something that wasn't a pair: " + pair.print());
}
}
goos::Object& cdr(goos::Object& pair) {
if (pair.is_pair()) {
return pair.as_pair()->cdr;
} else {
throw std::runtime_error("cdr called on something that wasn't a pair");
}
}
template <typename T>
void for_each_in_list(goos::Object& list, T f) {
goos::Object* iter = &list;
while (iter->is_pair()) {
f(car(*iter));
iter = &cdr(*iter);
}
if (!iter->is_empty_list()) {
throw std::runtime_error("malformed list");
}
}
} // namespace
void DecompilerTypeSystem::parse_type_defs(const std::vector<std::string>& file_path) {
goos::Reader reader;
auto read = reader.read_from_file(file_path);
auto data = cdr(read);
for_each_in_list(data, [&](goos::Object& o) {
if (car(o).as_symbol()->name == "define-extern") {
auto* rest = &cdr(o);
auto sym_name = car(*rest);
rest = &cdr(*rest);
auto sym_type = car(*rest);
if (!cdr(*rest).is_empty_list()) {
throw std::runtime_error("malformed define-extern");
}
add_symbol(sym_name.as_symbol()->name, parse_typespec(&ts, sym_type));
} else if (car(o).as_symbol()->name == "deftype") {
parse_deftype(cdr(o), &ts);
} else {
throw std::runtime_error("Decompiler cannot parse " + car(o).print());
}
});
}
std::string DecompilerTypeSystem::dump_symbol_types() {
assert(symbol_add_order.size() == symbols.size());
std::string result;
for (auto& symbol_name : symbol_add_order) {
auto skv = symbol_types.find(symbol_name);
if (skv == symbol_types.end()) {
result += fmt::format(";;(define-extern {} object) ;; unknown type\n", symbol_name);
} else {
result += fmt::format("(define-extern {} {})\n", symbol_name, skv->second.print());
}
}
return result;
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef JAK_DECOMPILERTYPESYSTEM_H
#define JAK_DECOMPILERTYPESYSTEM_H
#include "common/type_system/TypeSystem.h"
#include "third-party/fmt/format.h"
class DecompilerTypeSystem {
public:
DecompilerTypeSystem();
TypeSystem ts;
std::unordered_map<std::string, TypeSpec> symbol_types;
std::unordered_set<std::string> symbols;
std::vector<std::string> symbol_add_order;
void add_symbol(const std::string& name) {
if (symbols.find(name) == symbols.end()) {
symbols.insert(name);
symbol_add_order.push_back(name);
}
}
void add_symbol(const std::string& name, const std::string& base_type) {
add_symbol(name, TypeSpec(base_type));
}
void add_symbol(const std::string& name, const TypeSpec& type_spec) {
add_symbol(name);
auto skv = symbol_types.find(name);
if (skv == symbol_types.end() || skv->second == type_spec) {
symbol_types[name] = type_spec;
} else {
if (ts.typecheck(type_spec, skv->second, "", false, false)) {
} else {
fmt::print("Attempting to redefine type of symbol {} from {} to {}\n", name,
skv->second.print(), type_spec.print());
throw std::runtime_error("Type redefinition");
}
}
}
void parse_type_defs(const std::vector<std::string>& file_path);
std::string dump_symbol_types();
};
#endif // JAK_DECOMPILERTYPESYSTEM_H
+11
View File
@@ -514,3 +514,14 @@ std::shared_ptr<Form> buildList(std::vector<std::shared_ptr<Form>>& forms) {
}
return buildList(forms.data(), forms.size());
}
std::shared_ptr<Form> buildList(std::vector<std::string>& forms) {
if (forms.empty()) {
return gSymbolTable.getEmptyPair();
}
std::vector<std::shared_ptr<Form>> f;
for (auto& x : forms) {
f.push_back(toForm(x));
}
return buildList(f.data(), f.size());
}
+1
View File
@@ -122,6 +122,7 @@ std::shared_ptr<Form> buildList(const std::string& str);
std::shared_ptr<Form> buildList(std::shared_ptr<Form> form);
std::shared_ptr<Form> buildList(std::vector<std::shared_ptr<Form>>& forms);
std::shared_ptr<Form> buildList(std::shared_ptr<Form>* forms, int count);
std::shared_ptr<Form> buildList(std::vector<std::string>& forms);
template <typename... Args>
std::shared_ptr<Form> buildList(const std::string& str, Args... rest) {
+21
View File
@@ -0,0 +1,21 @@
# Language Changes
## V0.1
- The GOAL language version has been set to 0.1
- Calling a function with unknown argument/return types is now an error instead of a warning
- Getting a method of an object or type with `method` returns the correct type for methods using the `_type_` feature
- The `object-new` macro will now type check arguments
- The size argument to `(method object new)` is now an `int` instead of `int32`
- Using `set!` incorrectly, like `(set! 1 2)` will now create an error instead of having no effect
- GOOS now has a `fmt` form which wraps `libfmt` for doing string formatting.
- GOOS now has an `error` form for throwing an error with a string to describe it
- GOAL `if` now throws errors on extra arguments instead of silently ignoring them
- The first 1 MB of GOAL memory now cannot be read/written/executed so dereferencing a GOAL null pointer will now segfault
- The runtime now accepts command line boot arguments
- The runtime now defaults to loading `KERNEL.CGO` and using its `kernel-dispatcher` function.
- The runtime now accepts a `-nokernel` parameter for running without `KERNEL.CGO`.
- The runtime will now refuse to load object files from another major GOAL version
- Using `&+` and `&+!` now produces a pointer with the same type as the original.
- There is a `&-` which returns a `uint` and works with basically any input types
- The `&` operator works on fields and elements in arrays
- The `&->` operator has been added
+277
View File
@@ -0,0 +1,277 @@
# GOAL Operations
## `div.s`
Suspected source
```
(/ 1.0 x)
```
where `x` is in a GPR:
```
lwc1 f0, L345(fp) ;; first argument prepared first?
mtc1 f1, a0 ;; second argument prepared second?
div.s f0, f0, f1
```
Sequence
- Compile first
- First to FPR
- Compile second
- Second to FPR
## `daddu`
Used for `int` and `uint` addition.
Two element form:
```
daddu v0, a0, a1
```
is `(+ a0 a1)` - the order in the opcode matches the order in the expression.
## `daddiu` to get a symbol
```
daddiu v0, s7, #t
```
Note for `#t`: `#t` is linked when the code literally has a `#t` in it. Other cases are currently unknown.
## `dsubu`
Used for `int` and `uint` subtraction.
## `mult3` (EE `mult`)
Used for `int` multiplication.
Like `daddu` for opcode ordering:
```
mult3 v0, a0, a1
```
is `(* a0 a1)`.
## `div`
Used for `int` division.
```
div a0, a1
mflo v0
```
is `(/ a0 a1)`.
and also for `int` mod
```
div a0, a1
mfhi v0
```
## `or` used to get the value of false
```
or v0, s7, r0
```
## `or` used as a bitwise or
```
or v0, a0, a1
```
is `(logior a0 a1)`
## `and` used as a bitwise and
```
and v0, a0, a1
```
is `(logand a0 a1)`.
```
(logand #xfffffff0 (+ (ash (-> thing field) 2) 43))
```
is
```
ld v1, L346(fp) ;; first arg to the and
lhu a0, 14(a0) ;; second arg evaluation...
dsll a0, a0, 2
daddiu a0, a0, 43
and v0, v1, a0 ;; and result, first, second
```
## `nor` used as a bitwise nor
```
nor v0, a0, a1
```
is `(lognor a0 a1)`
## `xor` used as a bitwise xor
```
xor v0, a0, a1
```
is `(logxor a0 a1)`
## `nor` used as a logical not
```
nor v0, a0, r0
```
is `(lognot a0)`
# Common "Idioms"
## `ash`
Variable shift (`ash`) is an inline function
```
or v1, a0, r0
bgezl a1, L306
dsllv v0, v1, a1
dsubu a0, r0, a1
dsrav v0, v1, a0
L306:
```
## `abs` of integer
```
or v0, a0, r0
bltzl v0, L302
dsubu v0, r0, v0
L302:
```
## `min` of integers
```
or v0, a0, r0
or v1, a1, r0
slt a0, v0, v1
movz v0, v1, a0
```
## `max` of integers
```
or v0, a0, r0
or v1, a1, r0
slt a0, v0, v1
movn v0, v1, a0
```
# Others
## Integer constants that are large
A constant of `0xfffffff0` is loaded with `ld`
## Access value of symbol
Seems to always use `lw`?
# Control Flow Info
## Begin-like forms flush everything always, immediately after compiling
Example in `vector` with flushing:
```
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function vector3s+!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
L8:
daddiu sp, sp, -16
sd fp, 8(sp)
or fp, t9, r0
daddiu v1, fp, L109 ;; string: "Add 2 vectors3."
lwc1 f0, 0(a1)
lwc1 f1, 0(a2)
add.s f0, f0, f1
swc1 f0, 0(a0)
lwc1 f0, 4(a1)
lwc1 f1, 4(a2)
add.s f0, f0, f1
swc1 f0, 4(a0)
lwc1 f0, 8(a1)
lwc1 f1, 8(a2)
add.s f0, f0, f1
swc1 f0, 8(a0)
or v0, a0, r0
ld fp, 8(sp)
jr ra
daddiu sp, sp, 16
```
The `daddiu v1, fp, L109` loads a `string` into the `v1` register which is never used, immediately after the prologue. This will only happen if the value is flushed. This is very likely a documentation comment that accidentally got included as a string constant. It's unused, so there was likely no consumer of the string that did the `flush` - it was done by the top level evaluation.
```
(defun vector3s+! (stuff)
"Add 2 vectors3." ;; oops, a string constant instead of a comment.
... ; rest of the function
)
```
## Return-From evaluates to 0 bug
We would expect the value of `(return-from #f x)` to be nothing, as there's no possible way to use it. However, GOAL seems to have a small bug where `(return-from #f x)` always attempts to evaluate to 0. This would be like implementing it as:
```lisp
(set! retrun-reg return-value)
(goto end-of-function)
0 ;; oops
```
by accident.
Example in GOAL:
```
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function basic-type? (in gcommon)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
L285:
lwu v1, -4(a0)
lw a0, object(s7)
L286:
bne v1, a1, L287
or a2, s7, r0
; (return-from #f #t) starts here
daddiu v1, s7, #t ; compile/flush the #t
or v0, v1, r0 ; move to return register
beq r0, r0, L288 ; branch to end
sll r0, r0, 0 ; branch delay slot (usual filler)
or v1, r0, r0 ; unreachable loading of 0 into a register.
L287:
lwu v1, 4(v1)
bne v1, a0, L286
sll r0, r0, 0
or v0, s7, r0
L288:
jr ra
daddu sp, sp, r0
sll r0, r0, 0
sll r0, r0, 0
```
## Unused else case returning false in cond
From `delete!` in gcommon.
```
beq a2, a0, L222 ; (if (= a2 a0) only-one-case)
or a0, s7, r0 ; a0 is unused return value of if
lw a0, 2(a2) ; (set! (cdr v1) (cdr a2)), will evaluate to (cdr a2) which is stored in a0
sw a0, 2(v1)
L222: ; a0 = #f or a0 = (cdr a2) depending on branch taken, but it's totally unused!
or v0, a1, r0 ; return a1
jr ra
daddu sp, sp, r0
```
Also note that all cases stored their result in `a0`, even though nothing uses the result.
## Function Calls evaluate arguments in order:
```
lw t9, format(s7) ;; head of function
daddiu a0, s7, #t ;; first arg
daddiu a1, fp, L344 ;; second arg
sllv a2, gp, r0 ;; third arg
dsra32 a3, gp, 0 ;; fourth arg
pcpyud v1, gp, r0
sllv t0, v1, r0 ;; fifth arg
pcpyud v1, gp, r0
dsra32 t1, v1, 0 ;; sixth arg
por t2, gp, r0 ;; seventh arg
jalr ra, t9
sll v0, ra, 0
```
also an example of lack of common subexpression elimination on the `pcpyud v1, gp, r0`s.
### A second example with register type conversions:
```
lw t9, format(s7) ;; function
daddiu a0, s7, #t ;; first arg
daddiu a1, fp, L343 ;; second arg
lwc1 f0, 0(gp) ;; compile and flush third arg
mfc1 a2, f0 ;; move to correct reg type
jalr ra, t9
sll v0, ra, 0
```
+1001
View File
File diff suppressed because it is too large Load Diff
+11 -3
View File
@@ -70,16 +70,24 @@ set(RUNTIME_SOURCE
overlord/stream.cpp)
# the runtime should be built without any static/dynamic libraries.
add_executable(gk ${RUNTIME_SOURCE} main.cpp)
#add_executable(gk ${RUNTIME_SOURCE} main.cpp)
# we also build a runtime library for testing. This version is likely unable to call GOAL code correctly, but
# can be used to test other things.
add_library(runtime ${RUNTIME_SOURCE})
add_executable(gk main.cpp)
IF (WIN32)
# set stuff for windows
target_link_libraries(gk cross_sockets mman common_util)
target_link_libraries(runtime mman cross_sockets common_util)
target_link_libraries(gk cross_sockets mman common_util runtime)
ELSE()
# set stuff for other systems
target_link_libraries(gk cross_sockets pthread common_util)
target_link_libraries(runtime pthread cross_sockets common_util)
target_link_libraries(gk cross_sockets pthread common_util runtime)
ENDIF()
+13 -7
View File
@@ -47,12 +47,15 @@ u32 DebugSegment;
// Set to 1 to load game engine after boot automatically
u32 DiskBoot;
u32 MasterUseKernel;
void kboot_init_globals() {
strcpy(DebugBootLevel, "#f"); // no specified level
strcpy(DebugBootMessage, "play"); // play mode, the default retail mode
MasterExit = 0;
MasterDebug = 1;
MasterUseKernel = 1;
DebugSegment = 1;
DiskBoot = 0;
memset(&masterConfig, 0, sizeof(MasterConfig));
@@ -117,6 +120,9 @@ s32 goal_main(int argc, const char* const* argv) {
if (InitMachine() >= 0) { // init kernel
KernelCheckAndDispatch(); // run kernel
ShutdownMachine(); // kernel died, we should too.
} else {
fprintf(stderr, "InitMachine failed\n");
exit(1);
}
return 0;
@@ -143,15 +149,15 @@ void KernelCheckAndDispatch() {
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
} else {
if (ListenerFunction->value != s7.offset) {
fprintf(stderr, "Running Listener Function:\n");
auto cptr = Ptr<u8>(ListenerFunction->value).c();
for (int i = 0; i < 40; i++) {
fprintf(stderr, "%x ", cptr[i]);
}
fprintf(stderr, "\n");
// fprintf(stderr, "Running Listener Function:\n");
// auto cptr = Ptr<u8>(ListenerFunction->value).c();
// for (int i = 0; i < 40; i++) {
// fprintf(stderr, "%x ", cptr[i]);
// }
// fprintf(stderr, "\n");
auto result =
call_goal(Ptr<Function>(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem);
fprintf(stderr, "result of listener function: %lld\n", result);
// fprintf(stderr, "result of listener function: %lld\n", result);
#ifdef __linux__
cprintf("%ld\n", result);
#else
+1 -1
View File
@@ -75,6 +75,6 @@ void KernelCheckAndDispatch();
*/
void KernelShutdown();
constexpr bool MasterUseKernel = false;
extern u32 MasterUseKernel;
#endif // RUNTIME_KBOOT_H
+9
View File
@@ -8,6 +8,7 @@
#include <cstring>
#include <cassert>
#include <common/versions.h>
#include "klink.h"
#include "fileio.h"
#include "kscheme.h"
@@ -67,6 +68,14 @@ void link_control::begin(Ptr<uint8_t> object_file,
m_segment_process = 0;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (ofh->goal_version_major != versions::GOAL_VERSION_MAJOR) {
fprintf(
stderr,
"VERSION ERROR: C Kernel built from GOAL %d.%d, but object file %s is from GOAL %d.%d\n",
versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR, name, ofh->goal_version_major,
ofh->goal_version_minor);
exit(0);
}
if (link_debug_printfs) {
printf("Object file header:\n");
printf(" GOAL ver %d.%d obj %d len %d\n", ofh->goal_version_major, ofh->goal_version_minor,
-1
View File
@@ -149,7 +149,6 @@ void ProcessListenerMessage(Ptr<char> msg) {
// this setup allows listener function execution to clean up after itself.
ListenerFunction->value =
link_and_exec(buffer, "*listener*", 0, kdebugheap, LINK_FLAG_FORCE_DEBUG).offset;
fprintf(stderr, "ListenerFunction is now 0x%x\n", ListenerFunction->value);
return; // don't ack yet, this will happen after the function runs.
} break;
default:
+6
View File
@@ -94,6 +94,12 @@ void InitParms(int argc, const char* const* argv) {
reboot = 0;
}
// an added mode to allow booting without a KERNEL.CGO for testing
if (arg == "-nokernel") {
Msg(6, "dkernel: no kernel mode\n");
MasterUseKernel = false;
}
// GOAL Settings
// ----------------------------
+20 -18
View File
@@ -361,22 +361,26 @@ Ptr<Function> make_function_from_c_win32(void* func) {
}
/*
* push rdi
* push rsi
* push rdx
* push rcx
* pop r9
* pop r8
* pop rdx
* pop rcx
*
* sub rsp, 40
* call rax
* add rsp, 40
* ret
push rdi
push rsi
push rdx
push rcx
pop r9
pop r8
pop rdx
pop rcx
push r10
push r11
sub rsp, 40
call rax
add rsp, 40
pop r11
pop r10
ret
*/
for (auto x : {0x57, 0x56, 0x52, 0x51, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59, 0x48,
0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3}) {
for (auto x :
{0x57, 0x56, 0x52, 0x51, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59, 0x41, 0x52, 0x41, 0x53, 0x48,
0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0x41, 0x5B, 0x41, 0x5A, 0xC3}) {
mem.c()[i++] = x;
}
@@ -883,8 +887,6 @@ u64 method_set(u32 type_, u32 method_id, u32 method) {
if (method_id > 127)
printf("[METHOD SET ERROR] tried to set method %d\n", method_id);
// printf("METHOD SET id %d to 0x%x type 0x%x!\n", method_id, method, type_);
auto existing_method = type->get_method(method_id).offset;
if (method == 1) {
@@ -1089,7 +1091,7 @@ u64 sprint(u32 obj) {
*/
u64 print_object(u32 obj) {
if ((obj & OFFSET_MASK) == BINTEGER_OFFSET) {
return print_binteger(obj);
return print_binteger(s64(s32(obj)));
} else {
if ((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory
(obj < 0x84000 || 0x100000 <= obj)) { // not in kernel memory
+11 -7
View File
@@ -49,6 +49,9 @@ u8* g_ee_main_mem = nullptr;
namespace {
int g_argc = 0;
char** g_argv = nullptr;
/*!
* SystemThread function for running the DECI2 communication with the GOAL compiler.
*/
@@ -98,10 +101,6 @@ constexpr u64 EE_MAIN_MEM_MAP = 0x2000000000; // intentionally > 32-bit to
// so this should be used only for debugging.
constexpr bool EE_MEM_LOW_MAP = false;
// GOAL Boot arguments
constexpr const char* GOAL_ARGV[] = {"", "-fakeiso", "-boot", "-debug"};
constexpr int GOAL_ARGC = 4;
/*!
* SystemThread Function for the EE (PS2 Main CPU)
*/
@@ -132,6 +131,11 @@ void ee_runner(SystemThreadInterface& iface) {
printf("[EE] Run!\n");
memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE);
// prevent access to the first 1 MB of memory.
// On the PS2 this is the kernel and can't be accessed either.
// this may not work well on systems with a page size > 1 MB.
mprotect((void*)g_ee_main_mem, 1024 * 1024, PROT_NONE);
fileio_init_globals();
kboot_init_globals();
kdgo_init_globals();
@@ -146,7 +150,7 @@ void ee_runner(SystemThreadInterface& iface) {
kmemcard_init_globals();
kprint_init_globals();
goal_main(GOAL_ARGC, GOAL_ARGV);
goal_main(g_argc, g_argv);
printf("[EE] Done!\n");
// // kill the IOP todo
@@ -224,8 +228,8 @@ void iop_runner(SystemThreadInterface& iface) {
* Arguments are currently ignored.
*/
u32 exec_runtime(int argc, char** argv) {
(void)argc;
(void)argv;
g_argc = argc;
g_argv = argv;
// step 1: sce library prep
iop::LIBRARY_INIT();
+2 -2
View File
@@ -194,8 +194,8 @@ void Deci2Server::run() {
}
auto* hdr = (Deci2Header*)(buffer);
fprintf(stderr, "[DECI2] Got message:\n");
fprintf(stderr, " %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst);
fprintf(stderr, "[DECI2] Got message: %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto,
hdr->src, hdr->dst);
hdr->rsvd = got;
+3
View File
@@ -91,6 +91,9 @@ void IOP::kill_from_ee() {
void IOP::signal_run_iop() {
std::unique_lock<std::mutex> lk(iters_mutex);
iop_iters_des += 100; // todo, tune this
if (iop_iters_des - iop_iters_act > 500) {
iop_iters_des = iop_iters_act + 500;
}
iop_run_cv.notify_all();
}
+1 -1
View File
@@ -3,4 +3,4 @@
# Directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
$DIR/build/game/gk "$@"
$DIR/build/game/gk -fakeiso -debug -nokernel
+202 -1
View File
@@ -92,6 +92,12 @@
)
)
(defmacro shutdown-target ()
`(begin
(reset-target :shutdown)
)
)
;;;;;;;;;;;;;;;;;;;
;; GOAL Syntax
@@ -138,6 +144,26 @@
)
)
(defmacro until (test &rest body)
(with-gensyms (reloop)
`(begin
(label ,reloop)
,@body
(when-goto (not ,test) ,reloop)
)
)
)
(defmacro dotimes (var &rest body)
`(let (( ,(first var) 0))
(while (< ,(first var) ,(second var))
,@body
(+1! ,(first var))
)
,@(cddr var)
)
)
;; Backup some values, and restore after executing body.
;; Non-dynamic (nonlocal jumps out of body will skip restore)
(defmacro protect (defs &rest body)
@@ -165,8 +191,11 @@
`(set! ,place (+ ,place ,amount))
)
;; todo, handle too many arguments correct
(defmacro if (condition true-case &rest others)
(if (> (length others) 1)
(error "got too many arguments to if")
#f
)
(if (null? others)
`(cond (,condition ,true-case))
`(cond (,condition ,true-case)
@@ -175,6 +204,62 @@
)
)
(defmacro when (condition &rest body)
`(if ,condition
(begin ,@body)
)
)
(defmacro unless (condition &rest body)
`(if (not ,condition)
(begin ,@body)
)
)
;; TODO - these work but aren't very efficient.
(defmacro and (&rest args)
(with-gensyms (result end)
`(begin
(let ((,result (the object #f)))
,@(apply (lambda (x)
`(begin
(set! ,result ,x)
(if (eq? ,result #f)
(goto ,end)
)
)
)
args
)
(label ,end)
,result
)
)
)
)
(defmacro or (&rest args)
(with-gensyms (result end)
`(begin
(let ((,result (the object #f)))
,@(apply (lambda (x)
`(begin
(set! ,result ,x)
(if (not (eq? ,result #f))
(goto ,end)
)
)
)
args
)
(label ,end)
,result
)
)
)
)
;;;;;;;;;;;;;;;;;;;
;; Math Macros
;;;;;;;;;;;;;;;;;;;
@@ -201,4 +286,120 @@
(defmacro 1- (var)
`(- ,var 1)
)
(defmacro zero? (thing)
`(eq? ,thing 0)
)
(defmacro &+! (val amount)
`(set! ,val (&+ ,val ,amount))
)
(defmacro &- (a b)
`(- (the-as uint ,a) (the-as uint ,b))
)
(defmacro &-> (&rest args)
`(& (-> ,@args))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Bit Macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro align16 (value)
`(logand #xfffffff0 (+ (the-as integer ,value) 15))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPE STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro basic? (obj)
;; todo, make this more efficient
`(= 4 (logand (the integer ,obj) #b111))
)
(defmacro pair? (obj)
;; todo, make this more efficient
`(= 2 (logand (the integer ,obj) #b111))
)
(defmacro binteger? (obj)
`(zero? (logand (the integer ,obj) #b111))
)
(defmacro rtype-of (obj)
`(cond ((binteger? ,obj) binteger)
((pair? ,obj) pair)
(else (-> (the basic ,obj) type))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PAIR STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro cons (a b)
`(new 'global 'pair ,a ,b)
)
(defmacro list (&rest args)
(if (null? args)
(quote '())
`(cons ,(car args) (list ,@(cdr args)))
)
)
(defmacro null? (arg)
;; todo, make this better
`(eq? ,arg '())
)
(defmacro caar (arg)
`(car (car ,arg))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; METHOD STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro object-new (&rest sz)
(if (null? sz)
`(the ,(current-method-type) ((method object new) allocation type-to-make (the int (-> type-to-make size))))
`(the ,(current-method-type)((method object new) allocation type-to-make ,@sz))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TEST STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro expect-eq (a b &key (name "unknown"))
`(if (!= ,a ,b)
(format #t "Test Failed On Test ~D: ~A~%" *test-count* ,name)
(+! *test-count* 1)
)
)
(defmacro expect-true (a)
`(expect-eq ,a #t)
)
(defmacro expect-false (a)
`(expect-eq ,a #f)
)
(defmacro start-test (test-name)
`(begin
(define *test-name* ,test-name)
(define *test-count* 0)
)
)
(defmacro finish-test ()
`(format #t "Test ~A: ~D Passes~%" *test-name* *test-count*)
)
+2 -2
View File
@@ -71,7 +71,7 @@
(define-extern load (function string kheap object))
(define-extern loado (function string kheap object))
(define-extern unload (function string none))
(define-extern _format function)
(define-extern _format (function _varargs_ object))
(define-extern malloc (function kheap int pointer))
(define-extern kmalloc (function kheap int int string))
(define-extern new-dynamic-structure (function kheap type int structure))
@@ -101,7 +101,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; *listener-link-block*
;; *listener-function*
(define-extern *listener-function* (function object))
;; kernel-dispatcher
;; kernel-packages
;; *print-column*
+378
View File
@@ -259,8 +259,386 @@
)
;; The "print" method of a type should print out a single line representation of the object.
;; The default print method for a basic will be something like #<my-type @ #xbeef>
;; This is used when printing an object with format, using the "~A" format specification.
;; And of course in functions like print, printl.
(defmethod print bfloat ((obj bfloat))
"Override the default print method to print a bfloat like a normal float"
(format #t "~f" (-> obj data))
obj
)
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Type System
;;;;;;;;;;;;;;;;;;;;;;;;;;
; The asize-of method should return the total size in memory used by an object.
; It's used for traversing heaps of basics and copying basics.
; Most basic/structure types are "fixed size", and their default asize-of method will simply
; return the "size" field of their type, so you don't have to worry about it.
; However, some types are dynamic (like a string) and require that you provide your own method.
; A common approach is to have an "allocated-length" field, then have the asize-of method return
; (+ (-> obj type size) (* elem-size (-> obj allocated-length)))
; asize-of returns the actual size, including the type field, and can have any alignment.
;; A "type" object contains some basic information about a type as well as the list of methods.
;; Some types have more methods than others, so the method table makes "type" a dynamic type.
;; As a result, we should define an "asize-of" method for type. It's possibly unused because it's wrong.
(defmethod asize-of type ((obj type))
"Get the size in memory of a type"
;; The 28 is 8 bytes too large. It's also strange that types have a 16-byte aligned size always,
;; but this matches what the runtime does as well. There's no reason that I can see for this,
;; as other basics don't require 16-byte aligned sizes.
(align16 (+ 28 (* 4 (-> type allocated-length))))
)
(defun basic-type? ((obj basic) (input-type type))
"Is obj an object of type input-type, or of child type of input-type?
Note: checking if a basic is of type object will return #f."
(let ((basics-type (-> obj type))
(object-type object))
(until (eq? (set! basics-type (-> basics-type parent)) object-type)
(if (eq? basics-type input-type)
;; return-from #f will return from the function with the value of #t
(return-from #f #t)
)
)
)
#f ;; didn't find it, return false
)
(defun type-type? ((a type) (b type))
"is a a type (or child type) of type b?"
(until (eq? a object)
;; it's not clear why a might be zero?
;; perhaps if the type system is not yet initialized fully for the type?
(if (or (eq? a b) (zero? a))
(return-from #f #t)
)
(set! a (-> a parent))
)
#f
)
(defun find-parent-method ((the-type type) (method-id int))
"Find the nearest parent which has a different method, and get that method.
Use with extreme caution - if a checked parent has fewer methods than the child, it will
access out-of-bounds memory. Returns the nothing function if it gets to the top and
the parent has the same type, or if any parent has 0 as a method."
(let* ((child-method (-> the-type method-table method-id))
(parent-method child-method)
)
;; keep looking until we find a different parent method
(until (not (eq? parent-method child-method))
;; at the top of the type tree.
(if (eq? the-type object)
(return-from #f nothing)
)
(set! the-type (-> the-type parent))
(set! parent-method (-> the-type method-table method-id))
(if (eq? 0 (the int parent-method))
(return-from #f nothing)
)
)
parent-method
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; pair and list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun ref ((obj object) (idx int))
"Get the nth item from a list. No type checking or range checking is done, so be careful!"
(dotimes (i idx (car obj))
(set! obj (cdr obj))
)
)
(defmethod length pair ((obj pair))
"Get the number of elements in a proper list"
(if (eq? obj '())
(return-from #f 0)
)
(let ((lst (cdr obj))
(len 1))
(while (and (not (eq? lst '()))
(pair? lst)
)
(+1! len)
(set! lst (cdr lst))
)
len)
)
(defmethod asize-of pair ((obj pair))
"Get the asize of a pair"
(the-as int (-> pair size))
)
(defun last ((obj object))
"Get the last pair in a list."
(while (not (eq? (cdr obj) '()))
(set! obj (cdr obj))
)
obj
)
(defun member ((obj object) (lst object))
"if obj is a member of the list, return the pair containing obj as its car.
if not, return #f."
(while (and (not (eq? lst '()))
(not (eq? (car lst) obj)))
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
)
)
(define-extern name= (function basic basic symbol))
(defun nmember ((obj basic) (lst object))
"If obj is a member of the list, return the pair containing obj as its car.
If not, return #f. Use name= (see gstring.gc) to check equality."
(while (and (not (eq? lst '()))
(not (name= (the basic (car lst)) obj))
)
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
)
)
(defun assoc ((item object) (alst object))
"Get a pair with car of item from the association list (list of pairs) alst."
(while (and (not (null? alst))
(not (eq? (caar alst) item)))
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
)
)
(defun assoce ((item object) (alst object))
"Like assoc, but a pair with car of 'else will match anything"
(while (and (not (null? alst))
(not (eq? (caar alst) item))
(not (eq? (caar alst) 'else))
)
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
)
)
;; todo
;; nassoc
;; nassce
(defun append! ((front object) (back object))
"Append back to front."
(if (null? front)
(return-from #f back)
)
(let ((lst front))
;; seek to the end of front
(while (not (null? (cdr lst)))
(set! lst (cdr lst))
)
;; this check seems not needed
(if (not (null? lst))
(set! (cdr lst) back)
)
front
)
)
(defun delete! ((item object) (lst object))
"Delete the first occurance of item from a list and return the list.
Does nothing if the item isn't in the list."
(if (eq? (car lst) item)
(return-from #f (cdr lst))
)
(let ((iter (cdr lst))
(rep lst))
(while (and (not (null? iter))
(not (eq? (car iter) item)))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
)
)
(the pair lst)
)
(defun delete-car! ((item object) (lst object))
"Like delete, but will delete if (car item-from-list) is equal to item. Useful for deleting from association list by key."
;(format #t "call to delete car: ~A ~A~%" item lst)
(if (eq? (caar lst) item)
(return-from #f (cdr lst))
)
(let ((rep lst)
(iter (cdr lst)))
(while (and (not (null? iter))
(not (eq? (caar iter) item)))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
)
)
lst
)
(defun insert-cons! ((kv object) (alst object))
"Insert key-value pair into an association list. Also removes the old one if it was there."
(cons kv (delete-car! (car kv) alst))
)
(defun sort ((lst object) (compare (function object object object)))
"Sort the given list in place. Uses the given comparison function. The comparison function can
either return #t/#f or an integer, in which case the sign of the integer determines lt/gt."
;; in each iteration, we count how many changes we make. Once we make no changes, the list is sorted.
(let ((changes -1))
(while (not (zero? changes)) ;; outer loop
(set! changes 0) ;; reset changes for this iteration
(let ((iter lst)) ;; iterate through list
(while (and (not (null? (cdr iter)))
(pair? (cdr iter)))
;; L221
(let* ((val1 (car iter)) ;; value at iterator
(val2 (car (cdr iter))) ;; value after iterator
(c-result (compare val1 val2))) ;; run comparison function
;; check if val1 and val2 are in order. The compare function may either return #t
;; or it may return val1 - val2. There is an issue if val1 - val2 happens to equal #t or #f.
(unless (or
(and c-result (<= (the integer c-result) 0)) ;; not #f, and negative, we're sorted!
(eq? c-result #t) ;; explictly return #t, we're sorted!
)
;; these two aren't sorted! so we swap them and increment changes.
(+1! changes)
(set! (car iter) val2)
(set! (car (cdr iter)) val1)
)
;; move on to the next thing in the list.
(set! iter (cdr iter))
)
)
)
)
)
lst
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; inline array
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; a parent class for boxed "inline arrays" classes,
;; An inline-array is an array with a bunch of objects back-to-back, as opposed to a bunch of
;; references back to back.
;; Most inline-arrays are unboxed and are just data - this is a somewhat rarely used container parent
;; class for a class that wraps an unboxed inline-array.
;; the "heap-base" field of the type is used to store the indexing scale.
(deftype inline-array-class (basic)
((length int32 :offset-assert 4)
(allocated-length int32 :offset-assert 8)
(data uint8 :dynamic)
)
(:methods (new (symbol type int) _type_ 0) ;; we will override print later on. This is optional to include
)
)
(defmethod new inline-array-class ((allocation symbol) (type-to-make type) (cnt int))
"Create a new inline-array. Sets the length, allocated-length to cnt. Uses the mysterious heap-base field
of the type-to-make to determine the element size"
(let* ((sz (+ (-> type-to-make size) (* (-> type-to-make heap-base) cnt)))
(new-object (object-new (the int sz))))
;;(format 0 "create sz ~d at #x~X~%" sz new-object)
(unless (zero? new-object)
(set! (-> new-object length) cnt)
(set! (-> new-object allocated-length) cnt)
)
new-object
)
)
(defmethod length inline-array-class ((obj inline-array-class))
;"Get the length of an inline-array"
(-> obj length)
)
(defmethod asize-of inline-array-class ((obj inline-array-class))
;"Get the size in memory of an inline-array-class"
(+ (the-as int (-> obj type size))
(* (-> obj allocated-length) (-> obj type heap-base))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; array (todo)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; memcpy and similar
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mem-copy! ((dst pointer) (src pointer) (size integer))
"Copy memory from src to dst. Size is in bytes. This is not an efficient implementation,
however, there are _no restrictions_ on size, alignment etc. Increasing address copy."
(let ((i 0)
(d (the pointer dst))
(s (the pointer src))
)
(while (< i size)
(set! (-> (the (pointer uint8) d) 0) (-> (the (pointer uint8) s) 0))
(&+! d 1)
(&+! s 1)
(+1! i)
)
)
dst
)
(defun mem-set32! ((dst pointer) (value int) (n int))
"Memset a 32-bit value n times. Total memory filled is 4 * n bytes."
(let ((p (the pointer dst))
(i 0))
(while (< i n)
(set! (-> (the (pointer int32) p) 0) value)
(&+! p 4)
(+1! i)
)
)
dst
)
+2
View File
@@ -5,3 +5,5 @@
;; name in dgo: gkernel-h
;; dgos: KERNEL
(defglobalconstant *kernel-major-version* 2)
(defglobalconstant *kernel-minor-version* 0)
+20
View File
@@ -5,3 +5,23 @@
;; name in dgo: gkernel
;; dgos: KERNEL
(define *kernel-version* (the binteger (logior (ash *kernel-major-version* 16) *kernel-minor-version*)))
(defun kernel-dispatcher ()
"Kernel Dispatcher Function. This gets called from the main loop in kboot.cpp's KernelCheckAndDispatch"
;; check if we have a new listener function to run
(when *listener-function*
;; we do! enable method-set for debug purposes
(+! *enable-method-set* 1)
;; execute and print result
(let ((result (*listener-function*)))
(format #t "~D~%" result)
)
(+! *enable-method-set* -1)
;; clear the pending function.
(set! *listener-function* (the (function object) #f))
)
)
+53
View File
@@ -5,3 +5,56 @@
;; name in dgo: gstring
;; dgos: KERNEL
;; Note on strings:
;; the allocated length does not include an extra byte on the end for the null terminator!
(defmethod length string ((obj string))
; Get the length of a string. Like strlen
(let ((str-ptr (-> obj data)))
(while (!= 0 (-> str-ptr 0))
(set! str-ptr (the (pointer uint8) (&+ str-ptr 1)))
)
(- (the int str-ptr) (the int (-> obj data)))
)
)
(defmethod asize-of string ((obj string))
;; get the size in bytes of a string.
;; BUG - string should probably be (-> obj type), not that it matters, I don't think
;; anybody makes a subclass of string.
(+ (-> obj allocated-length) 1 (-> string size))
)
(defun copy-string<-string ((dst string) (src string))
"Copy data from one string to another, like strcpy"
(let ((dst-ptr (-> dst data))
(src-ptr (-> src data))
)
(while (!= 0 (-> src-ptr))
(set! (-> dst-ptr) (-> src-ptr))
(&+! dst-ptr 1)
(&+! src-ptr 1)
)
)
)
(defmethod new string ((allocation symbol) (type-to-make type) (size int) (other string))
"Create a new string of the given size. If other is not #f, copy data from it."
(cond
(other
(let* ((desired-size (max (length other) size))
(new-obj (object-new (+ desired-size 1 (-> type-to-make size))))
)
(set! (-> new-obj allocated-length) size)
(copy-string<-string new-obj other)
new-obj
)
)
(else
(let ((new-obj (object-new (+ 1 size (-> type-to-make size)))))
(set! (-> new-obj allocated-length) size)
new-obj
)
)
)
)
@@ -1,19 +1,17 @@
;-*-Scheme-*-
(test-setup 1.2345 #f)
(let* ((new-method (-> bfloat methods 0))
(print-method (-> bfloat methods 2))
(my-float (the bfloat (new-method 'global bfloat)))
(let* ((print-method (method bfloat print))
(my-float (new 'global 'bfloat))
)
(set! (-> my-float data) 1.23456)
(print-method my-float)
(format #t "~%")
)
#|
(let ((x 0))
(while (< x 9)
(format #t "method ~d of ~A is ~A~%" x bfloat (-> bfloat methods x))
(format #t "method ~d of ~A is ~A~%" x bfloat (-> bfloat method-table x))
(+1! x)
)
)
|#
0
+15
View File
@@ -0,0 +1,15 @@
(start-test "addr-of")
(deftype addr-of-test-type (basic)
((v1 int32 :offset-assert 4)
(arr int32 12 :offset-assert 8)
(end uint8 :offset-assert 56)
)
)
(let ((temp (new 'global 'addr-of-test-type)))
(expect-true (= 8 (&- (&-> temp arr 1) temp)))
(expect-true (= 0 (&- (&-> temp v1) temp)))
)
(finish-test)
@@ -1,6 +1 @@
;-*-Scheme-*-
(test-setup 80 #f)
(+ (align16 1) (align16 (* 3 5)) (align16 (/ 32 2)) (align16 (- -17)))
(+ (align16 1) (align16 (* 3 5)) (align16 (/ 32 2)) (align16 (- -17)))
+5
View File
@@ -0,0 +1,5 @@
(defun type-method-check ((obj type))
(align16 (+ 28 (* 4 (-> obj allocated-length))))
)
(type-method-check integer)
@@ -1,4 +1,4 @@
(test-setup '(a b c d e) #f)
(format #t "~A~%"
(append! (list 'a 'b) (list 'c 'd 'e)))
0
@@ -1,6 +1,4 @@
(test-setup 1 #f)
(start-test "approx-pi")
(defun test-approx-pi ((res integer))
(let ((rad (* res res))
@@ -48,5 +46,4 @@
(expect-true (< approx-pi 3.15))
)
;(format #t "~f~%" (test-approx-pi 1000))
1
(finish-test)
@@ -1,7 +1,3 @@
(test-setup 'w #f)
(format #t "~A~%"
(cdr (assoc 'e (list (cons 'a 'b) (cons 'e 'w) (cons 'x 'x)))))
(print-type (assoc 'a '()))
0
@@ -1,4 +1,3 @@
(test-setup #f #f)
(format #t "~A~%"
(assoc 'r (list (cons 'a 'b) (cons 'e 'w) (cons 'x 'x))))
0
@@ -1,4 +1,3 @@
(test-setup 'x #f)
(format #t "~A~%"
(cdr (assoce 'r (list (cons 'a 'b) (cons 'e 'w) (cons 'else 'x)))))
0
@@ -1,4 +1,3 @@
(test-setup 'x #f)
(format #t "~A~%"
(cdr (assoce 'r (list (cons 'a 'b) (cons 'r 'x) (cons 'else 'w)))))
0
@@ -1,11 +1,4 @@
;-*-Scheme-*-
(test-setup '#f#t#t#f#t#f#t#t #f)
;; awful hack to create a bfloat at #x6000000
(define-extern hack-bfloat integer)
(define hack-bfloat (+ #x6000000 *gtype-basic-offset*))
(define-extern hack-bfloat bfloat)
(define hack-bfloat (new 'global 'bfloat))
(format #t "~A~A~A~A"
@@ -21,4 +14,4 @@
(basic-type? #f basic) ;; #t
(basic-type? inspect function) ;; #t
)
0
+7
View File
@@ -0,0 +1,7 @@
(define hack-bfloat (new 'global 'bfloat))
(set! (-> hack-bfloat type) bfloat)
(set! (-> hack-bfloat data) 1.233)
(format #t "data ~f print ~A type ~A~%" (-> hack-bfloat data) hack-bfloat (-> hack-bfloat type))
0
+2
View File
@@ -0,0 +1,2 @@
(format #t "~A~%" (the binteger -17))
0
@@ -1,8 +1,4 @@
;-*-Scheme-*-
(test-setup 'ab #f)
(let ((my-pair (cons 'a 'b)))
(format #t "~A~A~%" (car my-pair) (cdr my-pair))
)
0
+6
View File
@@ -0,0 +1,6 @@
(let ((my-pair (cons 'a 'b)))
(set! (car my-pair) 'c)
(set! (cdr my-pair) 'd)
(format #t "~A~%" my-pair)
)
0
@@ -1,7 +1,3 @@
;-*-Scheme-*-
(test-setup 4 #f)
(let ((total 0))
(if (true-func)
(+! total 1)
@@ -23,5 +19,4 @@
(+! total 999)
)
total
)
)
+2
View File
@@ -0,0 +1,2 @@
(format #t "~A~%" (cons 'a 'b))
0
@@ -1,5 +1,3 @@
(test-setup '((a . b) (e . f)) #f)
(let ((my-list (list (cons 'a 'b)
(cons 'c 'd)
(cons 'e 'f)
@@ -9,3 +7,4 @@
(format #t "~A~%" my-list)
(format #t "~A~%" (assoc 'c my-list))
)
0
@@ -1,4 +1,3 @@
(test-setup '(a b d e) #f)
(format #t "~A~%"
(delete! 'c (list 'a 'b 'c 'd 'e)))
0
@@ -1,11 +1,6 @@
;-*-Scheme-*-
(test-setup 4950 #f)
(let ((sum 0))
(dotimes (i 100 7 8 9 sum)
(+! sum i)
;;(format #t "iter ~D sum ~D~%" i sum)
)
)
)
@@ -1,13 +1,12 @@
(test-setup 1 #f)
(start-test "dynamic-type")
(deftype test-dynamic-type (basic)
(
(pad0 int16 :offset 0)
(allocated-length int32 :offset 4)
(data int32 :dynamic :offset 8)
(over1 int32 :offset 8)
(over2 int32 :offset 12)
(pad0 int16 :offset 4)
(allocated-length int32 :offset 8)
(data int32 :dynamic :offset 12)
(over1 int32 :offset 12)
(over2 int32 :offset 16)
)
)
@@ -15,12 +14,13 @@
(defmethod new test-dynamic-type ((allocation symbol) (type-to-make type) (cnt integer))
;"Create a new inline-array. Sets the length, allocated-length to cnt. Uses the mysterious heap-base field
;of the type-to-make to determine the element size"
(let* ((sz (+ (-> type-to-make asize) (* 4 cnt)))
(new-object (object-new sz)))
(let* ((sz (+ (-> type-to-make size) (* 4 cnt)))
(new-object (object-new (the int sz))))
;;(format 0 "create sz ~d at #x~X~%" sz new-object)
(unless (zero? new-object)
(set! (-> new-object allocated-length) cnt)
)
new-object
)
)
@@ -31,28 +31,34 @@
)
(defmethod asize-of test-dynamic-type ((obj test-dynamic-type))
;"Get the size in memory of it"
(+ (-> obj type asize)
(the int (+ (-> obj type size)
(* (-> obj allocated-length) 4)
)
))
)
(define test-dynamic-obj
(the test-dynamic-type ((-> test-dynamic-type methods 0) 'global test-dynamic-type 40)))
(the test-dynamic-type (new 'global 'test-dynamic-type 40)))
;(inspect test-dynamic-obj)
; ;(define test-dynamic-obj (new 'global 'test-dynamic-type 40))
; ;(inspect test-dynamic-obj)
(set! (-> test-dynamic-obj data 0) 12)
(set! (-> test-dynamic-obj data 1) 20)
;(inspect test-dynamic-obj)
; ;(inspect test-dynamic-obj)
; (format #t "should be same (~d ~d) (~d ~d)~%" (-> test-dynamic-obj data 0) (-> test-dynamic-obj over1)
; (-> test-dynamic-obj data 1) (-> test-dynamic-obj over2))
; ; (format #t "should be same (~d ~d) (~d ~d)~%" (-> test-dynamic-obj data 0) (-> test-dynamic-obj over1)
; ; (-> test-dynamic-obj data 1) (-> test-dynamic-obj over2))
(expect-true (= (-> test-dynamic-obj data 0) (-> test-dynamic-obj over1)))
(expect-true (= (-> test-dynamic-obj data 1) (-> test-dynamic-obj over2)))
(set! (-> test-dynamic-obj pad0) 0)
(expect-true (= (-> test-dynamic-obj type) test-dynamic-type))
(expect-true (= (asize-of test-dynamic-obj) 180))
1
(finish-test)
+2
View File
@@ -0,0 +1,2 @@
(format #t "~A~%" '())
0
@@ -1,12 +1,8 @@
;-*-Scheme-*-
(test-setup "test pass!" #f)
(let ((test-result "test fail!"))
;; first, do one where we get something
(if (eq?
(-> structure methods 1)
(-> structure method-table 1)
(find-parent-method bfloat 1)
)
(set! test-result "test pass!")
@@ -23,5 +19,4 @@
(print test-result)
)
0
-1
View File
@@ -1,4 +1,3 @@
(define-extern _format function)
(define format _format)
(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6)
+6
View File
@@ -0,0 +1,6 @@
(let* ((base (the int integer))
(field (the int (-> integer method-table)))
(offset (- field base)))
;;(format #t "offset of methods table is ~d~%" offset)
offset
)
@@ -1,8 +1,7 @@
(test-setup '((c . w) (a . b) (e . f)) #f)
(let ((alist (list (cons 'a 'b)
(cons 'c 'd)
(cons 'e 'f))))
(set! alist (insert-cons! (cons 'c 'w) alist))
(format #t "~A~%" alist)
)
0
@@ -1,6 +1,4 @@
;-*-Scheme-*-
(test-setup 'd #f)
(format #t "~A~%"
(car (last (list 'a 'b 'c 'd)))
)
0
+1
View File
@@ -0,0 +1 @@
(format #t "~A~%" (list 'a 'b 'c 'd))
@@ -1,4 +1,3 @@
(test-setup '(c d) #f)
(format #t "~A~%"
(member 'c (list 'a 'b 'c 'd)))
0
@@ -1,5 +1,3 @@
(test-setup #f #f)
(format #t "~A~%"
(member 1234 (list 'a 'b 'c 'd))
)
)
@@ -1,8 +1,3 @@
;-*-Scheme-*-
;; add two constants together.
(test-setup 13 #f)
(let* ((base-addr #x6000000)
(offset #x123)
(ptr-int32 (the (pointer int32) base-addr))
@@ -14,5 +9,3 @@
(-> ptr-int16 3)
)
)
@@ -1,8 +1,3 @@
;-*-Scheme-*-
;; add two constants together.
(test-setup #x0b #f)
(let* ((base-addr #x6000000)
(word-cnt 23)
(base (the (pointer int32) base-addr))
@@ -12,14 +7,12 @@
)
(if (!= dst base)
(format #t "test failed, bad base returned!~%")
)
(format #t "test failed, bad base returned!~%")
)
(if (!= 0 (-> last-byte 1))
(format #t "set too many bytes!~%")
)
(format #t "set too many bytes!~%")
)
(-> last-byte 0)
)
)
+4
View File
@@ -0,0 +1,4 @@
(format #t "~A~A~%" (eq? (-> process method-table 2) (method process print))
(eq? (-> string method-table 3) (method "test" inspect))
)
0
@@ -1,8 +1,3 @@
;-*-Scheme-*-
(test-setup 'efgh #f)
(let ((my-pair (cons (cons 'a 'b) (cons 'c 'd))))
(set! (car (car my-pair)) 'e)
(set! (car (cdr my-pair)) 'f)
@@ -11,3 +6,4 @@
(format #t "~A~A~A~A~%" (car (car my-pair)) (car (cdr my-pair)) (cdr (car my-pair)) (cdr (cdr my-pair)))
(format #t "~A~%" my-pair)
)
0

Some files were not shown because too many files have changed in this diff Show More