diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 79b8023985..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\xtvas\Repositories\jak-project codebase based on best match to current usage at 2020-08-28 -# You can modify the rules from these initially generated values to suit your own policies -# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference -[*.cs] - diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..727015b74f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +third-party/* linguist-vendored +*.gc linguist-language=lisp +*.gd linguist-language=lisp +*.gs linguist-language=Scheme diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc3e6a860..70931db1bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,13 @@ set(CMAKE_CXX_STANDARD 14) # Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. -if(CMAKE_COMPILER_IS_GNUCXX) +if (CMAKE_COMPILER_IS_GNUCXX) message(STATUS "GCC detected, adding compile flags") - set(CMAKE_CXX_FLAGS + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ -Wall \ - -Winit-self \ + -Winit-self \ + -ggdb \ -Wextra \ -Wcast-align \ -Wcast-qual \ @@ -22,16 +23,17 @@ if(CMAKE_COMPILER_IS_GNUCXX) -Wredundant-decls \ -Wshadow \ -Wsign-promo") -else() - set(CMAKE_CXX_FLAGS "/EHsc") -endif(CMAKE_COMPILER_IS_GNUCXX) +else () + set(CMAKE_CXX_FLAGS "/EHsc") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10000000") +endif (CMAKE_COMPILER_IS_GNUCXX) IF (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -ENDIF() + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +ENDIF () option(CODE_COVERAGE "Enable Code Coverage Compiler Flags" OFF) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) @@ -50,9 +52,15 @@ include_directories(./) # build asset packer/unpacker add_subdirectory(asset_tool) +# build goos +add_subdirectory(common/goos) + # build type_system library for compiler/decompiler add_subdirectory(common/type_system) +# build common_util library +add_subdirectory(common/util) + # build cross platform socket library add_subdirectory(common/cross_sockets) @@ -79,5 +87,5 @@ add_subdirectory(third-party/fmt) # windows memory management lib IF (WIN32) - add_subdirectory(third-party/mman) -ENDIF() + add_subdirectory(third-party/mman) +ENDIF () diff --git a/README.md b/README.md index 6f953c768b..aabd7a847c 100644 --- a/README.md +++ b/README.md @@ -90,93 +90,6 @@ TODO - running tests - etc -## Project Layout - -- `goalc` is the GOAL compiler - - `gs` contains GOOS code for parts of GOOS implemented in GOOS - - `gc` contains GOAL code for parts of GOAL implemented in GOAL (must generate no machine code, just defining macros) -- `decompiler` is the decompiler -- `decompiler_out` is the decompiler output -- `data` will contain big assets and the output of the GOAL compiler (not checked in to git) -- `out` will contain the finished game (not checked into git) -- `resources` will contain data which is checked into git -- `game` will contain the game source code -- `common` will contain all data/type shared between different applications. -- `doc` will contain documentation (markdown format?) -- `iso_data` is where the files from the DVD go -- `third-party` will contain code we didn't write. Google Test is a git submodule in this folder. -- `tests` will contain all tests -- `asset_tool` will contain the asset packer/unpacker - -## Design - -(if anybody has better ideas, feel free to suggest improvements! This is just a rough plan for now) - -- All C++ code should build from the top-level `cmake`. -- All C++ applications (GOAL compiler, asset extractor, asset packer, runtime, test) should have a script in the top level which launches them. -- All file paths should be relative to the `jak` folder. -- The planned workflow for building a game: - - `git submodule update --init --recursive` : check out gtest - - `mkdir build; cd build` : create build folder for C++ - - `cmake ..; make -j` : build C++ code - - `cd ..` - - `./test.sh` : run gtests - - `./asset_extractor.sh ./iso_data` : extract assets from game - - `./build_engine.sh` : run GOAL compiler to build all game code - - `./build_game.sh` : run the asset packer to build the game - - `./run_game.sh` : run the game -- Workflow for development: - - `./gc.sh` : run the compiler in interactive mode - - `./gs.sh` : run a goos interpreter in interactive mode - - `./decomp.sh : run the decompiler - -## Current State - -- GOAL compiler just implements the GOOS Scheme Macro Language. Running `./gc.sh` just loads the GOOS library (`goalc/gs/goos-lib.gs`) and then goes into an interactive mode. Use `(exit)` to exit. -- `./test.sh` runs tests for some game C++ code, for GOOS, for the reader, for the listener connection, and for some early emitter stuff. -- The runtime boots in `fakeiso` mode which will load some dummy files. Then the C Kernel (`game/kernel`) will load the `KERNEL.CGO` and `GAME.CGO` files, which are from the "proof of concept" GOAL compiler. If you run `./gk.sh`, you should see it load stuff, then print: - -``` -calling play! -~~ HACK ~~ : fake play has been called -InitListenerConnect -InitCheckListener -kernel: machine started - -``` - -where the `~~ HACK ~~` message is from code in `KERNEL.CGO`. - -## Coding Guidelines - -- Avoid warnings -- Use asserts over throwing exceptions in game code (throwing exceptions from C code called by GOAL code is sketchy) - -## TODOs - -- Build on Windows! - - File paths - - Timer - - Windows calling convention for assembly stuff - - performance stats for `SystemThread` (probably just get rid of these performance stats completely) - - `mmap`ing executable memory - - line input library (appears windows compatible?) -- Clean up possible duplicate code in compiler/decompiler `util` folder, consider a common utility library -- Clean up header guard names (or just use `#pragma once`?) -- Investigate a better config format - - The current JSON library seems to have issues with comments, which I really like -- Clean up use of namespaces -- Clean up the print message when `gk` starts. -- Finish commenting runtime stuff -- Runtime document -- GOOS document -- Listener protocol document -- GOAL Compiler IR -- GOAL Compiler Skeleton -- Gtest setup for checking decompiler results against hand-decompiled stuff -- Clean up decompiler print spam, finish up the CFG stuff -- Decompiler document - ## Project Description This project is to port Jak 1 (NTSC, "black label" version) to PC. The strategy is to: @@ -211,6 +124,67 @@ Some statistics: The rough timeline is to finish sometime in 2022. If it looks like this is impossible, the project will be abandoned. But I have already spent about 4 months preparing to start this and seems doable. I also have some background in compilers, and familiarity with PS2 (worked on DobieStation PS2 emulator) / MIPS in general (wrote a PS1 emulator). I think the trick will be making good automated tools - the approach taken for SM64 and other N64 decompilations is way too labor-intensive to work. +## Project Layout + +- `goalc` is the GOAL compiler +- `decompiler` is the decompiler +- `decompiler_out` is the decompiler output +- `data` will contain big assets and the output of the GOAL compiler (not checked in to git) +- `out` will contain the finished game (not checked into git) +- `resources` will contain data which is checked into git +- `game` will contain the game source code (C/C++) +- `goal_src` will contain GOAL source code for the game +- `common` will contain all data/type shared between different applications. +- `doc` will contain documentation +- `iso_data` is where the files from the DVD go +- `third-party` will contain code we didn't write. Google Test is a git submodule in this folder. +- `tests` will contain all tests +- `asset_tool` will contain the asset packer/unpacker + +## Design + +(if anybody has better ideas, feel free to suggest improvements! This is just a rough plan for now) + +- All C++ code should build from the top-level `cmake`. +- All C++ applications (GOAL compiler, asset extractor, asset packer, runtime, test) should have a script in the top level which launches them. +- All file paths should be relative to the `jak` folder. +- The planned workflow for building a game: + - `git submodule update --init --recursive` : check out gtest + - `mkdir build; cd build` : create build folder for C++ + - `cmake ..; make -j` : build C++ code + - `cd ..` + - `./test.sh` : run gtests + - `./asset_extractor.sh ./iso_data` : extract assets from game + - `./build_engine.sh` : run GOAL compiler to build all game code + - `./build_game.sh` : run the asset packer to build the game + - `./run_game.sh` : run the game +- Workflow for development: + - `./gc.sh` : run the compiler in interactive mode + - `./gs.sh` : run a goos interpreter in interactive mode + - `./decomp.sh : run the decompiler + + +## Coding Guidelines + +- Avoid warnings +- Use asserts over throwing exceptions in game code (throwing exceptions from C code called by GOAL code is sketchy) + +## TODOs + +- Clean up header guard names (or just use `#pragma once`?) +- Investigate a better config format + - The current JSON library seems to have issues with comments, which I really like +- Clean up use of namespaces +- Clean up the print message when `gk` starts. +- Finish commenting runtime stuff +- Runtime document +- GOOS document +- Listener protocol document +- Gtest setup for checking decompiler results against hand-decompiled stuff +- Clean up decompiler print spam, finish up the CFG stuff +- Decompiler document + + ### GOAL Decompiler The decompiler is in progress, at @@ -272,80 +246,6 @@ The compiler will reuse a significant amount of code from my existing LISP compi An important part of the compiler is the test suite. Without tests the compiler will be full of bugs. So every feature should have a good set of tests. -The major components are - -- [ ] GOAL-IR, a typed linear intermediate representation for GOAL code - - - [ ] "Environment" - - [ ] "Ref" - - [ ] Constant propagation of integers/floats - - [ ] Constant propagation of memory locations (this is required to make sure bitfields updates know where to write their result) - - [ ] Ref `in_gpr` - -- [ ] The type system - - - [ ] Type inheritance - - [ ] Integer/float/pointer types (value semantics) - - [ ] Reference types - - [ ] Bitfield types - - [ ] 128-bit integer types (EE GPRs are 128-bit when used with MMI instructions, these will become `xmm`'s) - - [ ] Floating point vector types - - [ ] Structure types - - [ ] Inline structures as fields - - [ ] Array access / alignment rules - - [ ] Pointer dereferencing - - [ ] Methods (dynamic and static dispatch) - - [ ] Function arguments/returns - - [ ] Global symbols - - [ ] Type checking - - [ ] Lowest common ancestor - - [ ] Built-in types in the GOAL runtime/C Kernel - -- [x] The GOOS Macro Language - - - [x] S-expression parser (the "Reader") - - [x] Reader text db (for error messages that point to a specific line) - - [x] Scheme interpreter - -- [ ] Front-end (convert s-expressions (a tree structure) to GOAL-IR (a linear representation)) -- [ ] Parsing helpers -- [ ] Macro expansion -- [ ] Control flow/block forms -- [ ] Type definitions -- [ ] Inline assembly forms -- [ ] Function/method call -- [ ] Math forms -- [ ] Lexical scoping (immediate application of `lambda`) -- [ ] Function inlining (slightly different scoping rules of immediate `lambda`) -- [ ] Function/macro definition -- [ ] Static Objects - -- [ ] Regsiter allocation -- [ ] Analysis -- [ ] Allocation -- [ ] Stack spilling -- [ ] `xmm` and `gpr` promotion/demotions for EE 128-bit register usage - -- [ ] Codegen / Emitter (convert GOAL-IR + register allocations to x86 object file format) -- [ ] Emitter (convert GOAL-IR to instructions) -- [ ] x86-64 instruction generation (actually generate the machine code) -- [ ] Linking data -- [ ] 64-bit GPR -- [ ] 32-bit float -- [ ] 128-bit GPR -- [ ] 32-bit float x4 vector register -- [ ] function prologue/epilogue -- [ ] stack spilling -- [ ] static object and static object links - -- [ ] Listener/REPL - - [ ] Network connection - - [ ] Tracking loaded files - - [ ] Sending code from REPL - - [ ] GOOS REPL option - - [ ] Expand single macro debugging feature - - [ ] Interface for running gtests - ### Asset Extraction Tool Not started yet. The simplest version of this tool is just to use the decompiler logic to turn the level/art-group/texture/TXT files into GOAL source code, and copy all STR/sound/visibility files, as these don't need to be modified. diff --git a/common/common_types.h b/common/common_types.h index b4da746aaf..2ce985659a 100644 --- a/common/common_types.h +++ b/common/common_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file common_types.h * Common Integer Types. diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index a7f2827070..763cf596f4 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -40,7 +40,7 @@ void close_socket(int sock) { } int set_socket_option(int socket, int level, int optname, const void* optval, int optlen) { - int ret = setsockopt(socket, level, optname, (char*)&optval, optlen); + int ret = setsockopt(socket, level, optname, (const char*)optval, optlen); if (ret < 0) { printf("Failed to setsockopt(%d, %d, %d, _, _) - Error: %s\n", socket, level, optname, strerror(errno)); @@ -66,7 +66,7 @@ int set_socket_timeout(int socket, long microSeconds) { } return ret; #elif _WIN32 - unsigned long timeout = microSeconds / 1000; // milliseconds + DWORD timeout = microSeconds / 1000; // milliseconds return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #endif } @@ -86,3 +86,12 @@ int read_from_socket(int socket, char* buf, int len) { return recv(socket, buf, len, 0); #endif } + +bool socket_timed_out() { +#ifdef __linux + return errno == EAGAIN; +#elif _WIN32 + auto err = WSAGetLastError(); + return err == WSAETIMEDOUT; +#endif +} \ No newline at end of file diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h index eb8e09b38c..d643f89b12 100644 --- a/common/cross_sockets/xsocket.h +++ b/common/cross_sockets/xsocket.h @@ -1,3 +1,5 @@ +#pragma once + #ifdef __linux #include #include @@ -18,3 +20,4 @@ int set_socket_option(int socket, int level, int optname, const void* optval, in int set_socket_timeout(int socket, long microSeconds); int write_to_socket(int socket, const char* buf, int len); int read_from_socket(int socket, char* buf, int len); +bool socket_timed_out(); \ No newline at end of file diff --git a/common/goal_constants.h b/common/goal_constants.h index 88d6896e2f..6fe677a659 100644 --- a/common/goal_constants.h +++ b/common/goal_constants.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_GOAL_CONSTANTS_H #define JAK_GOAL_CONSTANTS_H diff --git a/goalc/goos/CMakeLists.txt b/common/goos/CMakeLists.txt similarity index 70% rename from goalc/goos/CMakeLists.txt rename to common/goos/CMakeLists.txt index a6b2e6b489..170c8df0d8 100644 --- a/goalc/goos/CMakeLists.txt +++ b/common/goos/CMakeLists.txt @@ -1,2 +1,2 @@ add_library(goos SHARED Object.cpp TextDB.cpp Reader.cpp Interpreter.cpp InterpreterEval.cpp) -target_link_libraries(goos util) \ No newline at end of file +target_link_libraries(goos common_util) \ No newline at end of file diff --git a/goalc/goos/Interpreter.cpp b/common/goos/Interpreter.cpp similarity index 99% rename from goalc/goos/Interpreter.cpp rename to common/goos/Interpreter.cpp index 285f607fbe..3c3eb8569e 100644 --- a/goalc/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -379,8 +379,8 @@ ArgumentSpec Interpreter::parse_arg_spec(const Object& form, Object& rest) { void Interpreter::vararg_check( const Object& form, const Arguments& args, - const std::vector>& unnamed, - const std::unordered_map>>& named) { + const std::vector>& unnamed, + const std::unordered_map>>& named) { assert(args.rest.empty()); if (unnamed.size() != args.unnamed.size()) { throw_eval_error(form, "Got " + std::to_string(args.unnamed.size()) + @@ -805,11 +805,9 @@ Object Interpreter::eval_quasiquote(const Object& form, return quasiquote_helper(rest.as_pair()->car, env); } -namespace { -bool truthy(const Object& o) { +bool Interpreter::truthy(const Object& o) { return !(o.is_symbol() && o.as_symbol()->name == "#f"); } -} // namespace /*! * Scheme "cond" statement - tested by integrated tests only. diff --git a/goalc/goos/Interpreter.h b/common/goos/Interpreter.h similarity index 97% rename from goalc/goos/Interpreter.h rename to common/goos/Interpreter.h index b19903e68e..6a643ce584 100644 --- a/goalc/goos/Interpreter.h +++ b/common/goos/Interpreter.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Interpreter.h * The GOOS Interpreter @@ -9,7 +11,7 @@ #include #include "Object.h" #include "Reader.h" -#include "goalc/util/MatchParam.h" +#include "common/util/MatchParam.h" namespace goos { class Interpreter { @@ -23,6 +25,16 @@ class Interpreter { Object eval(Object obj, const std::shared_ptr& env); Object intern(const std::string& name); void disable_printfs(); + Object eval_symbol(const Object& sym, const std::shared_ptr& env); + Arguments get_args(const Object& form, const Object& rest, const ArgumentSpec& spec); + void set_args_in_env(const Object& form, + const Arguments& args, + const ArgumentSpec& arg_spec, + const std::shared_ptr& env); + Object eval_list_return_last(const Object& form, + Object rest, + const std::shared_ptr& env); + bool truthy(const Object& o); Reader reader; Object global_environment; @@ -43,18 +55,13 @@ class Interpreter { void vararg_check( const Object& form, const Arguments& args, - const std::vector>& unnamed, - const std::unordered_map>>& named); + const std::vector>& unnamed, + const std::unordered_map>>& named); Object eval_pair(const Object& o, const std::shared_ptr& env); - Object eval_symbol(const Object& sym, const std::shared_ptr& env); - Arguments get_args(const Object& form, const Object& rest, const ArgumentSpec& spec); void eval_args(Arguments* args, const std::shared_ptr& env); ArgumentSpec parse_arg_spec(const Object& form, Object& rest); - Object eval_list_return_last(const Object& form, - Object rest, - const std::shared_ptr& env); Object quasiquote_helper(const Object& form, const std::shared_ptr& env); IntType number_to_integer(const Object& obj); @@ -206,11 +213,6 @@ class Interpreter { const Object& rest, const std::shared_ptr& env); - void set_args_in_env(const Object& form, - const Arguments& args, - const ArgumentSpec& arg_spec, - const std::shared_ptr& env); - bool want_exit = false; bool disable_printing = false; diff --git a/goalc/goos/InterpreterEval.cpp b/common/goos/InterpreterEval.cpp similarity index 99% rename from goalc/goos/InterpreterEval.cpp rename to common/goos/InterpreterEval.cpp index 8f73c46052..bd3e8eac47 100644 --- a/goalc/goos/InterpreterEval.cpp +++ b/common/goos/InterpreterEval.cpp @@ -66,7 +66,7 @@ Object Interpreter::eval_read_file(const Object& form, vararg_check(form, args, {ObjectType::STRING}, {}); try { - return reader.read_from_file(args.unnamed.at(0).as_string()->data); + return reader.read_from_file({args.unnamed.at(0).as_string()->data}); } catch (std::runtime_error& e) { throw_eval_error(form, std::string("reader error inside of read-file:\n") + e.what()); } @@ -84,7 +84,7 @@ Object Interpreter::eval_load_file(const Object& form, Object o; try { - o = reader.read_from_file(args.unnamed.at(0).as_string()->data); + o = reader.read_from_file({args.unnamed.at(0).as_string()->data}); } catch (std::runtime_error& e) { throw_eval_error(form, std::string("reader error inside of load-file:\n") + e.what()); } diff --git a/goalc/goos/Object.cpp b/common/goos/Object.cpp similarity index 94% rename from goalc/goos/Object.cpp rename to common/goos/Object.cpp index 1023ff3581..99b66d5dca 100644 --- a/goalc/goos/Object.cpp +++ b/common/goos/Object.cpp @@ -1,5 +1,5 @@ #include "Object.h" -#include "goalc/util/text_util.h" +#include "common/util/FileUtil.h" namespace goos { @@ -53,7 +53,7 @@ std::string fixed_to_string(FloatType x) { template <> std::string fixed_to_string(char x) { char buff[256]; - if (util::is_printable_char(x) && x != ' ') { + if (file_util::is_printable_char(x) && x != ' ') { // can print directly sprintf(buff, "#\\%c", x); return {buff}; @@ -233,4 +233,13 @@ ArgumentSpec make_varargs() { return as; } +bool Arguments::only_contains_named(const std::unordered_set& names) { + for (auto& kv : named) { + if (names.find(kv.first) == names.end()) { + return false; + } + } + return true; +} + } // namespace goos diff --git a/goalc/goos/Object.h b/common/goos/Object.h similarity index 98% rename from goalc/goos/Object.h rename to common/goos/Object.h index 926014bc6e..a9bd7796bd 100644 --- a/goalc/goos/Object.h +++ b/common/goos/Object.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Object.h * An "Object" represents a scheme object. @@ -45,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -128,7 +131,7 @@ class FixedObject { return object_type_to_string(ObjectType::INTEGER); if (std::is_same()) return object_type_to_string(ObjectType::CHAR); - assert(false); + throw std::runtime_error("Unsupported FixedObject type"); } }; @@ -299,6 +302,7 @@ class Object { } bool is_empty_list() const { return type == ObjectType::EMPTY_LIST; } + bool is_list() const { return type == ObjectType::EMPTY_LIST || type == ObjectType::PAIR; } bool is_int() const { return type == ObjectType::INTEGER; } bool is_float() const { return type == ObjectType::FLOAT; } bool is_char() const { return type == ObjectType::CHAR; } @@ -527,6 +531,7 @@ struct Arguments { Object get_named(const std::string& name, const Object& default_value); Object get_named(const std::string& name); bool has_named(const std::string& name); + bool only_contains_named(const std::unordered_set& names); }; class LambdaObject : public HeapObject { diff --git a/goalc/goos/Reader.cpp b/common/goos/Reader.cpp similarity index 97% rename from goalc/goos/Reader.cpp rename to common/goos/Reader.cpp index fa77d54996..a830564831 100644 --- a/goalc/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -11,8 +11,7 @@ #include "Reader.h" #include "third-party/linenoise.h" -#include "goalc/util/file_io.h" -#include "goalc/util/text_util.h" +#include "common/util/FileUtil.h" namespace goos { @@ -98,15 +97,6 @@ Reader::Reader() { for (const char* c = bonus; *c; c++) { valid_symbols_chars[(int)*c] = true; } - - // find the source directory - auto result = std::getenv("NEXT_DIR"); - if (!result) { - throw std::runtime_error( - "Environment variable NEXT_DIR is not set. Please set this to point to next/"); - } - - source_dir = result; } /*! @@ -147,8 +137,8 @@ Object Reader::read_from_string(const std::string& str) { /*! * Read a file */ -Object Reader::read_from_file(const std::string& filename) { - auto textFrag = std::make_shared(util::combine_path(get_source_dir(), filename)); +Object Reader::read_from_file(const std::vector& file_path) { + auto textFrag = std::make_shared(file_util::get_file_path(file_path)); db.insert(textFrag); auto result = internal_read(textFrag); @@ -670,7 +660,7 @@ bool Reader::try_token_as_integer(const Token& tok, Object& obj) { bool Reader::try_token_as_char(const Token& tok, Object& obj) { if (tok.text.size() >= 3 && tok.text[0] == '#' && tok.text[1] == '\\') { - if (tok.text.size() == 3 && util::is_printable_char(tok.text[2]) && tok.text[2] != ' ') { + if (tok.text.size() == 3 && file_util::is_printable_char(tok.text[2]) && tok.text[2] != ' ') { obj = Object::make_char(tok.text[2]); return true; } @@ -705,6 +695,6 @@ void Reader::throw_reader_error(TextStream& here, const std::string& err, int se * Get the source directory of the current project. */ std::string Reader::get_source_dir() { - return source_dir; + return file_util::get_project_path(); } } // namespace goos diff --git a/goalc/goos/Reader.h b/common/goos/Reader.h similarity index 94% rename from goalc/goos/Reader.h rename to common/goos/Reader.h index 1ac7e6a7a1..f5aafec45e 100644 --- a/goalc/goos/Reader.h +++ b/common/goos/Reader.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Reader.h * @@ -17,8 +19,8 @@ #include #include -#include "goalc/goos/Object.h" -#include "goalc/goos/TextDB.h" +#include "common/goos/Object.h" +#include "common/goos/TextDB.h" namespace goos { @@ -70,7 +72,7 @@ class Reader { Reader(); Object read_from_string(const std::string& str); Object read_from_stdin(const std::string& prompt_name); - Object read_from_file(const std::string& filename); + Object read_from_file(const std::vector& file_path); std::string get_source_dir(); @@ -97,7 +99,6 @@ class Reader { char valid_symbols_chars[256]; - std::string source_dir; std::unordered_map reader_macros; }; } // namespace goos diff --git a/goalc/goos/TextDB.cpp b/common/goos/TextDB.cpp similarity index 94% rename from goalc/goos/TextDB.cpp rename to common/goos/TextDB.cpp index 75ad3369a1..6a636f74b9 100644 --- a/goalc/goos/TextDB.cpp +++ b/common/goos/TextDB.cpp @@ -11,7 +11,7 @@ * (+ 1 (+ a b)) ; compute the sum */ -#include "goalc/util/file_io.h" +#include "common/util/FileUtil.h" #include "TextDB.h" @@ -60,7 +60,8 @@ int SourceText::get_line_idx(int offset) { return line; } } - assert(false); + throw std::runtime_error("Unable to get line index for character at position " + + std::to_string(offset)); } /*! @@ -79,7 +80,7 @@ std::pair SourceText::get_containing_line(int offset) { * Read text from a file. */ FileText::FileText(std::string filename_) : filename(std::move(filename_)) { - text = util::read_text_file(filename); + text = file_util::read_text_file(filename); build_offsets(); } diff --git a/goalc/goos/TextDB.h b/common/goos/TextDB.h similarity index 98% rename from goalc/goos/TextDB.h rename to common/goos/TextDB.h index 6abf8f7f60..f15939038d 100644 --- a/goalc/goos/TextDB.h +++ b/common/goos/TextDB.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file TextDB.h * The Text Database for storing source code text. @@ -20,7 +22,7 @@ #include #include -#include "goalc/goos/Object.h" +#include "common/goos/Object.h" namespace goos { /*! diff --git a/common/link_types.h b/common/link_types.h index ec70f63624..9572fda96b 100644 --- a/common/link_types.h +++ b/common/link_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file link_types.h * Types used in the linking data, shared between the object file generator and the kernel's linker. diff --git a/common/listener_common.h b/common/listener_common.h index f18c41b4a5..d2c0d5a3cc 100644 --- a/common/listener_common.h +++ b/common/listener_common.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file listener_common.h * Common types shared between the compiler and the runtime for the listener connection. @@ -33,7 +35,7 @@ enum class ListenerMessageKind : u16 { /*! * Type of message sent from compiler */ -enum ListenerToTargetMsgKind { +enum ListenerToTargetMsgKind : u16 { LTT_MSG_POKE = 1, //! "Poke" the game and have it flush buffers LTT_MSG_INSEPCT = 5, //! Inspect an object LTT_MSG_PRINT = 6, //! Print an object @@ -47,11 +49,15 @@ enum ListenerToTargetMsgKind { * TODO - there are other copies of this somewhere */ struct ListenerMessageHeader { - Deci2Header deci2_header; //! The header used for DECI2 communication - ListenerMessageKind msg_kind; //! GOAL Listener message kind - u16 u6; //! Unknown - u32 msg_size; //! Size of data after this header - u64 u8; //! Unknown + Deci2Header deci2_header; //! The header used for DECI2 communication + union { + ListenerMessageKind msg_kind; //! GOAL Listener message kind + ListenerToTargetMsgKind ltt_msg_kind; + }; + + u16 u6; //! Unknown + u32 msg_size; //! Size of data after this header + u64 u8; //! Unknown }; constexpr int DECI2_PORT = 8112; // TODO - is this a good choise? diff --git a/common/symbols.h b/common/symbols.h index a16cfcf543..fb2c3ab42f 100644 --- a/common/symbols.h +++ b/common/symbols.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file symbols.h * The location of fixed symbols in the GOAL symbol table. diff --git a/common/type_system/CMakeLists.txt b/common/type_system/CMakeLists.txt index d8db0b46ec..271fcd92b4 100644 --- a/common/type_system/CMakeLists.txt +++ b/common/type_system/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(type_system SHARED TypeSystem.cpp Type.cpp - TypeSpec.cpp) + TypeSpec.cpp + deftype.cpp) -target_link_libraries(type_system fmt) \ No newline at end of file +target_link_libraries(type_system fmt goos) \ No newline at end of file diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp index 0c68f48d86..55b7bae132 100644 --- a/common/type_system/Type.cpp +++ b/common/type_system/Type.cpp @@ -16,7 +16,7 @@ std::string reg_kind_to_string(RegKind kind) { case RegKind::FLOAT_4X: return "float-4x"; default: - assert(false); + throw std::runtime_error("Unsupported RegKind"); } } @@ -467,7 +467,7 @@ void StructureType::inherit(StructureType* parent) { } bool StructureType::operator==(const Type& other) const { - if (typeid(StructureType) != typeid(other)) { + if (typeid(*this) != typeid(other)) { return false; } diff --git a/common/type_system/Type.h b/common/type_system/Type.h index a8c3474a3e..dab3adec97 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_TYPE_H #define JAK_TYPE_H @@ -213,7 +215,7 @@ class StructureType : public ReferenceType { bool pack = false); std::string print() const override; void inherit(StructureType* parent); - const std::vector& fields() { return m_fields; } + const std::vector& fields() const { return m_fields; } bool operator==(const Type& other) const override; int get_size_in_memory() const override; int get_offset() const override; diff --git a/common/type_system/TypeSpec.cpp b/common/type_system/TypeSpec.cpp index c78def6cd8..2adaafeb55 100644 --- a/common/type_system/TypeSpec.cpp +++ b/common/type_system/TypeSpec.cpp @@ -43,4 +43,21 @@ TypeSpec TypeSpec::substitute_for_method_call(const std::string& method_type) co result.m_arguments.push_back(x.substitute_for_method_call(method_type)); } return result; +} + +bool TypeSpec::is_compatible_child_method(const TypeSpec& implementation, + const std::string& child_type) const { + bool ok = implementation.m_type == m_type || + (m_type == "_type_" && implementation.m_type == child_type); + if (!ok || implementation.m_arguments.size() != m_arguments.size()) { + return false; + } + + for (size_t i = 0; i < m_arguments.size(); i++) { + if (!m_arguments[i].is_compatible_child_method(implementation.m_arguments[i], child_type)) { + return false; + } + } + + return true; } \ No newline at end of file diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h index 04a0431b08..47d46eb8ff 100644 --- a/common/type_system/TypeSpec.h +++ b/common/type_system/TypeSpec.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file TypeSpec.h */ @@ -28,6 +30,8 @@ class TypeSpec { bool operator!=(const TypeSpec& other) const; bool operator==(const TypeSpec& other) const; + bool is_compatible_child_method(const TypeSpec& implementation, + const std::string& child_type) const; std::string print() const; void add_arg(const TypeSpec& ts) { m_arguments.push_back(ts); } @@ -43,6 +47,14 @@ class TypeSpec { TypeSpec substitute_for_method_call(const std::string& method_type) const; + size_t arg_count() const { return m_arguments.size(); } + + const TypeSpec& get_arg(int idx) const { return m_arguments.at(idx); } + const TypeSpec& last_arg() const { + assert(!m_arguments.empty()); + return m_arguments.back(); + } + private: friend class TypeSystem; std::string m_type; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 308128dce3..94b1b42530 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -228,6 +228,12 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const { return lookup_type(ts.base_type()); } +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); +} + /*! * Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to * see if this is an identical definition. If not, it's an error, and if so, nothing happens. @@ -263,7 +269,7 @@ MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, co if (got_existing) { // make sure we aren't changing anything. - if (ts != existing_info.type) { + if (!existing_info.type.is_compatible_child_method(ts, type->get_name())) { fmt::print( "[TypeSystem] The method {} of type {} was originally defined as {}, but has been " "redefined as {}\n", @@ -591,6 +597,7 @@ void TypeSystem::add_builtin_types() { // TYPE builtin_structure_inherit(type_type); + 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? diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index b5612992ec..0e156de785 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_TYPESYSTEM_H #define JAK_TYPESYSTEM_H @@ -49,6 +51,9 @@ class TypeSystem { Type* lookup_type(const TypeSpec& ts) const; Type* lookup_type(const std::string& name) const; + 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); MethodInfo add_new_method(Type* type, const TypeSpec& ts); MethodInfo lookup_method(const std::string& type_name, const std::string& method_name); @@ -74,6 +79,7 @@ class TypeSystem { bool print_on_error = true, bool throw_on_error = true) const; std::vector get_path_up_tree(const std::string& type); + int get_next_method_id(Type* type); /*! * Get a type by name and cast to a child class of Type*. Must succeed. @@ -97,7 +103,6 @@ class TypeSystem { int get_size_in_type(const Field& field); int get_alignment_in_type(const Field& field); Field lookup_field(const std::string& type_name, const std::string& field_name); - int get_next_method_id(Type* type); StructureType* add_builtin_structure(const std::string& parent, const std::string& type_name, bool boxed = false); diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp new file mode 100644 index 0000000000..3b0405ebed --- /dev/null +++ b/common/type_system/deftype.cpp @@ -0,0 +1,325 @@ +#include "deftype.h" +#include "third-party/fmt/core.h" + +/*! + * Missing Features + * - Bitfields + * - Int128 children + * - Refer to yourself (structure/basic only) + * - Method List + * - Evaluate constants. + * + */ + +namespace { +const goos::Object& car(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid deftype form"); + } + + return x->as_pair()->car; +} + +const goos::Object* cdr(const goos::Object* x) { + if (!x->is_pair()) { + throw std::runtime_error("invalid deftype form"); + } + + return &x->as_pair()->cdr; +} + +std::string deftype_parent_list(const goos::Object& list) { + if (!list.is_pair()) { + throw std::runtime_error("invalid parent list in deftype: " + list.print()); + } + + auto parent = list.as_pair()->car; + auto rest = list.as_pair()->cdr; + if (!rest.is_empty_list()) { + throw std::runtime_error("invalid parent list in deftype - can only have one parent"); + } + + if (!parent.is_symbol()) { + throw std::runtime_error("invalid parent in deftype parent list"); + } + + return parent.as_symbol()->name; +} + +bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSystem* ts) { + return ts->typecheck(ts->make_typespec(expected), actual, "", false, false); +} + +template +void for_each_in_list(const goos::Object& list, T f) { + const goos::Object* iter = &list; + while (iter->is_pair()) { + auto lap = iter->as_pair(); + f(lap->car); + iter = &lap->cdr; + } + + if (!iter->is_empty_list()) { + throw std::runtime_error("invalid list in for_each_in_list: " + list.print()); + } +} + +std::string symbol_string(const goos::Object& obj) { + if (obj.is_symbol()) { + return obj.as_symbol()->name; + } + throw std::runtime_error(obj.print() + " was supposed to be a symbol, but isn't"); +} + +int64_t get_int(const goos::Object& obj) { + if (obj.is_int()) { + return obj.integer_obj.value; + } + 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; + + auto name = symbol_string(car(rest)); + rest = cdr(rest); + + auto type = parse_typespec(ts, car(rest)); + rest = cdr(rest); + + int array_size = -1; + bool is_inline = false; + bool is_dynamic = false; + int offset_override = -1; + int offset_assert = -1; + + if (!rest->is_empty_list()) { + if (car(rest).is_int()) { + array_size = car(rest).integer_obj.value; + rest = cdr(rest); + } + + while (!rest->is_empty_list()) { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":inline") { + is_inline = true; + } else if (opt_name == ":dynamic") { + is_dynamic = true; + } else if (opt_name == ":offset") { + offset_override = get_int(car(rest)); + rest = cdr(rest); + } else if (opt_name == ":offset-assert") { + offset_assert = get_int(car(rest)); + if (offset_assert == -1) { + throw std::runtime_error("Cannot use -1 as offset-assert"); + } + rest = cdr(rest); + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + int actual_offset = ts->add_field_to_type(structure, name, type, is_inline, is_dynamic, + array_size, offset_override); + if (offset_assert != -1 && actual_offset != offset_assert) { + throw std::runtime_error("Field " + name + " was placed at " + std::to_string(actual_offset) + + " but offset-assert was set to " + std::to_string(offset_assert)); + } +} + +void declare_method(StructureType* type, TypeSystem* type_system, const goos::Object& def) { + for_each_in_list(def, [&](const goos::Object& _obj) { + auto obj = &_obj; + // (name args return-type [id]) + auto method_name = symbol_string(car(obj)); + obj = cdr(obj); + auto& args = car(obj); + obj = cdr(obj); + auto& return_type = car(obj); + obj = cdr(obj); + int id = -1; + if (!obj->is_empty_list()) { + auto& id_obj = car(obj); + id = get_int(id_obj); + if (!cdr(obj)->is_empty_list()) { + throw std::runtime_error("too many things in method def: " + def.print()); + } + } + + std::vector arg_types; + for_each_in_list(args, [&](const goos::Object& o) { + if (o.is_symbol()) { + arg_types.emplace_back(symbol_string(o)); + } else { + auto next = cdr(&o); + arg_types.emplace_back(symbol_string(car(next))); + if (!cdr(next)->is_empty_list()) { + throw std::runtime_error("too many things in method def arg type: " + def.print()); + }; + } + }); + + auto info = type_system->add_method( + type, method_name, + type_system->make_function_typespec(arg_types, symbol_string(return_type))); + + // check the method assert + if (id != -1) { + // method id assert! + if (id != info.id) { + printf("WARNING - ID assert failed on method %s of type %s (wanted %d got %d)\n", + method_name.c_str(), type->get_name().c_str(), id, info.id); + throw std::runtime_error("Method ID assert failed"); + } + } + }); +} + +TypeFlags parse_structure_def(StructureType* type, + TypeSystem* ts, + const goos::Object& fields, + const goos::Object& options) { + (void)options; + for_each_in_list(fields, [&](const goos::Object& o) { add_field(type, ts, o); }); + TypeFlags flags; + flags.heap_base = 0; + + flags.size = type->get_size_in_memory(); + flags.pad = 0; + + auto* rest = &options; + int size_assert = -1; + int method_count_assert = -1; + uint64_t flag_assert = 0; + bool flag_assert_set = false; + while (!rest->is_empty_list()) { + if (car(rest).is_pair()) { + auto opt_list = &car(rest); + auto& first = car(opt_list); + opt_list = cdr(opt_list); + + if (symbol_string(first) == ":methods") { + declare_method(type, ts, *opt_list); + } else { + throw std::runtime_error("Invalid option list in field specification: " + + car(rest).print()); + } + + rest = cdr(rest); + } else { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":size-assert") { + size_assert = get_int(car(rest)); + if (size_assert == -1) { + throw std::runtime_error("Cannot use -1 as size-assert"); + } + rest = cdr(rest); + } else if (opt_name == ":method-count-assert") { + method_count_assert = get_int(car(rest)); + if (method_count_assert == -1) { + throw std::runtime_error("Cannot use -1 as method_count_assert"); + } + rest = cdr(rest); + } else if (opt_name == ":flag-assert") { + flag_assert = get_int(car(rest)); + flag_assert_set = true; + rest = cdr(rest); + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + if (size_assert != -1 && flags.size != u16(size_assert)) { + throw std::runtime_error("Type " + type->get_name() + " came out to size " + + std::to_string(int(flags.size)) + " but size-assert was set to " + + std::to_string(size_assert)); + } + + flags.methods = ts->get_next_method_id(type); + + if (method_count_assert != -1 && flags.methods != u16(method_count_assert)) { + throw std::runtime_error( + "Type " + type->get_name() + " has " + std::to_string(int(flags.methods)) + + " methods, but method-count-assert was set to " + std::to_string(method_count_assert)); + } + + if (flag_assert_set && (flags.flag != flag_assert)) { + throw std::runtime_error( + fmt::format("Type {} has flag 0x{:x} but flag-assert was set to 0x{:x}", type->get_name(), + flags.flag, flag_assert)); + } + + return flags; +} + +} // namespace + +DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) { + auto iter = &deftype; + + auto& type_name_obj = car(iter); + iter = cdr(iter); + auto& parent_list_obj = car(iter); + iter = cdr(iter); + auto& field_list_obj = car(iter); + iter = cdr(iter); + auto& options_obj = *iter; + + if (!type_name_obj.is_symbol()) { + throw std::runtime_error("deftype must be given a symbol as the type name"); + } + + auto name = type_name_obj.as_symbol()->name; + auto parent_type_name = deftype_parent_list(parent_list_obj); + auto parent_type = ts->make_typespec(parent_type_name); + DeftypeResult result; + + if (is_type("basic", parent_type, ts)) { + auto new_type = std::make_unique(parent_type_name, name); + auto pto = dynamic_cast(ts->lookup_type(parent_type)); + assert(pto); + new_type->inherit(pto); + result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + ts->add_type(name, std::move(new_type)); + } else if (is_type("structure", parent_type, ts)) { + auto new_type = std::make_unique(parent_type_name, name); + auto pto = dynamic_cast(ts->lookup_type(parent_type)); + assert(pto); + new_type->inherit(pto); + result.flags = parse_structure_def(new_type.get(), ts, field_list_obj, options_obj); + ts->add_type(name, std::move(new_type)); + } else if (is_type("integer", parent_type, ts)) { + throw std::runtime_error("Creating a child type of integer is not supported yet."); + } else { + throw std::runtime_error("Creating a child type from " + parent_type.print() + + " is not allowed or not supported yet."); + } + + result.type = ts->make_typespec(name); + result.type_info = ts->lookup_type(result.type); + return result; +} diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h new file mode 100644 index 0000000000..13872693da --- /dev/null +++ b/common/type_system/deftype.h @@ -0,0 +1,24 @@ +#pragma once + +#include "TypeSystem.h" +#include "common/goos/Object.h" + +struct TypeFlags { + union { + uint64_t flag = 0; + struct { + uint16_t size; + uint16_t heap_base; + uint16_t methods; + uint16_t pad; + }; + }; +}; + +struct DeftypeResult { + TypeFlags flags; + TypeSpec type; + Type* type_info = nullptr; +}; + +DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts); diff --git a/common/type_system/type_util.h b/common/type_system/type_util.h index fcefbdd2ad..1375b1b203 100644 --- a/common/type_system/type_util.h +++ b/common/type_system/type_util.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_TYPE_UTIL_H #define JAK_TYPE_UTIL_H diff --git a/decompiler/util/BinaryReader.h b/common/util/BinaryReader.h similarity index 98% rename from decompiler/util/BinaryReader.h rename to common/util/BinaryReader.h index c0e3c9746a..f6e4e135d2 100644 --- a/decompiler/util/BinaryReader.h +++ b/common/util/BinaryReader.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_BINARYREADER_H #define JAK_V2_BINARYREADER_H diff --git a/common/util/BinaryWriter.h b/common/util/BinaryWriter.h new file mode 100644 index 0000000000..d58fdedf22 --- /dev/null +++ b/common/util/BinaryWriter.h @@ -0,0 +1,76 @@ +#pragma once + +#ifndef JAK_BINARYWRITER_H +#define JAK_BINARYWRITER_H + +#include +#include +#include +#include +#include + +struct BinaryWriterRef { + size_t offset; + size_t write_size; +}; + +class BinaryWriter { + public: + BinaryWriter() = default; + + template + BinaryWriterRef add(const T& obj) { + auto orig_size = data.size(); + data.resize(orig_size + sizeof(T)); + memcpy(data.data() + orig_size, &obj, sizeof(T)); + return {orig_size, sizeof(T)}; + } + + template + void add_at_ref(const T& obj, const BinaryWriterRef& ref) { + assert(ref.write_size == sizeof(T)); + assert(ref.offset + ref.write_size < get_size()); + memcpy(data.data() + ref.offset, &obj, sizeof(T)); + } + + void add_str_len(const std::string& str, size_t len) { add_cstr_len(str.c_str(), len); } + + void add_cstr_len(const char* str, size_t len) { + size_t i = 0; + while (*str) { + data.push_back(*str); + str++; + i++; + } + + while (i < len) { + data.push_back(0); + i++; + } + } + + BinaryWriterRef add_data(void* d, size_t len) { + auto orig_size = data.size(); + data.resize(orig_size + len); + memcpy(data.data() + orig_size, d, len); + return {orig_size, len}; + } + + size_t get_size() { return data.size(); } + + void* get_data() { return data.data(); } + + void write_to_file(const std::string& filename) { + auto fp = fopen(filename.c_str(), "wb"); + if (!fp) + throw std::runtime_error("failed to open " + filename); + if (fwrite(get_data(), get_size(), 1, fp) != 1) + throw std::runtime_error("failed to write " + filename); + fclose(fp); + } + + private: + std::vector data; +}; + +#endif // JAK_BINARYWRITER_H diff --git a/common/util/CMakeLists.txt b/common/util/CMakeLists.txt new file mode 100644 index 0000000000..caf551f79c --- /dev/null +++ b/common/util/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(common_util + SHARED + FileUtil.cpp + DgoWriter.cpp + Timer.cpp) diff --git a/common/util/DgoWriter.cpp b/common/util/DgoWriter.cpp new file mode 100644 index 0000000000..69adf5e950 --- /dev/null +++ b/common/util/DgoWriter.cpp @@ -0,0 +1,28 @@ +#include "BinaryWriter.h" +#include "FileUtil.h" +#include "DgoWriter.h" + +void build_dgo(const DgoDescription& description) { + BinaryWriter writer; + // dgo header + writer.add(description.entries.size()); + writer.add_cstr_len(description.dgo_name.c_str(), 60); + + for (auto& obj : description.entries) { + auto obj_data = file_util::read_binary_file(file_util::get_file_path({"data", obj.file_name})); + // size + writer.add(obj_data.size()); + // name + writer.add_str_len(obj.name_in_dgo, 60); + // data + writer.add_data(obj_data.data(), obj_data.size()); + // pad + while (writer.get_size() & 0xf) { + writer.add(0); + } + } + + printf("DGO: %15s %.3f MB\n", description.dgo_name.c_str(), + (float)(writer.get_size()) / (1 << 20)); + writer.write_to_file(file_util::get_file_path({"out", description.dgo_name})); +} diff --git a/common/util/DgoWriter.h b/common/util/DgoWriter.h new file mode 100644 index 0000000000..c59d1b7b0b --- /dev/null +++ b/common/util/DgoWriter.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef JAK_DGOWRITER_H +#define JAK_DGOWRITER_H + +#include + +struct DgoDescription { + std::string dgo_name; + struct DgoEntry { + std::string file_name; + std::string name_in_dgo; + }; + std::vector entries; +}; + +void build_dgo(const DgoDescription& description); + +#endif // JAK_DGOWRITER_H diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp new file mode 100644 index 0000000000..f10c05f070 --- /dev/null +++ b/common/util/FileUtil.cpp @@ -0,0 +1,110 @@ +#include "FileUtil.h" +#include +#include /* defines FILENAME_MAX */ +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +std::string file_util::get_project_path() { +#ifdef _WIN32 + char buffer[FILENAME_MAX]; + GetModuleFileNameA(NULL, buffer, FILENAME_MAX); + std::string::size_type pos = + std::string(buffer).rfind("jak-project"); // Strip file path down to \jak-project\ directory + return std::string(buffer).substr( + 0, pos + 11); // + 12 to include "\jak-project" in the returned filepath +#else + // do Linux stuff + char buffer[FILENAME_MAX + 1]; + auto len = readlink("/proc/self/exe", buffer, + FILENAME_MAX); // /proc/self acts like a "virtual folder" containing + // information about the current process + buffer[len] = '\0'; + std::string::size_type pos = + std::string(buffer).rfind("jak-project"); // Strip file path down to /jak-project/ directory + return std::string(buffer).substr( + 0, pos + 11); // + 12 to include "/jak-project" in the returned filepath +#endif +} + +std::string file_util::get_file_path(const std::vector& input) { + std::string currentPath = file_util::get_project_path(); + char dirSeparator; + +#ifdef _WIN32 + dirSeparator = '\\'; +#else + dirSeparator = '/'; +#endif + + std::string filePath = currentPath; + for (int i = 0; i < int(input.size()); i++) { + filePath = filePath + dirSeparator + input[i]; + } + + return filePath; +} + +void file_util::write_binary_file(const std::string& name, void* data, size_t size) { + FILE* fp = fopen(name.c_str(), "wb"); + if (!fp) { + throw std::runtime_error("couldn't open file " + name); + } + + if (fwrite(data, size, 1, fp) != 1) { + throw std::runtime_error("couldn't write file " + name); + } + + fclose(fp); +} + +void file_util::write_text_file(const std::string& file_name, const std::string& text) { + FILE* fp = fopen(file_name.c_str(), "w"); + if (!fp) { + printf("Failed to fopen %s\n", file_name.c_str()); + throw std::runtime_error("Failed to open file"); + } + fprintf(fp, "%s\n", text.c_str()); + fclose(fp); +} + +std::vector file_util::read_binary_file(const std::string& filename) { + auto fp = fopen(filename.c_str(), "rb"); + if (!fp) + throw std::runtime_error("File " + filename + + " cannot be opened: " + std::string(strerror(errno))); + fseek(fp, 0, SEEK_END); + auto len = ftell(fp); + rewind(fp); + + std::vector data; + data.resize(len); + + if (fread(data.data(), len, 1, fp) != 1) { + throw std::runtime_error("File " + filename + " cannot be read"); + } + fclose(fp); + + return data; +} + +std::string file_util::read_text_file(const std::string& path) { + std::ifstream file(path); + if (!file.good()) { + throw std::runtime_error("couldn't open " + path); + } + std::stringstream ss; + ss << file.rdbuf(); + return ss.str(); +} + +bool file_util::is_printable_char(char c) { + return c >= ' ' && c <= '~'; +} \ No newline at end of file diff --git a/common/util/FileUtil.h b/common/util/FileUtil.h new file mode 100644 index 0000000000..d2a2b68abc --- /dev/null +++ b/common/util/FileUtil.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include + +namespace file_util { +std::string get_project_path(); +std::string get_file_path(const std::vector& input); +void write_binary_file(const std::string& name, void* data, size_t size); +void write_text_file(const std::string& file_name, const std::string& text); +std::vector read_binary_file(const std::string& filename); +std::string read_text_file(const std::string& path); +bool is_printable_char(char c); +} // namespace file_util diff --git a/goalc/util/MatchParam.h b/common/util/MatchParam.h similarity index 93% rename from goalc/util/MatchParam.h rename to common/util/MatchParam.h index e8a5cc8c12..0a2140ebff 100644 --- a/goalc/util/MatchParam.h +++ b/common/util/MatchParam.h @@ -1,7 +1,8 @@ +#pragma once + #ifndef JAK1_MATCHPARAM_H #define JAK1_MATCHPARAM_H -namespace util { template struct MatchParam { MatchParam() { is_wildcard = true; } @@ -18,6 +19,5 @@ struct MatchParam { bool operator==(const T& other) const { return is_wildcard || (value == other); } bool operator!=(const T& other) const { return !(*this == other); } }; -} // namespace util #endif // JAK1_MATCHPARAM_H diff --git a/decompiler/util/Timer.cpp b/common/util/Timer.cpp similarity index 100% rename from decompiler/util/Timer.cpp rename to common/util/Timer.cpp diff --git a/decompiler/util/Timer.h b/common/util/Timer.h similarity index 98% rename from decompiler/util/Timer.h rename to common/util/Timer.h index 5972020339..b2f3b1dc71 100644 --- a/decompiler/util/Timer.h +++ b/common/util/Timer.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_TIMER_H #define JAK_V2_TIMER_H diff --git a/common/versions.h b/common/versions.h index 5ecb742ac8..a3098572a9 100644 --- a/common/versions.h +++ b/common/versions.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file versions.h * Version numbers for GOAL Language, Kernel, etc... diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 16aecbbc36..eca54d7d2f 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -12,7 +12,6 @@ add_executable(decompiler util/FileIO.cpp config.cpp util/LispPrint.cpp - util/Timer.cpp Function/BasicBlocks.cpp Disasm/InstructionMatching.cpp TypeSystem/GoalType.cpp @@ -22,4 +21,5 @@ add_executable(decompiler TypeSystem/TypeSpec.cpp Function/CfgVtx.cpp Function/CfgVtx.h) target_link_libraries(decompiler - minilzo) \ No newline at end of file + minilzo + common_util) \ No newline at end of file diff --git a/decompiler/Disasm/Instruction.cpp b/decompiler/Disasm/Instruction.cpp index 59cadb9bff..721115c97f 100644 --- a/decompiler/Disasm/Instruction.cpp +++ b/decompiler/Disasm/Instruction.cpp @@ -26,7 +26,7 @@ std::string InstructionAtom::to_string(const LinkedObjectFile& file) const { case IMM_SYM: return sym; default: - assert(false); + throw std::runtime_error("Unsupported InstructionAtom"); } } diff --git a/decompiler/Disasm/Instruction.h b/decompiler/Disasm/Instruction.h index bdf999bcd9..ca3ab99a37 100644 --- a/decompiler/Disasm/Instruction.h +++ b/decompiler/Disasm/Instruction.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Instruction.h * An EE instruction, represented as an operation, plus a list of source/destination atoms. diff --git a/decompiler/Disasm/InstructionDecode.h b/decompiler/Disasm/InstructionDecode.h index 7cefc76e1d..137234eb2b 100644 --- a/decompiler/Disasm/InstructionDecode.h +++ b/decompiler/Disasm/InstructionDecode.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file InstructionDecode.h * The Instruction Decoder - converts a LinkedWord into a Instruction. diff --git a/decompiler/Disasm/InstructionMatching.h b/decompiler/Disasm/InstructionMatching.h index 96098cac92..3fe9232e34 100644 --- a/decompiler/Disasm/InstructionMatching.h +++ b/decompiler/Disasm/InstructionMatching.h @@ -1,24 +1,10 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H #define JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H #include "Instruction.h" - -template -struct MatchParam { - MatchParam() { is_wildcard = true; } - - // intentionally not explicit so you don't have to put MatchParam(blah) everywhere - MatchParam(T x) { - value = x; - is_wildcard = false; - } - - T value; - bool is_wildcard = true; - - bool operator==(const T& other) { return is_wildcard || (value == other); } - bool operator!=(const T& other) { return !(*this == other); } -}; +#include "common/util/MatchParam.h" bool is_no_link_gpr_store(const Instruction& instr, MatchParam size, diff --git a/decompiler/Disasm/OpcodeInfo.h b/decompiler/Disasm/OpcodeInfo.h index fd035a140c..23413bbcad 100644 --- a/decompiler/Disasm/OpcodeInfo.h +++ b/decompiler/Disasm/OpcodeInfo.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file OpcodeInfo.h * Decoding info for each opcode. diff --git a/decompiler/Disasm/Register.cpp b/decompiler/Disasm/Register.cpp index 4c8eb53c23..93ba6f1681 100644 --- a/decompiler/Disasm/Register.cpp +++ b/decompiler/Disasm/Register.cpp @@ -5,6 +5,7 @@ #include "Register.h" #include +#include //////////////////////////// // Register Name Constants @@ -126,7 +127,7 @@ const char* Register::to_charp() const { case Reg::PCR: return pcr_to_charp(get_pcr()); default: - assert(false); + throw std::runtime_error("Unsupported Register"); } } diff --git a/decompiler/Disasm/Register.h b/decompiler/Disasm/Register.h index 2a8196800d..d6b48c3b16 100644 --- a/decompiler/Disasm/Register.h +++ b/decompiler/Disasm/Register.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Register.h * Representation of an EE register. diff --git a/decompiler/Function/BasicBlocks.h b/decompiler/Function/BasicBlocks.h index 3605fe96ec..42b1a1fedc 100644 --- a/decompiler/Function/BasicBlocks.h +++ b/decompiler/Function/BasicBlocks.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_BASICBLOCKS_H #define JAK_DISASSEMBLER_BASICBLOCKS_H diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h index bf82158eb7..a19abb30cf 100644 --- a/decompiler/Function/CfgVtx.h +++ b/decompiler/Function/CfgVtx.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_CFGVTX_H #define JAK_DISASSEMBLER_CFGVTX_H diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h index c972496229..179e51c901 100644 --- a/decompiler/Function/Function.h +++ b/decompiler/Function/Function.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef NEXT_FUNCTION_H #define NEXT_FUNCTION_H @@ -30,7 +32,7 @@ struct FunctionName { case FunctionKind::UNIDENTIFIED: return "(?)"; default: - assert(false); + throw std::runtime_error("Unsupported FunctionKind"); } } diff --git a/decompiler/ObjectFile/LinkedObjectFile.h b/decompiler/ObjectFile/LinkedObjectFile.h index f897b86475..7b535efbf9 100644 --- a/decompiler/ObjectFile/LinkedObjectFile.h +++ b/decompiler/ObjectFile/LinkedObjectFile.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file LinkedObjectFile.h * An object file's data with linking information included. diff --git a/decompiler/ObjectFile/LinkedObjectFileCreation.h b/decompiler/ObjectFile/LinkedObjectFileCreation.h index 245356b176..7faf3b602c 100644 --- a/decompiler/ObjectFile/LinkedObjectFileCreation.h +++ b/decompiler/ObjectFile/LinkedObjectFileCreation.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file LinkedObjectFileCreation.h * Create a LinkedObjectFile from raw object file data. diff --git a/decompiler/ObjectFile/LinkedWord.h b/decompiler/ObjectFile/LinkedWord.h index 559079c3a5..d1e3ff5b99 100644 --- a/decompiler/ObjectFile/LinkedWord.h +++ b/decompiler/ObjectFile/LinkedWord.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file LinkedWord.h * A word (4 bytes), possibly with some linking info. diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 92a5c0c8d6..ec6bcfc700 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -13,9 +13,10 @@ #include "LinkedObjectFileCreation.h" #include "decompiler/config.h" #include "third-party/minilzo/minilzo.h" -#include "decompiler/util/BinaryReader.h" +#include "common/util/BinaryReader.h" #include "decompiler/util/FileIO.h" -#include "decompiler/util/Timer.h" +#include "common/util/Timer.h" +#include "common/util/FileUtil.h" #include "decompiler/Function/BasicBlocks.h" /*! @@ -70,7 +71,7 @@ ObjectFileDB::ObjectFileDB(const std::vector& _dgos) { } printf("ObjectFileDB Initialized:\n"); - printf(" total dgos: %ld\n", _dgos.size()); + printf(" total dgos: %lld\n", _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); @@ -142,7 +143,7 @@ constexpr int MAX_CHUNK_SIZE = 0x8000; * Load the objects stored in the given DGO into the ObjectFileDB */ void ObjectFileDB::get_objs_from_dgo(const std::string& filename) { - auto dgo_data = read_binary_file(filename); + auto dgo_data = file_util::read_binary_file(filename); stats.total_dgo_bytes += dgo_data.size(); const char jak2_header[] = "oZlB"; @@ -415,7 +416,7 @@ void ObjectFileDB::write_object_file_words(const std::string& output_dir, bool d auto file_text = obj.linked_data.print_words(); auto file_name = combine_path(output_dir, obj.record.to_unique_name() + ".txt"); total_bytes += file_text.size(); - write_text_file(file_name, file_text); + file_util::write_text_file(file_name, file_text); total_files++; } }); @@ -442,7 +443,7 @@ void ObjectFileDB::write_disassembly(const std::string& output_dir, auto file_text = obj.linked_data.print_disassembly(); auto file_name = combine_path(output_dir, obj.record.to_unique_name() + ".func"); total_bytes += file_text.size(); - write_text_file(file_name, file_text); + file_util::write_text_file(file_name, file_text); total_files++; } }); @@ -517,7 +518,7 @@ void ObjectFileDB::find_and_write_scripts(const std::string& output_dir) { }); auto file_name = combine_path(output_dir, "all_scripts.lisp"); - write_text_file(file_name, all_scripts); + file_util::write_text_file(file_name, all_scripts); printf("Found scripts:\n"); printf(" total %.3f ms\n", timer.getMs()); diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index 6a7fabda61..286005e93d 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file ObjectFileDB.h * A "database" of object files found in DGO files. diff --git a/decompiler/TypeSystem/GoalFunction.h b/decompiler/TypeSystem/GoalFunction.h index 9f593a3ff7..c682ebc593 100644 --- a/decompiler/TypeSystem/GoalFunction.h +++ b/decompiler/TypeSystem/GoalFunction.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_GOALFUNCTION_H #define JAK_DISASSEMBLER_GOALFUNCTION_H diff --git a/decompiler/TypeSystem/GoalSymbol.h b/decompiler/TypeSystem/GoalSymbol.h index f4481f5d1e..5def7a1830 100644 --- a/decompiler/TypeSystem/GoalSymbol.h +++ b/decompiler/TypeSystem/GoalSymbol.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_GOALSYMBOL_H #define JAK_DISASSEMBLER_GOALSYMBOL_H diff --git a/decompiler/TypeSystem/GoalType.h b/decompiler/TypeSystem/GoalType.h index 553f3c91a8..56853fcbef 100644 --- a/decompiler/TypeSystem/GoalType.h +++ b/decompiler/TypeSystem/GoalType.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_GOALTYPE_H #define JAK_DISASSEMBLER_GOALTYPE_H diff --git a/decompiler/TypeSystem/TypeInfo.h b/decompiler/TypeSystem/TypeInfo.h index 4044d5aa07..3c67bae707 100644 --- a/decompiler/TypeSystem/TypeInfo.h +++ b/decompiler/TypeSystem/TypeInfo.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_TYPEINFO_H #define JAK_DISASSEMBLER_TYPEINFO_H diff --git a/decompiler/TypeSystem/TypeSpec.h b/decompiler/TypeSystem/TypeSpec.h index 6657362302..3ce0afa5e6 100644 --- a/decompiler/TypeSystem/TypeSpec.h +++ b/decompiler/TypeSystem/TypeSpec.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DISASSEMBLER_TYPESPEC_H #define JAK_DISASSEMBLER_TYPESPEC_H diff --git a/decompiler/config.cpp b/decompiler/config.cpp index f633806745..9f67840882 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -1,6 +1,7 @@ #include "config.h" #include "third-party/json.hpp" #include "util/FileIO.h" +#include "common/util/FileUtil.h" Config gConfig; @@ -9,7 +10,7 @@ Config& get_config() { } void set_config(const std::string& path_to_config_file) { - auto config_str = read_text_file(path_to_config_file); + auto config_str = file_util::read_text_file(path_to_config_file); // to ignore comments in json, which may be useful auto cfg = nlohmann::json::parse(config_str, nullptr, true, true); diff --git a/decompiler/config.h b/decompiler/config.h index bc8a401a50..c2c455741a 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK2_DISASSEMBLER_CONFIG_H #define JAK2_DISASSEMBLER_CONFIG_H diff --git a/decompiler/main.cpp b/decompiler/main.cpp index 034bc819ad..85d63c64a2 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -5,6 +5,7 @@ #include "config.h" #include "util/FileIO.h" #include "TypeSystem/TypeInfo.h" +#include "common/util/FileUtil.h" int main(int argc, char** argv) { printf("Jak Disassembler\n"); @@ -26,8 +27,8 @@ int main(int argc, char** argv) { } ObjectFileDB db(dgos); - write_text_file(combine_path(out_folder, "dgo.txt"), db.generate_dgo_listing()); - write_text_file(combine_path(out_folder, "obj.txt"), db.generate_obj_listing()); + file_util::write_text_file(combine_path(out_folder, "dgo.txt"), db.generate_dgo_listing()); + file_util::write_text_file(combine_path(out_folder, "obj.txt"), db.generate_obj_listing()); db.process_link_data(); db.find_code(); diff --git a/decompiler/util/FileIO.cpp b/decompiler/util/FileIO.cpp index 5466d18653..c92bb85524 100644 --- a/decompiler/util/FileIO.cpp +++ b/decompiler/util/FileIO.cpp @@ -3,35 +3,10 @@ #include #include -std::string read_text_file(const std::string& path) { - std::ifstream file(path); - std::stringstream ss; - ss << file.rdbuf(); - return ss.str(); -} - std::string combine_path(const std::string& parent, const std::string& child) { return parent + "/" + child; } -std::vector read_binary_file(const std::string& filename) { - auto fp = fopen(filename.c_str(), "rb"); - if (!fp) - throw std::runtime_error("File " + filename + " cannot be opened"); - fseek(fp, 0, SEEK_END); - auto len = ftell(fp); - rewind(fp); - - std::vector data; - data.resize(len); - - if (fread(data.data(), len, 1, fp) != 1) { - throw std::runtime_error("File " + filename + " cannot be read"); - } - - return data; -} - std::string base_name(const std::string& filename) { size_t pos = 0; assert(!filename.empty()); @@ -70,13 +45,3 @@ uint32_t crc32(const uint8_t* data, size_t size) { uint32_t crc32(const std::vector& data) { return crc32(data.data(), data.size()); } - -void write_text_file(const std::string& file_name, const std::string& text) { - FILE* fp = fopen(file_name.c_str(), "w"); - if (!fp) { - printf("Failed to fopen %s\n", file_name.c_str()); - throw std::runtime_error("Failed to open file"); - } - fprintf(fp, "%s\n", text.c_str()); - fclose(fp); -} \ No newline at end of file diff --git a/decompiler/util/FileIO.h b/decompiler/util/FileIO.h index 8f02577f4c..74354ce4ca 100644 --- a/decompiler/util/FileIO.h +++ b/decompiler/util/FileIO.h @@ -1,15 +1,13 @@ +#pragma once + #ifndef JAK_V2_FILEIO_H #define JAK_V2_FILEIO_H #include #include -std::string read_text_file(const std::string& path); std::string combine_path(const std::string& parent, const std::string& child); -std::vector read_binary_file(const std::string& filename); std::string base_name(const std::string& filename); -void write_text_file(const std::string& file_name, const std::string& text); - void init_crc(); uint32_t crc32(const uint8_t* data, size_t size); uint32_t crc32(const std::vector& data); diff --git a/decompiler/util/LispPrint.h b/decompiler/util/LispPrint.h index 28eaf1f5db..a1b964dbdd 100644 --- a/decompiler/util/LispPrint.h +++ b/decompiler/util/LispPrint.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK2_DISASSEMBLER_LISPPRINT_H #define JAK2_DISASSEMBLER_LISPPRINT_H diff --git a/doc/compiler_example.md b/doc/compiler_example.md new file mode 100644 index 0000000000..0397785e68 --- /dev/null +++ b/doc/compiler_example.md @@ -0,0 +1,440 @@ +# Compiler Example +This describes how the compiler works on a small piece of code in `example_goal.gc`. +To run this yourself, start the compiler and runtime, then run: + +``` +(lt) +(asm-file "doc/example_goal.gc" :color :load) +``` + +And you should see: +``` +The value of 10 factorial is 3628800 +``` + +# Overview +The code to read from the GOAL REPL is in `Compiler.cpp`, in `Compiler::execute_repl`. Compiling an `asm-file` form will call `Compiler::compile_asm_file` in `CompilerControl.cpp`, which is where we'll start. + +I've divided the process into these steps: +1. __Read__: Convert from text to a representation of Lisp syntax. +2. __IR-Pass__: Convert the S-Expressions to an intermediate representation (IR). +3. __Register Allocation__: Map variables in the IR (`IRegister`s) to real hardware registers +4. __Code Generation__: Generate x86-64 instructions from the IR +5. __Object File Generation__: Put the instructions and static data in an object file and generate linking data +6. __Sending__: Send the code to the runtime +7. __Linking__: The runtime links the code so it can be run, and runs it. +8. __Result__: The code prints the message which is sent back to the REPL. + +# Reader +The reader converts GOAL/GOOS source into a `goos::Object`. One of the core ideas of lisp is that "code is data", so GOAL code is represented as GOOS data. This makes it easy for GOOS macros to operate on GOAL code. A GOOS object can represent a number, string, pair, etc. This strips out comments/whitespace. + +The reader is run with this code: +``` +auto code = m_goos.reader.read_from_file({filename}); +``` +If you were to `code.print()`, you would get: +``` +(top-level (defun factorial-iterative ((x integer)) (let ((result 1)) (while (!= x 1) (set! result (* result x)) (set! x (- x 1))) result)) (define-extern _format function) (define format _format) (let ((x 10)) (format #t "The value of ~D factorial is ~D~%" x (factorial-iterative x)))) +``` + + +There are a few details worth mentioning about this process: +- The reader will expand `'my-symbol` to `(quote my-symbol)` +- The reader will throw errors on synatx errors (mismatched parentheses, bad strings/numbers, etc.) +- Using `read_from_file` adds information about where each thing came from to a map stored in the reader. This map is used to determine the source file/line for compiler errors. + +# IR Pass +This pass converts code (represented as a `goos::Object`) into intermediate representation. This is stored in an `Env*`, a tree structure. At the top is a `GlobalEnv*`, then an `FileEnv` for each file compiled, then a `FunctionEnv` for each function in the file. There are environments within `FunctionEnv` that are used for lexical scoping and the other types of GOAL scopes. The Intermediate Representation (IR) is a list per function that's built up in order as the compiler goes through the function. Note that the IR is a list of instructions and doesn't have a tree or more complicated structure. Here's an example of the IR for the example function: + +``` +Function: function-factorial-iterative + mov-ic igpr-2, 1 + mov igpr-3, igpr-2 + goto label-10 + mov igpr-4, igpr-3 + imul igpr-4, igpr-0 + mov igpr-3, igpr-4 + mov igpr-5, igpr-0 + mov-ic igpr-6, 1 + subi igpr-5, igpr-6 + mov igpr-0, igpr-5 + mov-ic igpr-7, 1 + j(igpr-0 != igpr-7) label-3 + ret igpr-1 igpr-3 + +Function: function-top-level + mov-fa igpr-0, function-factorial-iterative + mov 'factorial-iterative, igpr-0 + mov igpr-1, '_format + mov 'format, igpr-1 + mov-ic igpr-2, 10 + mov igpr-3, igpr-2 + mov igpr-4, '#t + mov-sva igpr-5, static-string "The value of ~D factorial is ~D~%" + mov igpr-6, 'factorial-iterative + mov igpr-8, igpr-3 + call igpr-6 (ret igpr-7) (args igpr-8) + mov igpr-9, igpr-7 + mov igpr-10, 'format + mov igpr-12, igpr-4 + mov igpr-13, igpr-5 + mov igpr-14, igpr-3 + mov igpr-15, igpr-9 + call igpr-10 (ret igpr-11) (args igpr-12 igpr-13 igpr-14 igpr-15) + mov igpr-16, igpr-11 + ret igpr-17 igpr-16 +``` +The `function-top-level` is the "top level" function, which is everything not in a function. In the example, this is just defining the function, defining `format`, and calling `format`. + +You'll notice that there are a ton of `mov`s between `igpr`. The compiler inserts tons of moves. Because this is all done in a single pass, there's a lot of cases where the compiler can't know if a move is needed or not. But the register allocator can figure it out and will remove most unneeded moves. Adding moves can also prevent stack spills. For example, consider the case where you want to get the return value of function `a`, then call function `b`, then call function `c` with the return value of function `a`. If there are a lot of moves, the register allocator can figure out a way to temporarily stash the value in a saved register instead of spilling to the stack. + +Another thing to notice is that GOAL nested function calls suck. Example: +``` +(format #t "The value of ~D factorial is ~D~%" x (factorial-iterative x)) +``` +requires loading `format`, `#t`, the string, and `x` into registers, then calling `(factorial-iterative x)`, then calling `format`. This has to be done this way, just in case the `factorial-iterative` call modifies the value of `format`, `#t`, or `x`. + +## IR Pass Implementation +An important type in the compiler is `Val`, which is a specification on how to get a value. A `Val` has an associated GOAL type (`TypeSpec`) and the IR Pass should take care of all type checking. A `Val` can represent a constant, a memory location (relative to pointer, or static data, etc), a spot in an array, a register etc. A `Val` representing a register is a `RegVal`, which contains an `IRegister`. An `IRegister` is a register that's not yet mapped to the hardware, and instead has a unique integer to identify itself. The IR assumes there are infinitely many `IRegister`s, and a later stage maps `IRegister`s to real hardware registers. + +The general process starts with a `compile` function, which dispatches other `compile_` functions as needed. These generally take in `goos::Object` as a code input, emit IR into an `Env`/or modify things in the `Env`, and return a `Val*` describing the result. + +In general, GOAL is very greedy and `compile` functions emit IR to do things, then put the result in a register, and return a `RegVal`. + +However, there is an exception for memory related things. Consider +``` +(-> my-object my-field) ; my_object->my_field in C +``` +This shouldn't return a `Val` for a register containing the value of `my_object->my_field`, but should instead return something that represents "the memory location of my_field in my_object". This way you can do +``` +(set! (-> my-object my-field) val) +;; or +(& (-> my-object my-field)) ;; &my_object->my_field +``` +and the compiler will have enough information to figure out the memory address. + +If the compiler actually needs the value of something, and wants to be sure its a value in a register, it will use the `to_reg` method of `Val`. This will emit IR into the current function to get the value in a register, then return a `Val` that represents this register. Example `to_reg` implementation for integer constants: +``` +RegVal* IntegerConstantVal::to_reg(Env* fe) { + auto rv = fe->make_gpr(m_ts); + fe->emit(std::make_unique(rv, m_value)); + return rv; +} +``` + + +Note that `to_reg` can emit code like reading from memory, where the order of operations really matters, so you have to be very careful. + +It's extremely dangerous to let a memory reference `Val` propagate too far. Consider this example: + +``` +(let ((x (-> my-object my-field))) + (set! (-> my-object my-field) 12) + x + ) +``` +Where `x` should be the old value of `my-field`. The `Val` for `x` needs to be `to_reg`ed _before_ getting inside the `let`. There's also some potential confusion around the order that you compile and `to_gpr` things. In a case where you need a bunch of values in gprs, you should do the `to_gpr` immediately after compiling to match the exact behavior of the original GOAL. For example + +``` +(+ (-> my-array (* x y)) (some-function)) +;; like c++ my_array[x*y] + some_function() +``` +When we `compile` the `(-> my-array (* x y))`, it will emit code to calculate the `(*x y)`, but won't actually do the memory access until we call `to_reg` on the result. This memory access should happen __before__ `some-function` is called. + + +In general, each time you `compile` something, you should immediately `to_gpr` it, _before_ `compile`ing the next thing. Many places will only accept a `RegVal` as an input to help with this. Also, the result for almost all compilation functions should be `to_reg`ed. The only exceptions are forms which deal with memory references (address of operator, dereference operator) or math. + +Another important thing is that compilation functions should _never_ modify any existing `IRegister`s or `Val`s, unless that function is `set!`, which handles the situation correctly. Instead, create new `IRegister`s and move into those. I am planning to implement a `settable` flag to help reduce errors. + +For example: +- `RegVal` storing a local variable: is `settable`, you can modify local variables by writing to the register they use. +- `RegVal` storing the result of a memory dereference: not `settable`, you should set the memory instead. +- `RegVal` containing the result of converting a `xmm` to `gpr`: not settable, you need to set the original `xmm` instead + +The only settable `RegVal` is one corresponding to a local variable. + +## Following the Code + +This pass runs from here: +``` +auto obj_file = compile_object_file(obj_file_name, code, !no_code); +``` +That function sets up a `FileEnv*`, then runs +``` + file_env->add_top_level_function( + compile_top_level_function("top-level", std::move(code), compilation_env)); +``` +which compiles the body of the function with: +``` +auto result = compile_error_guard(code, fe.get()); +``` + +The `compile_error_guard` function takes in code (as a `goos::Object`) and a `Env*`, and returns a `Val` representing the return value of the code. It calls the `compile` function, but wraps it in a `try catch` block to catch any compilation errors and print an error message. In the case where there's no error, it just does: +``` +return compile(code, env); +``` + +The `compile` function is pretty simple: +``` +/*! + * Highest level compile function + */ +Val* Compiler::compile(const goos::Object& code, Env* env) { + switch (code.type) { + case goos::ObjectType::PAIR: + return compile_pair(code, env); + case goos::ObjectType::INTEGER: + return compile_integer(code, env); + case goos::ObjectType::SYMBOL: + return compile_symbol(code, env); + case goos::ObjectType::STRING: + return compile_string(code, env); + case goos::ObjectType::FLOAT: + return compile_float(code, env); + default: + ice("Don't know how to compile " + code.print()); + } + return get_none(); +} +``` +In our case, the code starts with `(defun..`, which is actually a GOOS macro. It throws away the docstring, creates a lambda, then stores the function in a symbol: +``` +;; Define a new function +(defmacro defun (name bindings &rest body) + (if (and + (> (length body) 1) ;; more than one thing in function + (string? (first body)) ;; first thing is a string + ) + ;; then it's a docstring and we ignore it. + `(define ,name (lambda :name ,name ,bindings ,@(cdr body))) + ;; otherwise don't ignore it. + `(define ,name (lambda :name ,name ,bindings ,@body)) + ) + ) +``` + +The compiler notices this is a macro in `compile_pair`: +``` + if (head.is_symbol()) { + // ... + + goos::Object macro_obj; + if (try_getting_macro_from_goos(head, ¯o_obj)) { + return compile_goos_macro(code, macro_obj, rest, env); + } + + // ... + } + +``` +The `compile_goos_macro` function sets up a GOOS environment and interprets the GOOS macro to generate more GOAL code: +``` +Val* Compiler::compile_goos_macro(const goos::Object& o, + const goos::Object& macro_obj, + const goos::Object& rest, + Env* env) { + auto macro = macro_obj.as_macro(); + Arguments args = m_goos.get_args(o, rest, macro->args); + auto mac_env_obj = EnvironmentObject::make_new(); // GOOS environment + auto mac_env = mac_env_obj.as_env(); + mac_env->parent_env = m_goos.global_environment.as_env(); + m_goos.set_args_in_env(o, args, macro->args, mac_env); + auto goos_result = m_goos.eval_list_return_last(macro->body, macro->body, mac_env); // evaluate GOOS macro + return compile_error_guard(goos_result, env); // compile resulting GOAL code +} +``` +and the last line of that function compiles the result of macro expansion in GOAL. + +As an example, I'm going to look at `compile_add`, which handles the `+` form, and is representative of typical compiler code for this part. We start by checking that the arguments look valid: +``` +Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); // get arguments to + in a list + if (!args.named.empty() || args.unnamed.empty()) { + throw_compile_error(form, "Invalid + form"); + } +``` + +Then we compile the first thing in the `(+ ...` form, get its type, and pick a math mode (int, float): +``` +auto first_val = compile_error_guard(args.unnamed.at(0), env); +auto first_type = first_val->type(); +auto math_type = get_math_mode(first_type); +``` +In the integer case, we first create a new variable in the IR called an `IRegister` that must be in a GPR (as opposed to an XMM floating point register), and then emit an IR instruction that sets this result register to the first argument. +``` +auto result = env->make_gpr(first_type); +env->emit(std::make_unique(result, first_val->to_gpr(env))); +``` + +Then, for each of the remaining arguments, we do: +``` + for (size_t i = 1; i < args.unnamed.size(); i++) { + env->emit(std::make_unique( + IntegerMathKind::ADD_64, result, + to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env) + ->to_gpr(env))); + } +``` +which emits an IR to add the value to the sum. The `to_math_type` will emit any code needed to convert this to the correct numeric type (returns either a numeric constant or a `RegVal` containing the value). + +An important detail is that we create a new register which will hold the result. This may seem inefficient in cases, but a later compile pass will try to make this new register be the same register as `first_val` if possible, and will eliminate the `IR_RegSet`. + +# Register Allocation + +This step figures out how to match up `IRegister`s to real hardware registers. In the case where there aren't enough hardware registers, it figures out how to "spill" variables onto the stack. The current implementation is a very greedy one, so it doesn't always succeed at doing things perfectly. The stack spilling is also not handled very efficiently, but the hope is that most functions won't require stack spilling. + +This step is run from `compile_asm_function` on the line: +``` +color_object_file(obj_file); + +void Compiler::color_object_file(FileEnv* env) { + for (auto& f : env->functions()) { + AllocationInput input; + for (auto& i : f->code()) { + input.instructions.push_back(i->to_rai()); + input.debug_instruction_names.push_back(i->print()); + } + input.max_vars = f->max_vars(); + input.constraints = f->constraints(); + + // debug prints removed + + f->set_allocations(allocate_registers(input)); + } +} +``` +The actual algorithm is too complicated to describe here, but it figures out a mapping from `IRegister`s to hardware registers. It also figures out how much space on the stack is needed for any stack spills, which saved registers will be used, and deals with aligning the stack. + + +# Code Generation +This part actually generates the static data and x86 instructions and stores them in an `ObjectGenerator`. See `CodeGenerator::do_function`. It emits the function prologue and epilogue, as well as any extra loads/stores from the stack that the register allocator added. Each `IR` gets to emit instructions with: +``` +ir->do_codegen(&m_gen, allocs, i_rec); +``` + +Each IR has its own `do_codegen` that emits the right instruction, and also any linking data that's needed. For example, instructions that access the symbol table are patched by the runtime to directly access the correct slot of the hash table, so the `do_codegen` also lets the `ObjectGenerator` know about this link: +``` +// IR_GetSymbolValue::do_codegen + +// look at register allocation result to determine hw register +auto dst_reg = get_reg(m_dest, allocs, irec); + +// add an instruction +auto instr = gen->add_instr(IGen::load32u_gpr64_gpr64_plus_gpr64_plus_s32( + dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), 0x0badbeef), irec); + +// add link info +gen->link_instruction_symbol_mem(instr, m_src->name()); +``` +here `0xbadbeef` is used as a placeholder offset - the runtime should patch this to the actual offset of the symbol. + +There's a ton of book-keeping to figure out the correct offsets for `rip`-relative addressing, or how to deal with jumps to/from IR which become multiple (or zero!) x86-64 instructions. It should all be handled by `ObjectFileGenerator`, and not in `do_codegen` or `CodeGenerator`. + +# Object File Generation +Once the `CodeGenerator` is done going through all functions and static data, it runs: +``` +return m_gen.generate_data_v3().to_vector(); +``` +This actually lays out everything in memory. It takes a few passes because x86 instructions are variable length (may even change based on which reigsters are used!), so it's a little bit tricky to figure out offsets between different instructions or instructions and data. Finally it generates link data tables, which efficiently pack together links to the same symbols into a single entry, to avoid duplicated symbol names. The link table also contains information about linking references in between different segments, as different parts of the object file may be loaded into different spots in memory, and will need to reference each other. + +This is the final result for top-level function (stored in top-level segment) +``` +;; prologue +push rbx +push rbp +push r10 +push r11 +push rbx + +;; load address of factorial-iterative function +lea rax,[rip+0x0] +;; convert to GOAL pointer +sub rax,r15 +;; store in symbol table +mov DWORD PTR [r15+r14*1+0xbadbeef],eax + +;; load _format value +mov eax,DWORD PTR [r15+r14*1+0xbadbeef] +;; store in format +mov DWORD PTR [r15+r14*1+0xbadbeef],eax + +;; load constant 10 (the greedy regalloc does poorly here) +mov eax,0xa +mov rbx,rax + +;; load format from symbol table +mov ebp,DWORD PTR [r15+r14*1+0xbadbeef] + +;; load #t from symbol table +mov r10d,DWORD PTR [r15+r14*1+0xbadbeef] + +;; get address of string +lea r11,[rip+0x0] +;; convert to GOAL pointer +sub r11,r15 + +;; get factorial-iterative from symbol table +mov ecx,DWORD PTR [r15+r14*1+0xbadbeef] +;; move 10 into argument register +mov rdi,rbx +;; convert factorial-iterative to a real pointer +add rcx,r15 +;; call factorial +call rcx + +;; move args into argument registers +mov rdi,r10 +mov rsi,r11 +mov rdx,rbx +mov rcx,rax + +;; call format +add rbp,r15 +call rbp + +;; epilogue +pop rbx +pop r11 +pop r10 +pop rbp +pop rbx +ret +``` +and the factorial function (stored in main segment) +``` +mov eax,0x1 +jmp 0x18 +mul eax,edi +movsxd rax,eax +mov ecx,0x1 +sub rdi,rcx +mov ecx,0x1 +cmp rdi,rcx +jne 0xa +ret +``` + +# Sending and Receiving +The result of `codegen_object_file` is sent with: +``` +m_listener.send_code(data); +``` +which adds a message header then just sends the code over the socket. + +The receive process is complicated, so this is just a quick summary +- `Deci2Server` receives it +- calls `GoalProtoHandler` in chunks, which stores it in a buffer (`MessBuffArea`) +- Once fully received, `WaitForMessageAndAck` will return a pointer to the message. The name of this function is totally wrong, it doesn't wait for a message, and it doesn't ack the message. +- The main loop in `KernelCheckAndDispatch` will see this message and run `ProcessListenerMessage` +- `ProcessListenerMessage` sees that it has code, copies the message to the debug heap, and links it. +``` +auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block"); +memcpy(buffer.c(), msg.c(), MessCount); +ListenerLinkBlock->value = buffer.offset + 4; +ListenerFunction->value = link_and_exec(buffer, "*listener*", 0, kdebugheap, LINK_FLAG_FORCE_DEBUG).offset; +``` +- The `link_and_exec` function doesn't actually exectue anything becuase it doesn't have the `LINK_FLAG_EXECUTE` set, it just links things. It moves the top level function and linking data to the top of the heap (temporary storage for the kernel) and keep both the main segment and debug segment of the code on the debug heap. It'll move them together and eliminate gaps before linking. After linking, the `ListenerFunction->value` will contain a pointer to the top level function, which is stored in the top temp area of the heap. This `ListenerFunction` is the GOAL `*listener-function*` symbol. +- The next time the GOAL kernel runs, it will notice that `*listener-function*` is set, then call this function, then set it to `#f` to indicate it called the function. +- This +- After this, `ClearPending()` is called, which sends all of the `print` messages with the `Deci2Server` back to the compiler. +- Because the GOAL kernel changed `ListenerFunction` to `#f`, it does a `SendAck()` to send a special `ACK` message to the compiler, saying "I got the function, ran it, and didn't crash. Now I'm ready for more messages." diff --git a/doc/example_goal.gc b/doc/example_goal.gc new file mode 100644 index 0000000000..2c1b2724b4 --- /dev/null +++ b/doc/example_goal.gc @@ -0,0 +1,18 @@ +(defun factorial-iterative ((x integer)) + (let ((result 1)) + (while (!= x 1) + (set! result (* result x)) + (set! x (- x 1)) + ) + result + ) + ) + +;; until we load KERNEL.CGO automatically, we have to do this to +;; make format work correctly. +(define-extern _format function) +(define format _format) + +(let ((x 10)) + (format #t "The value of ~D factorial is ~D~%" x (factorial-iterative x)) + ) diff --git a/doc/goal_todo.md b/doc/goal_todo.md new file mode 100644 index 0000000000..148bdb50b2 --- /dev/null +++ b/doc/goal_todo.md @@ -0,0 +1,19 @@ +Done (has documentation) +- `e` +- `:exit` +- `asm-file`, `m`, `ml` +- `listen-to-target`, `reset-target`, `:status`, `lt`, `r` + +Done (needs documentation) +- `top-level` +- `begin` +- `seval` +- `#cond`, `#when`, `#unless` + +- Macro System + + + + +Todo +- Type System diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index e51dde9e25..6ded6b6508 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -1,5 +1,5 @@ # We define our own compilation flags here. -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) # Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. @@ -78,8 +78,8 @@ add_library(runtime ${RUNTIME_SOURCE}) IF (WIN32) # set stuff for windows - target_link_libraries(gk cross_sockets mman) + target_link_libraries(gk cross_sockets mman common_util) ELSE() # set stuff for other systems - target_link_libraries(gk cross_sockets pthread) + target_link_libraries(gk cross_sockets pthread common_util) ENDIF() diff --git a/game/common/dgo_rpc_types.h b/game/common/dgo_rpc_types.h index 0a83f162ba..6675a66328 100644 --- a/game/common/dgo_rpc_types.h +++ b/game/common/dgo_rpc_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file dgo_rpc_types.h * Types used for the DGO Remote Procedure Call between the EE and the IOP diff --git a/game/common/loader_rpc_types.h b/game/common/loader_rpc_types.h index f4f8f1d7bb..f3a2968595 100644 --- a/game/common/loader_rpc_types.h +++ b/game/common/loader_rpc_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file loader_rpc_types.h * Types used for the Loader Remote Procedure Call between the EE and the IOP diff --git a/game/common/play_rpc_types.h b/game/common/play_rpc_types.h index 119d4cf04e..a467926dd0 100644 --- a/game/common/play_rpc_types.h +++ b/game/common/play_rpc_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file play_rpc_types.h * Types used for the play Remote Procedure Call between the EE and the IOP. diff --git a/game/common/player_rpc_types.h b/game/common/player_rpc_types.h index ef80030ed7..2c2d0f1fb5 100644 --- a/game/common/player_rpc_types.h +++ b/game/common/player_rpc_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file player_rpc_types.h * Types used for the player Remote Procedure Call between the EE and the IOP. diff --git a/game/common/ramdisk_rpc_types.h b/game/common/ramdisk_rpc_types.h index aa488d7a5d..23870b6d36 100644 --- a/game/common/ramdisk_rpc_types.h +++ b/game/common/ramdisk_rpc_types.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file ramdisk_rpc_types.h * Types used for the RamDisk Remote Procedure Call between the EE and the IOP diff --git a/game/fake_iso.txt b/game/fake_iso.txt index 28f3185fc3..cca0e660ae 100644 --- a/game/fake_iso.txt +++ b/game/fake_iso.txt @@ -2,8 +2,8 @@ ; Each entry should consist of an ISO name, followed by a file name ; note that tweakval, vagdir, screen1 have dummy data for now. -KERNEL.CGO resources/KERNEL.CGO -GAME.CGO resources/GAME.CGO +KERNEL.CGO out/KERNEL.CGO +GAME.CGO out/GAME.CGO TEST.CGO resources/TEST.CGO TWEAKVAL.MUS resources/TWEAKVAL.MUS VAGDIR.AYB resources/VAGDIR.AYB diff --git a/game/kernel/Ptr.h b/game/kernel/Ptr.h index 9764de9fb4..225a23f579 100644 --- a/game/kernel/Ptr.h +++ b/game/kernel/Ptr.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Ptr.h * Representation of a GOAL pointer which can be converted to/from a C pointer. diff --git a/game/kernel/asm_funcs.asm b/game/kernel/asm_funcs.asm index 2b41efe59d..753725c7ac 100644 --- a/game/kernel/asm_funcs.asm +++ b/game/kernel/asm_funcs.asm @@ -121,9 +121,9 @@ _call_goal_asm_linux: ;; set GOAL function pointer mov r13, rcx ;; offset - mov r15, r8 + mov r14, r8 ;; symbol table - mov r14, r9 + mov r15, r9 ;; call GOAL by function pointer call r13 @@ -165,8 +165,8 @@ _call_goal_asm_win32: mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument - mov r15, [rsp + 144] ;; symbol table - mov r14, [rsp + 152] ;; offset + mov r15, [rsp + 152] ;; symbol table + mov r14, [rsp + 144] ;; offset call r13 diff --git a/game/kernel/fileio.h b/game/kernel/fileio.h index d481c25d64..abd2b27e58 100644 --- a/game/kernel/fileio.h +++ b/game/kernel/fileio.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file fileio.h * GOAL Low-Level File I/O and String Utilities diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 1cd2cc1a13..d33fe4738a 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -15,6 +15,7 @@ #include "kscheme.h" #include "ksocket.h" #include "klisten.h" +#include "kprint.h" #ifdef _WIN32 #include "Windows.h" @@ -136,7 +137,30 @@ void KernelCheckAndDispatch() { auto old_listener = ListenerFunction->value; // dispatch the kernel //(**kernel_dispatcher)(); - call_goal(Ptr(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem); + + // todo remove. this is added while KERNEL.CGO is broken. + if (MasterUseKernel) { + call_goal(Ptr(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(ListenerFunction->value).c(); + for (int i = 0; i < 40; i++) { + fprintf(stderr, "%x ", cptr[i]); + } + fprintf(stderr, "\n"); + auto result = + call_goal(Ptr(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem); + fprintf(stderr, "result of listener function: %lld\n", result); +#ifdef __linux__ + cprintf("%ld\n", result); +#else + cprintf("%lld\n", result); +#endif + ListenerFunction->value = s7.offset; + } + } + ClearPending(); // if the listener function changed, it means the kernel ran it, so we should notify compiler. diff --git a/game/kernel/kboot.h b/game/kernel/kboot.h index fd53697f1b..e9ac165d55 100644 --- a/game/kernel/kboot.h +++ b/game/kernel/kboot.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kboot.h * GOAL Boot. Contains the "main" function to launch GOAL runtime. @@ -73,4 +75,6 @@ void KernelCheckAndDispatch(); */ void KernelShutdown(); +constexpr bool MasterUseKernel = false; + #endif // RUNTIME_KBOOT_H diff --git a/game/kernel/kdgo.cpp b/game/kernel/kdgo.cpp index 6181c18bb2..f57fbedf29 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/kdgo.cpp @@ -138,8 +138,8 @@ u32 InitRPC() { */ void StopIOP() { x[2] = 0x14; // todo - this type and message - RpcSync(PLAYER_RPC_CHANNEL); - RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); + // RpcSync(PLAYER_RPC_CHANNEL); + // RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); printf("IOP shut down\n"); // sceDmaSync(0x10009000, 0, 0); printf("DMA shut down\n"); diff --git a/game/kernel/kdgo.h b/game/kernel/kdgo.h index 69e8dcae2a..0135d7e555 100644 --- a/game/kernel/kdgo.h +++ b/game/kernel/kdgo.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kdgo.h * Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading. diff --git a/game/kernel/kdsnetm.h b/game/kernel/kdsnetm.h index bdbef66096..7326f74f23 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/kdsnetm.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kdsnetm.h * Low-level DECI2 wrapper for ksocket diff --git a/game/kernel/klink.h b/game/kernel/klink.h index 2ba1eea78f..76f82e485f 100644 --- a/game/kernel/klink.h +++ b/game/kernel/klink.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file klink.cpp * GOAL Linker for x86-64 diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index 4f1d733bbe..22f9aad10b 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -133,6 +133,9 @@ void ProcessListenerMessage(Ptr msg) { break; case LTT_MSG_RESET: MasterExit = 1; + if (protoBlock.msg_id == UINT64_MAX) { + MasterExit = 2; + } break; case LTT_MSG_CODE: { auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block"); @@ -146,6 +149,7 @@ void ProcessListenerMessage(Ptr 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: diff --git a/game/kernel/klisten.h b/game/kernel/klisten.h index faf656859c..f8781e33b6 100644 --- a/game/kernel/klisten.h +++ b/game/kernel/klisten.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file klisten.h * Implementation of the Listener protocol diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 168619844a..f5b611b364 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -595,7 +595,8 @@ void InitMachineScheme() { intern_from_c("*kernel-boot-level*")->value = intern_from_c(DebugBootLevel).offset; } - if (DiskBoot) { + // todo remove MasterUseKernel + if (DiskBoot && MasterUseKernel) { *EnableMethodSet = (*EnableMethodSet) + 1; load_and_link_dgo_from_c("game", kglobalheap, LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, diff --git a/game/kernel/kmachine.h b/game/kernel/kmachine.h index 01ab86b10a..f60148326e 100644 --- a/game/kernel/kmachine.h +++ b/game/kernel/kmachine.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kmachine.h * GOAL Machine. Contains low-level hardware interfaces for GOAL. diff --git a/game/kernel/kmalloc.h b/game/kernel/kmalloc.h index 4969567277..4efbaea194 100644 --- a/game/kernel/kmalloc.h +++ b/game/kernel/kmalloc.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kmalloc.h * GOAL Kernel memory allocator. diff --git a/game/kernel/kmemcard.h b/game/kernel/kmemcard.h index e22474b24c..c19f52997e 100644 --- a/game/kernel/kmemcard.h +++ b/game/kernel/kmemcard.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kmemcard.h * Memory card interface. Very messy code. diff --git a/game/kernel/kprint.cpp b/game/kernel/kprint.cpp index 0fde516a49..44f1ae27a8 100644 --- a/game/kernel/kprint.cpp +++ b/game/kernel/kprint.cpp @@ -884,6 +884,8 @@ s32 format_impl(uint64_t* args) { call_method_of_type(in, type, GOAL_PRINT_METHOD); } } else { + // TODO - if we can't throw exceptions, what is the option? + // log, break and continue? throw std::runtime_error("failed to find symbol in format!"); } } diff --git a/game/kernel/kprint.h b/game/kernel/kprint.h index 0c4d178b80..6364ab3e2c 100644 --- a/game/kernel/kprint.h +++ b/game/kernel/kprint.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kprint.h * GOAL Print. Contains GOAL I/O, Print, Format... diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index d2c41a96cb..614544af5b 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -342,7 +342,8 @@ Ptr make_function_from_c_linux(void* func) { * Create a GOAL function from a C function. This doesn't export it as a global function, it just * creates a function object on the global heap. * - * The implementation is to create a simple trampoline function which jumps to the C function. + * This creates a simple trampoline function which jumps to the C function and reorders the + * arguments to be correct for Windows. */ Ptr make_function_from_c_win32(void* func) { // allocate a function object on the global heap @@ -386,6 +387,38 @@ Ptr make_function_from_c_win32(void* func) { return mem.cast(); } +/*! + * Create a GOAL function from a C function. This calls a windows function, but doesn't scramble + * the argument order. It's supposed to be used with _format_win32 which assumes GOAL order. + */ +Ptr make_function_for_format_from_c_win32(void* func) { + // allocate a function object on the global heap + auto mem = Ptr( + alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x80)); + auto f = (uint64_t)func; + auto fp = (u8*)&f; + + int i = 0; + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = fp[j]; + } + + /* + * sub rsp, 40 + * call rax + * add rsp, 40 + * ret + */ + for (auto x : {0x48, 0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3}) { + mem.c()[i++] = x; + } + + return mem.cast(); +} + /*! * Create a GOAL function from a C function. This doesn't export it as a global function, it just * creates a function object on the global heap. @@ -440,6 +473,17 @@ Ptr make_function_symbol_from_c(const char* name, void* f) { return func; } +/*! + * Given a C function and a name, create a GOAL function and store it in the symbol with the given + * name. This is designed for _format_win32, which is special because it takes 8 arguments. + */ +Ptr make_format_function_symbol_from_c_win32(const char* name, void* f) { + auto sym = intern_from_c(name); + auto func = make_function_for_format_from_c_win32(f); + sym->value = func.offset; + return func; +} + /*! * Set the named symbol to the value. This isn't specific to functions. */ @@ -958,7 +1002,9 @@ uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, * Wrapper around _call_goal_asm for calling a GOAL function from C. */ u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { - auto st_ptr = (void*)((uint8_t*)(offset) + st); + // auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler! + void* st_ptr = (void*)st; + void* fptr = f.c(); #ifdef __linux__ return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset); @@ -1776,7 +1822,7 @@ s32 InitHeapAndSymbol() { #ifdef __linux__ make_function_symbol_from_c("_format", (void*)_format_linux); #elif _WIN32 - make_function_symbol_from_c("_format", (void*)_format_win32); + make_format_function_symbol_from_c_win32("_format", (void*)_format_win32); #endif // allocations @@ -1828,25 +1874,28 @@ s32 InitHeapAndSymbol() { intern_from_c("*boot-video-mode*")->value = 0; // load the kernel! - method_set_symbol->value++; - load_and_link_dgo_from_c("kernel", kglobalheap, - LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, - 0x400000); - method_set_symbol->value--; + // todo, remove MasterUseKernel + if (MasterUseKernel) { + method_set_symbol->value++; + load_and_link_dgo_from_c("kernel", kglobalheap, + LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, + 0x400000); + method_set_symbol->value--; - // check the kernel version! - auto kernel_version = intern_from_c("*kernel-version*")->value; - if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { - MsgErr("\n"); - MsgErr( - "dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the " - "goal> prompt (:mch) then mkee your kernel in linux.\n", - KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13, - (kernel_version >> 3) & 0xffff); - return -1; - } else { - printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13, - (kernel_version >> 3) & 0xffff); + // check the kernel version! + auto kernel_version = intern_from_c("*kernel-version*")->value; + if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { + MsgErr("\n"); + MsgErr( + "dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the " + "goal> prompt (:mch) then mkee your kernel in linux.\n", + KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + return -1; + } else { + printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + } } // setup deci2count for message counter. diff --git a/game/kernel/kscheme.h b/game/kernel/kscheme.h index 5fa1bbf33e..69cfc1518e 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/kscheme.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file kscheme.h * Implementation of GOAL runtime. diff --git a/game/kernel/ksocket.h b/game/kernel/ksocket.h index 648033e83a..41397ff1a7 100644 --- a/game/kernel/ksocket.h +++ b/game/kernel/ksocket.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file ksocket.h * GOAL Socket connection to listener using DECI2/DSNET diff --git a/game/kernel/ksound.h b/game/kernel/ksound.h index 3dd047953b..865de08950 100644 --- a/game/kernel/ksound.h +++ b/game/kernel/ksound.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file ksound.h * There's not much here. My guess is this was set up as framework to match the kmachine.cpp format, diff --git a/game/main.cpp b/game/main.cpp index c0a1fc30fc..e6a1d3c257 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -10,7 +10,9 @@ int main(int argc, char** argv) { while (true) { // run the runtime in a loop so we can reset the game and have it restart cleanly printf("gk %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); - exec_runtime(argc, argv); + if (exec_runtime(argc, argv) == 2) { + return 0; + } } return 0; } \ No newline at end of file diff --git a/game/overlord/dma.h b/game/overlord/dma.h index a973b7d499..aca9c0fb21 100644 --- a/game/overlord/dma.h +++ b/game/overlord/dma.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file dma.h * DMA Related functions for Overlord. diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index 97f85fa18b..76284f2531 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -15,6 +15,7 @@ #include "game/sce/iop.h" #include "isocommon.h" #include "overlord.h" +#include "common/util/FileUtil.h" using namespace iop; @@ -71,25 +72,15 @@ void fake_iso_init_globals() { read_in_progress = false; } -//! will hold prefix for the source folder. -static const char* next_dir = nullptr; -static const char* fake_iso_path = nullptr; - /*! * Initialize the file system. */ int FS_Init(u8* buffer) { (void)buffer; - // get path to next/. Will be set in the gk.sh launch script. - next_dir = std::getenv("NEXT_DIR"); - assert(next_dir); // get path to next/data/fake_iso.txt, the map file. char fakeiso_path[512]; - strcpy(fakeiso_path, next_dir); - fake_iso_path = std::getenv("FAKE_ISO_PATH"); - assert(fake_iso_path); - strcat(fakeiso_path, fake_iso_path); + strcpy(fakeiso_path, file_util::get_file_path({"game", "fake_iso.txt"}).c_str()); // open the map. FILE* fp = fopen(fakeiso_path, "r"); @@ -97,10 +88,13 @@ int FS_Init(u8* buffer) { fseek(fp, 0, SEEK_END); size_t len = ftell(fp); rewind(fp); - char* fakeiso = (char*)malloc(len); + char* fakeiso = (char*)malloc(len + 1); if (fread(fakeiso, len, 1, fp) != 1) { +#ifdef __linux__ assert(false); +#endif } + fakeiso[len] = '\0'; // loop over lines char* ptr = fakeiso; @@ -137,6 +131,7 @@ int FS_Init(u8* buffer) { ptr++; i++; } + e->file_path[i] = 0; fake_iso_entry_count++; } @@ -193,10 +188,8 @@ FileRecord* FS_FindIN(const char* iso_name) { static const char* get_file_path(FileRecord* fr) { assert(fr->location < fake_iso_entry_count); static char path_buffer[1024]; - strcpy(path_buffer, next_dir); -#ifdef __linux__ + strcpy(path_buffer, file_util::get_project_path().c_str()); strcat(path_buffer, "/"); -#endif strcat(path_buffer, fake_iso_entries[fr->location].file_path); return path_buffer; } diff --git a/game/overlord/fake_iso.h b/game/overlord/fake_iso.h index a120484ada..20c403b9ee 100644 --- a/game/overlord/fake_iso.h +++ b/game/overlord/fake_iso.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file fake_iso.h * This provides an implementation of IsoFs for reading a "fake iso". diff --git a/game/overlord/iso.cpp b/game/overlord/iso.cpp index b374e3388e..17424022be 100644 --- a/game/overlord/iso.cpp +++ b/game/overlord/iso.cpp @@ -597,8 +597,9 @@ u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) { // once we're done, send the header to the EE, and start reading object data if (cmd->bytes_processed == sizeof(ObjectHeader)) { - printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent to 0x%p)\n", - cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer); + // printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent + // to 0x%p)\n", + // cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer); DMA_SendToEE(&cmd->objHeader, sizeof(ObjectHeader), cmd->ee_destination_buffer); DMA_Sync(); cmd->ee_destination_buffer += sizeof(ObjectHeader); diff --git a/game/overlord/iso.h b/game/overlord/iso.h index 08477dbcbe..38d26fd370 100644 --- a/game/overlord/iso.h +++ b/game/overlord/iso.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file iso.h * CD/DVD Reading. diff --git a/game/overlord/iso_api.h b/game/overlord/iso_api.h index f3255f9214..caaadb4953 100644 --- a/game/overlord/iso_api.h +++ b/game/overlord/iso_api.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_ISO_API_H #define JAK_V2_ISO_API_H #include "isocommon.h" diff --git a/game/overlord/iso_cd.h b/game/overlord/iso_cd.h index 99a3e9b26e..922d207ea3 100644 --- a/game/overlord/iso_cd.h +++ b/game/overlord/iso_cd.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file iso_cd.cpp * IsoFs API for accessing the CD/DVD drive. diff --git a/game/overlord/iso_queue.cpp b/game/overlord/iso_queue.cpp index 9804776522..3f327bd111 100644 --- a/game/overlord/iso_queue.cpp +++ b/game/overlord/iso_queue.cpp @@ -103,7 +103,6 @@ void InitBuffers() { IsoBufferHeader* AllocateBuffer(uint32_t size) { IsoBufferHeader* buffer = TryAllocateBuffer(size); if (buffer) { - printf("--------------- allocated buffer size %d\n", size); return buffer; } else { while (true) { @@ -148,7 +147,6 @@ IsoBufferHeader* TryAllocateBuffer(uint32_t size) { */ void FreeBuffer(IsoBufferHeader* buffer) { IsoBufferHeader* b = (IsoBufferHeader*)buffer; - printf("--------------- free buffer size %d\n", b->buffer_size); if (b->buffer_size == BUFFER_PAGE_SIZE) { b->next = sFreeBuffer; sFreeBuffer = (IsoBuffer*)b; @@ -287,7 +285,6 @@ void ProcessMessageData() { // if we're done with the buffer, free it and load the next one (if there is one) if (callback_buffer->data_size == 0) { popped_command->callback_buffer = (IsoBufferHeader*)callback_buffer->next; - printf("free 1\n"); FreeBuffer(callback_buffer); } } @@ -324,7 +321,6 @@ void ReleaseMessage(IsoMessage* cmd) { while (cmd->callback_buffer) { auto old_head = cmd->callback_buffer; cmd->callback_buffer = (IsoBufferHeader*)old_head->next; - printf("free 2\n"); FreeBuffer(old_head); } diff --git a/game/overlord/iso_queue.h b/game/overlord/iso_queue.h index 283b6c4ef7..fcaa1a9ec0 100644 --- a/game/overlord/iso_queue.h +++ b/game/overlord/iso_queue.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_ISO_QUEUE_H #define JAK_V2_ISO_QUEUE_H diff --git a/game/overlord/isocommon.h b/game/overlord/isocommon.h index 1617b9a0dc..b5e29169fe 100644 --- a/game/overlord/isocommon.h +++ b/game/overlord/isocommon.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file isocommon.h * Common ISO utilities. diff --git a/game/overlord/overlord.h b/game/overlord/overlord.h index 18e3f14468..0648b29ca5 100644 --- a/game/overlord/overlord.h +++ b/game/overlord/overlord.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_OVERLORD_H #define JAK_V2_OVERLORD_H diff --git a/game/overlord/ramdisk.h b/game/overlord/ramdisk.h index 0fad7da8f3..bb64d0a76c 100644 --- a/game/overlord/ramdisk.h +++ b/game/overlord/ramdisk.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file ramdisk.cpp * A RAMDISK RPC for storing files in the extra RAM left over on the IOP. diff --git a/game/overlord/sbank.h b/game/overlord/sbank.h index 6ea2b8cbe5..557a8ba764 100644 --- a/game/overlord/sbank.h +++ b/game/overlord/sbank.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_SBANK_H #define JAK_V2_SBANK_H diff --git a/game/overlord/soundcommon.h b/game/overlord/soundcommon.h index 34bf6a8b76..20857c1dca 100644 --- a/game/overlord/soundcommon.h +++ b/game/overlord/soundcommon.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_SOUNDCOMMON_H #define JAK_V2_SOUNDCOMMON_H diff --git a/game/overlord/srpc.h b/game/overlord/srpc.h index 6cf282f62f..61d60095da 100644 --- a/game/overlord/srpc.h +++ b/game/overlord/srpc.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_SRPC_H #define JAK_V2_SRPC_H diff --git a/game/overlord/ssound.h b/game/overlord/ssound.h index e7451c135a..d467a70b74 100644 --- a/game/overlord/ssound.h +++ b/game/overlord/ssound.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_SSOUND_H #define JAK_V2_SSOUND_H diff --git a/game/overlord/stream.h b/game/overlord/stream.h index 6382244ea9..a06ac74e66 100644 --- a/game/overlord/stream.h +++ b/game/overlord/stream.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_V2_STREAM_H #define JAK_V2_STREAM_H diff --git a/game/runtime.cpp b/game/runtime.cpp index f851e11a82..bb0c7b84dc 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -123,7 +123,7 @@ void ee_runner(SystemThreadInterface& iface) { return; } - printf(" Main memory mapped at 0x%016lx\n", (u64)(g_ee_main_mem)); + printf(" Main memory mapped at 0x%016llx\n", (u64)(g_ee_main_mem)); printf(" Main memory size 0x%x bytes (%.3f MB)\n", EE_MAIN_MEM_SIZE, (double)EE_MAIN_MEM_SIZE / (1 << 20)); @@ -163,7 +163,7 @@ void ee_runner(SystemThreadInterface& iface) { */ void iop_runner(SystemThreadInterface& iface) { IOP iop; - printf("\n\n\n[IOP] Restart!\n"); + printf("[IOP] Restart!\n"); iop.reset_allocator(); ee::LIBRARY_sceSif_register(&iop); iop::LIBRARY_register(&iop); @@ -223,7 +223,7 @@ void iop_runner(SystemThreadInterface& iface) { * Main function to launch the runtime. * Arguments are currently ignored. */ -void exec_runtime(int argc, char** argv) { +u32 exec_runtime(int argc, char** argv) { (void)argc; (void)argv; @@ -253,5 +253,6 @@ void exec_runtime(int argc, char** argv) { // join and exit tm.join(); - printf("GOAL Runtime Shutdown\n"); + printf("GOAL Runtime Shutdown (code %d)\n", MasterExit); + return MasterExit; } diff --git a/game/runtime.h b/game/runtime.h index b252447ada..3f526bd563 100644 --- a/game/runtime.h +++ b/game/runtime.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file runtime.h * Setup and launcher for the runtime. @@ -9,6 +11,6 @@ #include "common/common_types.h" extern u8* g_ee_main_mem; -void exec_runtime(int argc, char** argv); +u32 exec_runtime(int argc, char** argv); #endif // JAK1_RUNTIME_H diff --git a/game/sce/deci2.h b/game/sce/deci2.h index bc29fc057a..b4149cde7c 100644 --- a/game/sce/deci2.h +++ b/game/sce/deci2.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file deci2.h * Implementation of SCE DECI2 library. diff --git a/game/sce/iop.h b/game/sce/iop.h index 5b65a0d5e0..f0ced35b3d 100644 --- a/game/sce/iop.h +++ b/game/sce/iop.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK1_IOP_H #define JAK1_IOP_H diff --git a/game/sce/libcdvd_ee.h b/game/sce/libcdvd_ee.h index 95625dda76..1fea511f7c 100644 --- a/game/sce/libcdvd_ee.h +++ b/game/sce/libcdvd_ee.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file libcdvd_ee.h * Stub implementation of the EE CD/DVD library diff --git a/game/sce/libscf.h b/game/sce/libscf.h index 662c005cde..f8e8c4b7df 100644 --- a/game/sce/libscf.h +++ b/game/sce/libscf.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK1_LIBSCF_H #define JAK1_LIBSCF_H diff --git a/game/sce/sif_ee.h b/game/sce/sif_ee.h index 3d3186e1b7..a027035905 100644 --- a/game/sce/sif_ee.h +++ b/game/sce/sif_ee.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK1_SIF_EE_H #define JAK1_SIF_EE_H diff --git a/game/sce/stubs.h b/game/sce/stubs.h index af4110d243..91d1b66b3a 100644 --- a/game/sce/stubs.h +++ b/game/sce/stubs.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK1_STUBS_H #define JAK1_STUBS_H diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 4d7f466aab..f3e2f09b61 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -64,20 +64,17 @@ bool Deci2Server::init() { if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, &opt, sizeof(opt)) < 0) { close_server_socket(); return false; - } - printf("[Deci2Server] Created Socket Options\n"); + }; if (set_socket_option(server_socket, TCP_SOCKET_LEVEL, TCP_NODELAY, &opt, sizeof(opt)) < 0) { close_server_socket(); return false; } - printf("[Deci2Server] Created TCP Socket Options\n"); if (set_socket_timeout(server_socket, 100000) < 0) { close_server_socket(); return false; } - printf("[Deci2Server] Created Socket Timeout\n"); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; @@ -197,8 +194,8 @@ void Deci2Server::run() { } auto* hdr = (Deci2Header*)(buffer); - printf("[DECI2] Got message:\n"); - printf(" %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst); + 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); hdr->rsvd = got; @@ -245,7 +242,7 @@ void Deci2Server::run() { return; } got += x > 0 ? x : 0; - hdr->rsvd += got; + hdr->rsvd = got; } } @@ -263,6 +260,7 @@ void Deci2Server::accept_thread_func() { // close new_sock = accept(server_socket, (sockaddr*)&addr, &l); if (new_sock >= 0) { + set_socket_timeout(new_sock, 100000); u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; write_to_socket(new_sock, (char*)&versions, 8); // todo, check result? server_connected = true; diff --git a/game/system/Deci2Server.h b/game/system/Deci2Server.h index c8ddbaa1da..1bd164ffe6 100644 --- a/game/system/Deci2Server.h +++ b/game/system/Deci2Server.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file Deci2Server.h * Basic implementation of a DECI2 server. diff --git a/game/system/IOP_Kernel.h b/game/system/IOP_Kernel.h index 488718ffce..c25d312322 100644 --- a/game/system/IOP_Kernel.h +++ b/game/system/IOP_Kernel.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_IOP_KERNEL_H #define JAK_IOP_KERNEL_H diff --git a/game/system/SystemThread.cpp b/game/system/SystemThread.cpp index 2650245bcb..d449f7d405 100644 --- a/game/system/SystemThread.cpp +++ b/game/system/SystemThread.cpp @@ -118,7 +118,7 @@ void SystemThreadInterface::initialization_complete() { std::unique_lock mlk(thread.initialization_mutex); thread.initialization_complete = true; thread.initialization_cv.notify_all(); - printf(" OK\n"); + printf("# %s initialized\n", thread.name.c_str()); } /*! @@ -134,36 +134,3 @@ bool SystemThreadInterface::get_want_exit() const { void SystemThreadInterface::trigger_shutdown() { thread.manager->shutdown(); } - -// TODO-Windows -#ifdef __linux__ -#include -#include - -/*! - * Get thread performance statistics and report them. - */ -void SystemThreadInterface::report_perf_stats() { - if (thread.stat_diff_timer.getMs() > 16.f) { - thread.stat_diff_timer.start(); - - uint64_t current_ns = thread.stats_timer.getNs(); - rusage stats; - getrusage(RUSAGE_THREAD, &stats); - - uint64_t current_kernel = stats.ru_stime.tv_usec + (1000000 * stats.ru_stime.tv_sec); - uint64_t current_user = stats.ru_utime.tv_usec + (1000000 * stats.ru_utime.tv_sec); - - uint64_t ns_dt = current_ns - thread.last_collection_nanoseconds; - uint64_t dt_kernel = current_kernel - thread.last_cpu_kernel; - uint64_t dt_user = current_user - thread.last_cpu_user; - - thread.cpu_kernel = dt_kernel * 1000. / (double)ns_dt; - thread.cpu_user = dt_user * 1000. / (double)ns_dt; - - thread.last_cpu_kernel = current_kernel; - thread.last_cpu_user = current_user; - thread.last_collection_nanoseconds = current_ns; - } -} -#endif \ No newline at end of file diff --git a/game/system/SystemThread.h b/game/system/SystemThread.h index 0a01af1837..a2ab512829 100644 --- a/game/system/SystemThread.h +++ b/game/system/SystemThread.h @@ -1,3 +1,5 @@ +#pragma once + /*! * @file SystemThread.h * Threads for the runtime. @@ -24,8 +26,6 @@ class SystemThreadManager; * Runs a function in a thread and provides a SystemThreadInterface to that function. * Once the thread is ready, it should tell the interface with intitialization_complete(). * Thread functions should try to return when get_want_exit() returns true. - * Thread functions should also call report_perf_stats every now and then to update performance - * statistics. */ class SystemThread { public: @@ -63,7 +63,6 @@ class SystemThreadInterface { public: SystemThreadInterface(SystemThread* p) : thread(*p) {} void initialization_complete(); - void report_perf_stats(); bool get_want_exit() const; void trigger_shutdown(); diff --git a/game/system/Timer.h b/game/system/Timer.h index fed4c826ce..cc7242bdad 100644 --- a/game/system/Timer.h +++ b/game/system/Timer.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef RUNTIME_TIMER_H #define RUNTIME_TIMER_H diff --git a/game/system/deci_common.h b/game/system/deci_common.h index 94760abca6..4adbcbace8 100644 --- a/game/system/deci_common.h +++ b/game/system/deci_common.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK_DECIM_COMMON_H #define JAK_DECIM_COMMON_H #include "common/common_types.h" diff --git a/game/system/iop_thread.h b/game/system/iop_thread.h index 3b10645d6e..bd42fc127c 100644 --- a/game/system/iop_thread.h +++ b/game/system/iop_thread.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef JAK1_IOP_THREAD_H #define JAK1_IOP_THREAD_H diff --git a/gc.sh b/gc.sh index ac079580a1..1d1e30f93a 100755 --- a/gc.sh +++ b/gc.sh @@ -3,6 +3,4 @@ # Directory of this script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -export NEXT_DIR=$DIR -export FAKE_ISO_PATH=/game/fake_iso.txt $DIR/build/goalc/goalc "$@" \ No newline at end of file diff --git a/gk.sh b/gk.sh index d49ccff100..d103729b8f 100755 --- a/gk.sh +++ b/gk.sh @@ -3,6 +3,4 @@ # Directory of this script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -export NEXT_DIR=$DIR -export FAKE_ISO_PATH=/game/fake_iso.txt $DIR/build/game/gk "$@" diff --git a/goal_src/build/all_files.gc b/goal_src/build/all_files.gc new file mode 100644 index 0000000000..438da9a6b7 --- /dev/null +++ b/goal_src/build/all_files.gc @@ -0,0 +1,511 @@ +(defglobalconstant all-goal-files + ( + "goal_src/kernel/gcommon.gc" + "goal_src/kernel/gstring-h.gc" + "goal_src/kernel/gkernel-h.gc" + "goal_src/kernel/gkernel.gc" + "goal_src/kernel/pskernel.gc" + "goal_src/kernel/gstring.gc" + "goal_src/kernel/dgo-h.gc" + "goal_src/kernel/gstate.gc" + "goal_src/engine/util/types-h.gc" + "goal_src/engine/ps2/vu1-macros.gc" + "goal_src/engine/math/math.gc" + "goal_src/engine/math/vector-h.gc" + "goal_src/engine/physics/gravity-h.gc" + "goal_src/engine/geometry/bounding-box-h.gc" + "goal_src/engine/math/matrix-h.gc" + "goal_src/engine/math/quaternion-h.gc" + "goal_src/engine/math/euler-h.gc" + "goal_src/engine/math/transform-h.gc" + "goal_src/engine/geometry/geometry-h.gc" + "goal_src/engine/math/trigonometry-h.gc" + "goal_src/engine/math/transformq-h.gc" + "goal_src/engine/geometry/bounding-box.gc" + "goal_src/engine/math/matrix.gc" + "goal_src/engine/math/transform.gc" + "goal_src/engine/math/quaternion.gc" + "goal_src/engine/math/euler.gc" + "goal_src/engine/geometry/geometry.gc" + "goal_src/engine/math/trigonometry.gc" + "goal_src/engine/sound/gsound-h.gc" + "goal_src/engine/ps2/timer-h.gc" + "goal_src/engine/ps2/timer.gc" + "goal_src/engine/ps2/vif-h.gc" + "goal_src/engine/dma/dma-h.gc" + "goal_src/engine/gfx/hw/video-h.gc" + "goal_src/engine/gfx/hw/vu1-user-h.gc" + "goal_src/engine/dma/dma.gc" + "goal_src/engine/dma/dma-buffer.gc" + "goal_src/engine/dma/dma-bucket.gc" + "goal_src/engine/dma/dma-disasm.gc" + "goal_src/engine/ps2/pad.gc" + "goal_src/engine/gfx/hw/gs.gc" + "goal_src/engine/gfx/hw/display-h.gc" + "goal_src/engine/math/vector.gc" + "goal_src/engine/load/file-io.gc" + "goal_src/engine/load/loader-h.gc" + "goal_src/engine/gfx/texture-h.gc" + "goal_src/engine/level/level-h.gc" + "goal_src/engine/camera/math-camera-h.gc" + "goal_src/engine/camera/math-camera.gc" + "goal_src/engine/gfx/font-h.gc" + "goal_src/engine/gfx/decomp-h.gc" + "goal_src/engine/gfx/hw/display.gc" + "goal_src/engine/engine/connect.gc" + "goal_src/engine/ui/text-h.gc" + "goal_src/engine/game/settings-h.gc" + "goal_src/engine/gfx/capture.gc" + "goal_src/engine/debug/memory-usage-h.gc" + "goal_src/engine/gfx/texture.gc" + "goal_src/engine/game/main-h.gc" + "goal_src/engine/anim/mspace-h.gc" + "goal_src/engine/draw/drawable-h.gc" + "goal_src/engine/draw/drawable-group-h.gc" + "goal_src/engine/draw/drawable-inline-array-h.gc" + "goal_src/engine/draw/draw-node-h.gc" + "goal_src/engine/draw/drawable-tree-h.gc" + "goal_src/engine/draw/drawable-actor-h.gc" + "goal_src/engine/draw/drawable-ambient-h.gc" + "goal_src/engine/game/task/game-task-h.gc" + "goal_src/engine/game/task/hint-control-h.gc" + "goal_src/engine/gfx/generic/generic-h.gc" + "goal_src/engine/gfx/lights-h.gc" + "goal_src/engine/gfx/ocean/ocean-h.gc" + "goal_src/engine/gfx/ocean/ocean-trans-tables.gc" + "goal_src/engine/gfx/ocean/ocean-tables.gc" + "goal_src/engine/gfx/ocean/ocean-frames.gc" + "goal_src/engine/gfx/sky/sky-h.gc" + "goal_src/engine/gfx/mood-h.gc" + "goal_src/engine/gfx/time-of-day-h.gc" + "goal_src/engine/data/art-h.gc" + "goal_src/engine/gfx/generic/generic-vu1-h.gc" + "goal_src/engine/gfx/merc/merc-h.gc" + "goal_src/engine/gfx/merc/generic-merc-h.gc" + "goal_src/engine/gfx/tie/generic-tie-h.gc" + "goal_src/engine/gfx/generic/generic-work-h.gc" + "goal_src/engine/gfx/shadow/shadow-cpu-h.gc" + "goal_src/engine/gfx/shadow/shadow-vu1-h.gc" + "goal_src/engine/ps2/memcard-h.gc" + "goal_src/engine/game/game-info-h.gc" + "goal_src/engine/gfx/wind-h.gc" + "goal_src/engine/gfx/tie/prototype-h.gc" + "goal_src/engine/anim/joint-h.gc" + "goal_src/engine/anim/bones-h.gc" + "goal_src/engine/engine/engines.gc" + "goal_src/engine/data/res-h.gc" + "goal_src/engine/data/res.gc" + "goal_src/engine/gfx/lights.gc" + "goal_src/engine/physics/dynamics-h.gc" + "goal_src/engine/target/surface-h.gc" + "goal_src/engine/target/pat-h.gc" + "goal_src/engine/game/fact-h.gc" + "goal_src/engine/anim/aligner-h.gc" + "goal_src/engine/game/game-h.gc" + "goal_src/engine/game/generic-obs-h.gc" + "goal_src/engine/camera/pov-camera-h.gc" + "goal_src/engine/util/sync-info-h.gc" + "goal_src/engine/util/smush-control-h.gc" + "goal_src/engine/physics/trajectory-h.gc" + "goal_src/engine/debug/debug-h.gc" + "goal_src/engine/target/joint-mod-h.gc" + "goal_src/engine/collide/collide-func-h.gc" + "goal_src/engine/collide/collide-mesh-h.gc" + "goal_src/engine/collide/collide-shape-h.gc" + "goal_src/engine/collide/collide-target-h.gc" + "goal_src/engine/collide/collide-touch-h.gc" + "goal_src/engine/collide/collide-edge-grab-h.gc" + "goal_src/engine/draw/process-drawable-h.gc" + "goal_src/engine/game/effect-control-h.gc" + "goal_src/engine/collide/collide-frag-h.gc" + "goal_src/engine/game/projectiles-h.gc" + "goal_src/engine/target/target-h.gc" + "goal_src/engine/gfx/depth-cue-h.gc" + "goal_src/engine/debug/stats-h.gc" + "goal_src/engine/gfx/vis/bsp-h.gc" + "goal_src/engine/collide/collide-cache-h.gc" + "goal_src/engine/collide/collide-h.gc" + "goal_src/engine/gfx/shrub/shrubbery-h.gc" + "goal_src/engine/gfx/tie/tie-h.gc" + "goal_src/engine/gfx/tfrag/tfrag-h.gc" + "goal_src/engine/gfx/background-h.gc" + "goal_src/engine/gfx/tfrag/subdivide-h.gc" + "goal_src/engine/entity/entity-h.gc" + "goal_src/engine/gfx/sprite/sprite-h.gc" + "goal_src/engine/gfx/shadow/shadow-h.gc" + "goal_src/engine/gfx/eye-h.gc" + "goal_src/engine/sparticle/sparticle-launcher-h.gc" + "goal_src/engine/sparticle/sparticle-h.gc" + "goal_src/engine/entity/actor-link-h.gc" + "goal_src/engine/camera/camera-h.gc" + "goal_src/engine/camera/cam-debug-h.gc" + "goal_src/engine/camera/cam-interface-h.gc" + "goal_src/engine/camera/cam-update-h.gc" + "goal_src/engine/debug/assert-h.gc" + "goal_src/engine/ui/hud-h.gc" + "goal_src/engine/ui/progress-h.gc" + "goal_src/engine/ps2/rpc-h.gc" + "goal_src/engine/nav/path-h.gc" + "goal_src/engine/nav/navigate-h.gc" + "goal_src/engine/load/load-dgo.gc" + "goal_src/engine/load/ramdisk.gc" + "goal_src/engine/sound/gsound.gc" + "goal_src/engine/math/transformq.gc" + "goal_src/engine/collide/collide-func.gc" + "goal_src/engine/anim/joint.gc" + "goal_src/engine/geometry/cylinder.gc" + "goal_src/engine/gfx/wind.gc" + "goal_src/engine/gfx/vis/bsp.gc" + "goal_src/engine/gfx/tfrag/subdivide.gc" + "goal_src/engine/gfx/sprite/sprite.gc" + "goal_src/engine/gfx/sprite/sprite-distort.gc" + "goal_src/engine/debug/debug-sphere.gc" + "goal_src/engine/debug/debug.gc" + "goal_src/engine/gfx/merc/merc-vu1.gc" + "goal_src/engine/gfx/merc/merc-blend-shape.gc" + "goal_src/engine/gfx/merc/merc.gc" + "goal_src/engine/gfx/ripple.gc" + "goal_src/engine/anim/bones.gc" + "goal_src/engine/gfx/generic/generic-vu0.gc" + "goal_src/engine/gfx/generic/generic.gc" + "goal_src/engine/gfx/generic/generic-vu1.gc" + "goal_src/engine/gfx/generic/generic-effect.gc" + "goal_src/engine/gfx/generic/generic-merc.gc" + "goal_src/engine/gfx/generic/generic-tie.gc" + "goal_src/engine/gfx/shadow/shadow-cpu.gc" + "goal_src/engine/gfx/shadow/shadow-vu1.gc" + "goal_src/engine/gfx/depth-cue.gc" + "goal_src/engine/gfx/font.gc" + "goal_src/engine/load/decomp.gc" + "goal_src/engine/gfx/background.gc" + "goal_src/engine/draw/draw-node.gc" + "goal_src/engine/gfx/shrub/shrubbery.gc" + "goal_src/engine/gfx/shrub/shrub-work.gc" + "goal_src/engine/gfx/tfrag/tfrag-near.gc" + "goal_src/engine/gfx/tfrag/tfrag.gc" + "goal_src/engine/gfx/tfrag/tfrag-methods.gc" + "goal_src/engine/gfx/tfrag/tfrag-work.gc" + "goal_src/engine/gfx/tie/tie.gc" + "goal_src/engine/gfx/tie/tie-near.gc" + "goal_src/engine/gfx/tie/tie-work.gc" + "goal_src/engine/gfx/tie/tie-methods.gc" + "goal_src/engine/util/sync-info.gc" + "goal_src/engine/physics/trajectory.gc" + "goal_src/engine/sparticle/sparticle-launcher.gc" + "goal_src/engine/sparticle/sparticle.gc" + "goal_src/engine/entity/entity-table.gc" + "goal_src/engine/load/loader.gc" + "goal_src/engine/game/task/task-control-h.gc" + "goal_src/engine/game/game-info.gc" + "goal_src/engine/game/game-save.gc" + "goal_src/engine/game/settings.gc" + "goal_src/engine/ambient/mood-tables.gc" + "goal_src/engine/ambient/mood.gc" + "goal_src/engine/ambient/weather-part.gc" + "goal_src/engine/gfx/time-of-day.gc" + "goal_src/engine/gfx/sky/sky-utils.gc" + "goal_src/engine/gfx/sky/sky.gc" + "goal_src/engine/gfx/sky/sky-tng.gc" + "goal_src/engine/level/load-boundary-h.gc" + "goal_src/engine/level/load-boundary.gc" + "goal_src/engine/level/load-boundary-data.gc" + "goal_src/engine/level/level-info.gc" + "goal_src/engine/level/level.gc" + "goal_src/engine/ui/text.gc" + "goal_src/engine/collide/collide-probe.gc" + "goal_src/engine/collide/collide-frag.gc" + "goal_src/engine/collide/collide-mesh.gc" + "goal_src/engine/collide/collide-touch.gc" + "goal_src/engine/collide/collide-edge-grab.gc" + "goal_src/engine/collide/collide-shape.gc" + "goal_src/engine/collide/collide-shape-rider.gc" + "goal_src/engine/collide/collide.gc" + "goal_src/engine/collide/collide-planes.gc" + "goal_src/engine/gfx/merc/merc-death.gc" + "goal_src/engine/gfx/water/water-h.gc" + "goal_src/engine/camera/camera.gc" + "goal_src/engine/camera/cam-interface.gc" + "goal_src/engine/camera/cam-master.gc" + "goal_src/engine/camera/cam-states.gc" + "goal_src/engine/camera/cam-states-dbg.gc" + "goal_src/engine/camera/cam-combiner.gc" + "goal_src/engine/camera/cam-update.gc" + "goal_src/engine/geometry/vol-h.gc" + "goal_src/engine/camera/cam-layout.gc" + "goal_src/engine/camera/cam-debug.gc" + "goal_src/engine/camera/cam-start.gc" + "goal_src/engine/draw/process-drawable.gc" + "goal_src/engine/game/task/hint-control.gc" + "goal_src/engine/ambient/ambient.gc" + "goal_src/engine/debug/assert.gc" + "goal_src/engine/game/generic-obs.gc" + "goal_src/engine/target/target-util.gc" + "goal_src/engine/target/target-part.gc" + "goal_src/engine/collide/collide-reaction-target.gc" + "goal_src/engine/target/logic-target.gc" + "goal_src/engine/target/sidekick.gc" + "goal_src/engine/game/voicebox.gc" + "goal_src/engine/target/target-handler.gc" + "goal_src/engine/target/target.gc" + "goal_src/engine/target/target2.gc" + "goal_src/engine/target/target-death.gc" + "goal_src/engine/debug/menu.gc" + "goal_src/engine/draw/drawable.gc" + "goal_src/engine/draw/drawable-group.gc" + "goal_src/engine/draw/drawable-inline-array.gc" + "goal_src/engine/draw/drawable-tree.gc" + "goal_src/engine/gfx/tie/prototype.gc" + "goal_src/engine/collide/main-collide.gc" + "goal_src/engine/game/video.gc" + "goal_src/engine/game/main.gc" + "goal_src/engine/collide/collide-cache.gc" + "goal_src/engine/entity/relocate.gc" + "goal_src/engine/debug/memory-usage.gc" + "goal_src/engine/entity/entity.gc" + "goal_src/engine/nav/path.gc" + "goal_src/engine/geometry/vol.gc" + "goal_src/engine/nav/navigate.gc" + "goal_src/engine/anim/aligner.gc" + "goal_src/engine/game/effect-control.gc" + "goal_src/engine/gfx/water/water.gc" + "goal_src/engine/game/collectables-part.gc" + "goal_src/engine/game/collectables.gc" + "goal_src/engine/game/task/task-control.gc" + "goal_src/engine/game/task/process-taskable.gc" + "goal_src/engine/camera/pov-camera.gc" + "goal_src/engine/game/powerups.gc" + "goal_src/engine/game/crates.gc" + "goal_src/engine/ui/hud.gc" + "goal_src/engine/ui/hud-classes.gc" + "goal_src/engine/ui/progress/progress-static.gc" + "goal_src/engine/ui/progress/progress-part.gc" + "goal_src/engine/ui/progress/progress-draw.gc" + "goal_src/engine/ui/progress/progress.gc" + "goal_src/engine/ui/credits.gc" + "goal_src/engine/game/projectiles.gc" + "goal_src/engine/gfx/ocean/ocean.gc" + "goal_src/engine/gfx/ocean/ocean-vu0.gc" + "goal_src/engine/gfx/ocean/ocean-texture.gc" + "goal_src/engine/gfx/ocean/ocean-mid.gc" + "goal_src/engine/gfx/ocean/ocean-transition.gc" + "goal_src/engine/gfx/ocean/ocean-near.gc" + "goal_src/engine/gfx/shadow/shadow.gc" + "goal_src/engine/gfx/eye.gc" + "goal_src/engine/util/glist-h.gc" + "goal_src/engine/util/glist.gc" + "goal_src/engine/debug/anim-tester.gc" + "goal_src/engine/debug/viewer.gc" + "goal_src/engine/debug/part-tester.gc" + "goal_src/engine/debug/default-menu.gc" + "goal_src/levels/common/texture-upload.gc" + "goal_src/levels/common/rigid-body-h.gc" + "goal_src/levels/common/water-anim.gc" + "goal_src/levels/common/dark-eco-pool.gc" + "goal_src/levels/common/rigid-body.gc" + "goal_src/levels/common/nav-enemy-h.gc" + "goal_src/levels/common/nav-enemy.gc" + "goal_src/levels/common/baseplat.gc" + "goal_src/levels/common/basebutton.gc" + "goal_src/levels/common/tippy.gc" + "goal_src/levels/common/joint-exploder.gc" + "goal_src/levels/common/babak.gc" + "goal_src/levels/common/sharkey.gc" + "goal_src/levels/common/orb-cache.gc" + "goal_src/levels/common/plat.gc" + "goal_src/levels/common/plat-button.gc" + "goal_src/levels/common/plat-eco.gc" + "goal_src/levels/common/ropebridge.gc" + "goal_src/levels/common/ticky.gc" + "goal_src/levels/common/mistycannon.gc" + "goal_src/levels/common/babak-with-cannon.gc" + "goal_src/levels/beach/air-h.gc" + "goal_src/levels/beach/air.gc" + "goal_src/levels/beach/wobbler.gc" + "goal_src/levels/beach/twister.gc" + "goal_src/levels/beach/beach-obs.gc" + "goal_src/levels/beach/bird-lady.gc" + "goal_src/levels/beach/bird-lady-beach.gc" + "goal_src/levels/beach/mayor.gc" + "goal_src/levels/beach/sculptor.gc" + "goal_src/levels/beach/pelican.gc" + "goal_src/levels/beach/lurkerworm.gc" + "goal_src/levels/beach/lurkercrab.gc" + "goal_src/levels/beach/lurkerpuppy.gc" + "goal_src/levels/beach/beach-rocks.gc" + "goal_src/levels/beach/seagull.gc" + "goal_src/levels/beach/beach-part.gc" + "goal_src/levels/village_common/villagep-obs.gc" + "goal_src/levels/village_common/oracle.gc" + "goal_src/levels/common/battlecontroller.gc" + "goal_src/levels/citadel/citadel-part.gc" + "goal_src/levels/citadel/citadel-obs.gc" + "goal_src/levels/citadel/citb-plat.gc" + "goal_src/levels/citadel/citadel-sages.gc" + "goal_src/levels/common/snow-bunny.gc" + "goal_src/levels/citadel/citb-bunny.gc" + "goal_src/levels/citadel/citb-drop-plat-CIT.gc" + "goal_src/levels/l1_only/citb-drop-plat-L1.gc" + "goal_src/levels/citadel/assistant-citadel.gc" + "goal_src/levels/darkcave/darkcave-obs.gc" + "goal_src/levels/demo/demo-obs.gc" + "goal_src/levels/common/static-screen.gc" + "goal_src/levels/finalboss/robotboss-h.gc" + "goal_src/levels/finalboss/robotboss-part.gc" + "goal_src/levels/finalboss/sage-finalboss-part.gc" + "goal_src/levels/finalboss/light-eco.gc" + "goal_src/levels/finalboss/robotboss-weapon.gc" + "goal_src/levels/finalboss/robotboss-misc.gc" + "goal_src/levels/finalboss/green-eco-lurker.gc" + "goal_src/levels/finalboss/robotboss.gc" + "goal_src/levels/finalboss/final-door.gc" + "goal_src/levels/finalboss/sage-finalboss-FIN.gc" + "goal_src/levels/l1_only/sage-finalboss-L1.gc" + "goal_src/levels/intro/evilbro.gc" + "goal_src/levels/jungleb/jungleb-obs.gc" + "goal_src/levels/jungleb/plat-flip.gc" + "goal_src/levels/jungleb/aphid.gc" + "goal_src/levels/jungleb/plant-boss.gc" + "goal_src/levels/jungle/jungle-elevator.gc" + "goal_src/levels/jungle/bouncer.gc" + "goal_src/levels/jungle/hopper.gc" + "goal_src/levels/jungle/junglesnake.gc" + "goal_src/levels/jungle/darkvine.gc" + "goal_src/levels/jungle/jungle-obs.gc" + "goal_src/levels/jungle/jungle-mirrors.gc" + "goal_src/levels/jungle/junglefish.gc" + "goal_src/levels/jungle/fisher-JUN.gc" + "goal_src/levels/jungle/fisher-JUNGLE-L1.gc" + "goal_src/levels/jungle/jungle-part.gc" + "goal_src/levels/common/launcherdoor.gc" + "goal_src/levels/l1_only/target-racer-h-L1-RACERP.gc" + "goal_src/levels/racer_common/target-racer-h-FIC-LAV-MIS-OGR-ROL.gc" + "goal_src/levels/racer_common/racer-part.gc" + "goal_src/levels/racer_common/racer.gc" + "goal_src/levels/l1_only/target-racer-L1-RACERP.gc" + "goal_src/levels/racer_common/target-racer-FIC-LAV-MIS-OGR-ROL.gc" + "goal_src/levels/l1_only/racer-states-L1-RACERP.gc" + "goal_src/levels/racer_common/racer-states-FIC-LAV-MIS-OGR-ROL.gc" + "goal_src/levels/racer_common/collide-reaction-racer.gc" + "goal_src/levels/common/blocking-plane.gc" + "goal_src/levels/flut_common/flut-part.gc" + "goal_src/levels/flut_common/flutflut.gc" + "goal_src/levels/flut_common/target-flut.gc" + "goal_src/levels/village1/farmer.gc" + "goal_src/levels/village1/explorer.gc" + "goal_src/levels/village1/assistant.gc" + "goal_src/levels/village1/sage.gc" + "goal_src/levels/village1/yakow.gc" + "goal_src/levels/l1_only/village-obs-L1.gc" + "goal_src/levels/village1/village-obs-VI1.gc" + "goal_src/levels/village1/fishermans-boat.gc" + "goal_src/levels/village1/village1-part.gc" + "goal_src/levels/village1/village1-part2.gc" + "goal_src/levels/village1/sequence-a-village1.gc" + "goal_src/levels/training/training-obs.gc" + "goal_src/levels/training/training-part.gc" + "goal_src/levels/misty/misty-obs.gc" + "goal_src/levels/misty/misty-warehouse.gc" + "goal_src/levels/misty/misty-conveyor.gc" + "goal_src/levels/misty/mud.gc" + "goal_src/levels/misty/muse.gc" + "goal_src/levels/misty/bonelurker.gc" + "goal_src/levels/misty/quicksandlurker.gc" + "goal_src/levels/misty/misty-teetertotter.gc" + "goal_src/levels/misty/balloonlurker.gc" + "goal_src/levels/misty/misty-part.gc" + "goal_src/levels/misty/sidekick-human.gc" + "goal_src/levels/firecanyon/firecanyon-part.gc" + "goal_src/levels/firecanyon/assistant-firecanyon.gc" + "goal_src/levels/village2/village2-part.gc" + "goal_src/levels/village2/village2-obs.gc" + "goal_src/levels/village2/village2-part2.gc" + "goal_src/levels/village2/gambler.gc" + "goal_src/levels/village2/warrior.gc" + "goal_src/levels/village2/geologist.gc" + "goal_src/levels/village2/swamp-blimp.gc" + "goal_src/levels/village2/sage-bluehut.gc" + "goal_src/levels/village2/flutflut-bluehut.gc" + "goal_src/levels/village2/assistant-village2.gc" + "goal_src/levels/village2/sunken-elevator.gc" + "goal_src/levels/swamp/swamp-obs.gc" + "goal_src/levels/swamp/swamp-bat.gc" + "goal_src/levels/swamp/swamp-rat.gc" + "goal_src/levels/swamp/swamp-rat-nest.gc" + "goal_src/levels/swamp/kermit.gc" + "goal_src/levels/swamp/swamp-part.gc" + "goal_src/levels/swamp/billy.gc" + "goal_src/levels/maincave/cavecrystal-light.gc" + "goal_src/levels/maincave/maincave-obs.gc" + "goal_src/levels/maincave/maincave-part.gc" + "goal_src/levels/maincave/spiderwebs.gc" + "goal_src/levels/maincave/dark-crystal.gc" + "goal_src/levels/maincave/baby-spider.gc" + "goal_src/levels/maincave/mother-spider-h.gc" + "goal_src/levels/maincave/mother-spider-egg.gc" + "goal_src/levels/maincave/mother-spider-proj.gc" + "goal_src/levels/maincave/mother-spider.gc" + "goal_src/levels/maincave/gnawer.gc" + "goal_src/levels/maincave/driller-lurker.gc" + "goal_src/levels/sunken/sunken-part.gc" + "goal_src/levels/sunken/sunken-part2.gc" + "goal_src/levels/sunken/sunken-part3.gc" + "goal_src/levels/sunken/sunken-part4.gc" + "goal_src/levels/sunken/sunken-part5.gc" + "goal_src/levels/sunken/target-tube.gc" + "goal_src/levels/sunken/sunken-obs.gc" + "goal_src/levels/sunken/shover.gc" + "goal_src/levels/sunken/square-platform.gc" + "goal_src/levels/sunken/sun-iris-door.gc" + "goal_src/levels/sunken/orbit-plat.gc" + "goal_src/levels/sunken/wedge-plats.gc" + "goal_src/levels/sunken/wall-plat.gc" + "goal_src/levels/sunken/qbert-plat.gc" + "goal_src/levels/sunken/steam-cap.gc" + "goal_src/levels/sunken/sun-exit-chamber.gc" + "goal_src/levels/sunken/floating-launcher.gc" + "goal_src/levels/sunken/sunken-water.gc" + "goal_src/levels/sunken/whirlpool.gc" + "goal_src/levels/sunken/sunken-pipegame.gc" + "goal_src/levels/sunken/bully.gc" + "goal_src/levels/sunken/double-lurker.gc" + "goal_src/levels/sunken/helix-water.gc" + "goal_src/levels/sunken/puffer.gc" + "goal_src/levels/sunken/sunken-fish.gc" + "goal_src/levels/rolling/rolling-obs.gc" + "goal_src/levels/rolling/rolling-lightning-mole.gc" + "goal_src/levels/rolling/rolling-robber.gc" + "goal_src/levels/rolling/rolling-race-ring.gc" + "goal_src/levels/firecanyon/firecanyon-obs.gc" + "goal_src/levels/ogre/ogre-part.gc" + "goal_src/levels/ogre/ogreboss.gc" + "goal_src/levels/ogre/ogre-obs.gc" + "goal_src/levels/ogre/flying-lurker.gc" + "goal_src/levels/village3/village3-part.gc" + "goal_src/levels/village3/village3-obs.gc" + "goal_src/levels/village3/minecart.gc" + "goal_src/levels/village3/miners.gc" + "goal_src/levels/village3/assistant-village3.gc" + "goal_src/levels/village3/sage-village3.gc" + "goal_src/levels/robocave/cave-trap.gc" + "goal_src/levels/robocave/spider-egg.gc" + "goal_src/levels/robocave/robocave-part.gc" + "goal_src/levels/snow/target-snowball.gc" + "goal_src/levels/snow/target-ice.gc" + "goal_src/levels/snow/ice-cube.gc" + "goal_src/levels/snow/snow-ball.gc" + "goal_src/levels/snow/snow-obs.gc" + "goal_src/levels/snow/snow-flutflut-obs.gc" + "goal_src/levels/snow/snow-bumper.gc" + "goal_src/levels/snow/snow-ram-h.gc" + "goal_src/levels/snow/snow-ram-boss.gc" + "goal_src/levels/snow/snow-ram.gc" + "goal_src/levels/snow/snow-part.gc" + "goal_src/levels/snow/yeti.gc" + "goal_src/levels/lavatube/lavatube-obs.gc" + "goal_src/levels/lavatube/lavatube-energy.gc" + "goal_src/levels/lavatube/lavatube-part.gc" + "goal_src/levels/lavatube/assistant-lavatube.gc" + "goal_src/levels/title/title-obs.gc" + "goal_src/old/lava/lava.gc" + ) + ) \ No newline at end of file diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc new file mode 100644 index 0000000000..89cb46f6fd --- /dev/null +++ b/goal_src/goal-lib.gc @@ -0,0 +1,204 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; OTHER STUFF +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; get list of all goal files +(asm-file "goal_src/build/all_files.gc") + +;; tell compiler about stuff defined/implemented in the runtime. +(asm-file "goal_src/kernel-defs.gc") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; BUILD SYSTEM +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; compile, color, and save a file +(defmacro m (file) + `(asm-file ,file :color :write) + ) + +;; compile, color, load and save a file +(defmacro ml (file) + `(asm-file ,file :color :load :write) + ) + +(desfun make-build-command (file) + `(asm-file ,file :color :write) + ) + +(defmacro build-game () + `(begin + ,@(apply make-build-command all-goal-files) + (build-dgos "goal_src/build/dgos.txt") + ) + ) + +(defmacro blg () + `(begin + (build-game) + (dgo-load "kernel" global #xf #x200000) + (dgo-load "game" global #xf #x200000) + ) + ) + +(defmacro e () + `(:exit) + ) + +(defmacro db () + `(begin + (set-config! print-ir #t) + (set-config! print-regalloc #t) + ) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CONDITIONAL COMPILATION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro #when (clause &rest body) + `(#cond (,clause ,@body)) + ) + +(defmacro #unless (clause &rest body) + `(#cond ((not ,clause) ,@body)) + ) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TARGET CONTROL +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro lt (&rest args) + ;; shortcut for listen-to-target. also sends a :status command to make sure + ;; all buffers on the target are flushed. + `(begin + (listen-to-target ,@args) + (:status) + ) + ) + +(defmacro r (&rest args) + ;; shortcut to completely reset the target and connect, regardless of current state + `(begin + ;; connect, so we can send reset. if we're already connected, does nothing + (listen-to-target ,@args) + ;; send a reset message, disconnecting us + (reset-target) + ;; establish connection again + (listen-to-target ,@args) + ;; flush buffers + (:status) + ) + ) + + +;;;;;;;;;;;;;;;;;;; +;; GOAL Syntax +;;;;;;;;;;;;;;;;;;; +;; Bind vars in body +(defmacro let (bindings &rest body) + `((lambda :inline-only #t ,(apply first bindings) ,@body) + ,@(apply second bindings))) + +;; Let, but recursive, allowing you to define variables in terms of others. +(defmacro let* (bindings &rest body) + (if (null? bindings) + `(begin ,@body) + `((lambda :inline-only #t (,(caar bindings)) + (let* ,(cdr bindings) ,@body)) + ,(car (cdar bindings)) + ) + ) + ) + +;; Define a new function +(defmacro defun (name bindings &rest body) + (if (and + (> (length body) 1) ;; more than one thing in function + (string? (first body)) ;; first thing is a string + ) + ;; then it's a docstring and we ignore it. + `(define ,name (lambda :name ,name ,bindings ,@(cdr body))) + ;; otherwise don't ignore it. + `(define ,name (lambda :name ,name ,bindings ,@body)) + ) + ) + +(defmacro while (test &rest body) + (with-gensyms (reloop test-exit) + `(begin + (goto ,test-exit) + (label ,reloop) + ,@body + (label ,test-exit) + (when-goto ,test ,reloop) + #f + ) + ) + ) + +;; Backup some values, and restore after executing body. +;; Non-dynamic (nonlocal jumps out of body will skip restore) +(defmacro protect (defs &rest body) + (if (null? defs) + ;; nothing to backup, just insert body (base case) + `(begin ,@body) + + ;; a unique name for the thing we are backing up + (with-gensyms (backup) + ;; store the original value of the first def in backup + `(let ((,backup ,(first defs))) + ;; backup any other things which need backing up + (protect ,(cdr defs) + ;; execute the body + ,@body + ) + ;; restore the first thing + (set! ,(first defs) ,backup) + ) + ) + ) + ) + +(defmacro +! (place amount) + `(set! ,place (+ ,place ,amount)) + ) + +;; todo, handle too many arguments correct +(defmacro if (condition true-case &rest others) + (if (null? others) + `(cond (,condition ,true-case)) + `(cond (,condition ,true-case) + (else ,(first others)) + ) + ) + ) + +;;;;;;;;;;;;;;;;;;; +;; Math Macros +;;;;;;;;;;;;;;;;;;; + +(defmacro +1 (var) + `(+ ,var 1) + ) + +(defmacro +! (place amount) + `(set! ,place (+ ,place ,amount)) + ) + +(defmacro +1! (place) + `(set! ,place (+ 1 ,place)) + ) + +(defmacro -! (place amount) + `(set! ,place (- ,place ,amount)) + ) + +(defmacro *! (place amount) + `(set! ,place (* ,place ,amount)) + ) + +(defmacro 1- (var) + `(- ,var 1) + ) \ No newline at end of file diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc new file mode 100644 index 0000000000..862d2c7ff9 --- /dev/null +++ b/goal_src/kernel-defs.gc @@ -0,0 +1,148 @@ +;; kernel-defs.gc +;; everything defined in the C Kernel / runtime + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; kscheme - InitHeapAndSymbol +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; fixed symbols +(define-extern #f symbol) +(define-extern #t symbol) +(define-extern function type) +(define-extern basic type) +(define-extern string type) +(define-extern symbol type) +(define-extern type type) +(define-extern object type) +(define-extern link-block type) +(define-extern integer type) +(define-extern sinteger type) +(define-extern uinteger type) +(define-extern binteger type) +(define-extern int8 type) +(define-extern int16 type) +(define-extern int32 type) +(define-extern int64 type) +(define-extern int128 type) +(define-extern uint8 type) +(define-extern uint16 type) +(define-extern uint32 type) +(define-extern uint64 type) +(define-extern uint128 type) +(define-extern float type) +(define-extern process-tree type) +(define-extern process type) +(define-extern thread type) +(define-extern structure type) +(define-extern pair type) +(define-extern pointer type) +(define-extern number type) +(define-extern array type) +(define-extern vu-function type) +(define-extern connectable type) +(define-extern stack-frame type) +(define-extern file-stream type) +(define-extern kheap type) +(define-extern nothing (function none)) +(define-extern delete-basic (function basic none)) +(define-extern static symbol) +(define-extern global kheap) +(define-extern debug kheap) +(define-extern loading-level kheap) ;; not a kheap at boot +(define-extern loading-package kheap) ;; not a kheap at boot +(define-extern process-level-heap kheap) ;; not a kheap at boot +(define-extern stack symbol) +(define-extern scratch symbol) +(define-extern *stratch-top* pointer) +(define-extern zero-func (function int)) +;; asize-of-basic-func - has a bug +;; copy-basic-func - has a bug +;; level - unknown type +;; art-group +;; texture-page-dir +;; texture-page +;; sound +;; dgo +;; top-level + +(define-extern string->symbol (function string symbol)) +(define-extern print (function object object)) +(define-extern inspect (function object object)) +(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 malloc (function kheap int pointer)) +(define-extern kmalloc (function kheap int int string)) +(define-extern new-dynamic-structure (function kheap type int structure)) +(define-extern method-set! (function type int function none)) ;; may actually return function. +(define-extern link (function pointer string int kheap int pointer)) +(define-extern dgo-load (function string kheap int int none)) +(define-extern link-begin (function pointer string int kheap int int)) +(define-extern link-resume (function int)) +;; mc-run +;; mc-format +;; mc-unformat +;; mc-create-file +;; mc-save +;; mc-load +;; mc-check-result +;; mc-get-slot-info +;; mc-makefile +;; kset-language + +(define-extern *debug-segment* symbol) +(define-extern *enable-method-set* int) +;; *boot-video-mode* ? +(define-extern *deci-count* int) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; klisten - InitListener +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; *listener-link-block* +;; *listener-function* +;; kernel-dispatcher +;; kernel-packages +;; *print-column* + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; kmachine - InitMachineScheme +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; put-display-env +;; syncv +;; sync-path +;; reset-path +;; rest-graph +;; dma-sync +;; gs-put-imr +;; gs-get-imr +;; gs-store-image +;; flush-cache +;; cpad-open +;; cpad-get-data +;; install-handler +;; install-debug-handler +;; file-stream-open +;; file-stream-close +;; file-stream-length +;; file-stream-seek +;; file-stream-read +;; file-stream-write +;; scf-get-language +;; scf-get-time +;; scf-get-aspect +;; scf-get-volume +;; scf-get-territory +;; scf-get-timeout +;; scf-get-inactive-timeout +;; dma-to-iop +;; kernel-shutdown +;; aybabtu +;; *stack-top* +;; *stack-base* +;; *stack-size* +;; *kernel-boot-message* +;; *kernel-boot-mode* +;; *kernel-boot-level* \ No newline at end of file diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 0f99919f9d..b12f231059 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -5,3 +5,262 @@ ;; name in dgo: gcommon ;; dgos: KERNEL +;; gcommon is the first file compiled and loaded. +;; it's expected that this function will mostly be hand-decompiled + + + +;; The "identity" returns its input unchanged. It uses the special GOAL "object" +;; type, which can basically be anything, so this will work on integers, floats, +;; strings, structures, arrays, etc. The only things which doesn't work with "object" +;; is a 128-bit integer. The upper 64-bits of the integer will usually be lost. +(defun identity ((x object)) + ;; there is an optional "docstring" that can go at the beginning of a function + "Function which returns its input. The first function of the game!" + + ;; the last thing in the function body is the return value. This is like "return x;" in C + ;; the return type of the function is figured out automatically by the compiler + ;; you don't have to specify it manually. + x + ) + + +(defun 1/ ((x float)) + "Reciprocal floating point" + + ;; this function computes 1.0 / x. GOAL allows strange function names like "1/". + + ;; Declaring this an inline function is like a C inline function, however code is + ;; still generated so it can be used a function object. GOAL inline functions have type + ;; checking, so they are preferable to macros when possible, to get better error messages. + (declare (inline)) + + ;; the division form will pick the math type (float, int) based on the type of the first + ;; argument. In this case, "1." is a floating point constant, so this becomes a floating point division. + (/ 1. x) + ) + +(defun + ((x int) (y int)) + "Compute the sum of two integers" + + ;; this wraps the compiler's built-in handling of "add two integers" in a GOAL function. + ;; now "+" can be used as a function object, but is limited to adding two integers when used like this. + ;; The compiler is smart enough to not use this function unless "+" is being used as a function object. + ;; ex: (+ a b c), (+ a b) ; won't use this function, uses built-in addition + ;; (set-combination-function! my-thing +) ; + becomes a function pointer in this case + (+ x y) + ) + +(defun - ((x int) (y int)) + "Compute the difference of two integers" + (- x y) + ) + +(defun * ((x int) (y int)) + "Compute the product of two integers" + ;; TODO - verify that this matches the PS2 exactly. + ;; Uses mult (three operand form) in MIPS + (* x y) + ) + +(defun / ((x int) (y int)) + "Compute the quotient of two integers" + ;; TODO - verify this matches the PS2 exactly + (/ x y) + ) + +(defun ash ((value integer) (shift-amount integer)) + "Arithmetic shift value by shift-amount. + A positive shift-amount will shift to the left and a negative will shift to the right. + " + + ;; currently the compiler does not support "ash", so this function is also used to implement "ash". + ;; in the future, the compiler should be able to use constant propagation to turn constant shifts + ;; into x86 constant shifts when possible (which are faster). The GOAL compiler seems to do this. + + ;; The original implementation was inline assembly, to take advantage of branch delay slots: + ;; (or v1 a0 r0) ;; likely inserted by register coloring, not entirely needed + ;; (bgezl a1 end) ;; branch to function end if positive shift (left)... + ;; (dsllv v0 v1 a1) ;; do left shift in delay slot + ;; + ;; (dsubu a0 r0 a1) ;; negative shift amount for right shift + ;; (dsrav v0 v1 a0) ;; do right shift + ;; (label end) + + (declare (inline)) + (if (> shift-amount 0) + ;; these correspond to x86-64 variable shift instructions. + ;; the exact behavior of GOAL shifts (signed/unsigned) are unknown so for now shifts must + ;; be manually specified. + (shlv value shift-amount) + (sarv value (- shift-amount)) + ) + ) + +(defun mod ((a integer) (b integer)) + "Compute mod. It does what you expect for positive numbers. For negative numbers, nobody knows what to expect. + This is a 32-bit operation. It uses an idiv on x86 and gets the remainder." + + ;; The original implementation is div, mfhi + ;; todo - verify this is exactly the same as the PS2. + (mod a b) + ) + + +(defun rem ((a integer) (b integer)) + "Compute remainder (32-bit). It is identical to mod. It uses a idiv and gets the remainder" + + ;; The original implementation is div, mfhi + ;; todo - verify this is exactly the same as the PS2. + (mod a b) + ) + +(defun abs ((a int)) + "Take the absolute value of an integer" + + ;; short function, good candidate for inlining + (declare (inline)) + + ;; The original implementation was inline assembly, to take advantage of branch delay slots: + ;; (or v0 a0 r0) ;; move input to output unchanged, for positive case + ;; (bltzl v0 end) ;; if negative, execute the branch delay slot below... + ;; (dsubu v0 r0 v0) ;; negate + ;; (label end) + + + (if (> a 0) ;; condition is "a > 0" + a ;; true case, return a + (- a) ;; false case, return -a. (- a) is like (- 0 a) + ) + ) + +(defun min ((a integer) (b integer)) + "Compute minimum." + + ;; The original implementation was inline assembly, to take advantage of branch delay slots: + ;; (or v0 a0 r0) ;; move first arg to output (case of second arg being min) + ;; (or v1 a1 r0) ;; move second arg to v1 (likely strange coloring) + ;; (slt a0 v0 v1) ;; compare args + ;; (movz v0 v1 a0) ;; conditional move the second arg to v0 if it's the minimum + + (declare (inline)) + (if (> a b) b a) + ) + +(defun max ((a integer) (b integer)) + "Compute maximum." + (declare (inline)) + (if (> a b) a b) + ) + +(defun logior ((a integer) (b integer)) + "Compute the bitwise inclusive-or" + (logior a b) + ) + +(defun logand ((a integer) (b integer)) + "Compute the bitwise and" + (logand a b) + ) + +(defun lognor ((a integer) (b integer)) + "Compute not or." + ;; Note - MIPS has a 'nor' instruction, but x86 doesn't. + ;; the GOAL x86 compiler therefore doesn't have a nor operation, + ;; so lognor is implemented by this inline function instead. + (declare (inline)) + (lognot (logior a b)) + ) + +(defun logxor ((a integer) (b integer)) + "Compute the logical exclusive-or" + (logxor a b) + ) + +(defun lognot ((a integer)) + "Compute the bitwise not" + (lognot a) + ) + +(defun false-func () + "Return false" + ;; In GOAL, #f is false. It's a symbol. Each symbol exists as an object, and each symbol has a value + ;; The value of the false symbol #f is the false symbol #f. + + ;; To get the symbol, instead of its value, we use quote. Writing 'x is equivalent to (quote x) + '#f + ) + +(defun true-func () + "Return true" + ;; GOAL consideres anything that's not #f to be true. But there's also an explicit true symbol. + '#t + ) + +;; The C Kernel implements the format function and creates a trampoline function in the GOAL heap which jumps to +;; format. (In OpenGOAL, there's actually two trampoline functions, to make the 8 arguments all work.) +;; For some reason, the C Kernel names this trampoline function _format. We need to set the value of format +;; _format in order for format to work. + +;; I suspect this was to let us define (yet another) function here which set up C-style var args (supported from C Kernel) +;; or 128-bit arguments (unimplemented in C Kernel), but both of these were never finished. +(define format _format) + +;; TODO - vec4s + +;; The "boxed float" type "bfloat" is just a float wrapped in a basic (structure type that has runtime type information) +;; it's a way to have a floating point number that knows its a floating point number and can print/inspect itself +;; Compared to a normal float, it's much less efficient, so this is used extremely rarely. + +;; a GOAL deftype contains the following: +;; - type name +;; - parent type name +;; - field list +;; - method declarations +;; - additional options +;; It has "asserts" that can be used to make sure that the type is laid out in memory in the same way as the game. +;; You provide the actual offsets/sizes/method ids, and if there is a mismatch, it throws a compiler error. +;; The decompile will generate these automatically in the future. + +;; Type Name: should be a unique name. Can't be the name of a function or global variable. In this case, it's bfloat +;; Parent Type: Should be the name of the parent type ("basic" in this case). Will inherit fields and methods from the parent. +;; children of "basic" are structure types with runtime type information. +;; Field List: each field of the type, listed as (name type-name [options]) +;; use the :offset-assert X to do a check at comile-time that the OpenGOAL compiler places the field at the given offset. +;; if the compiler came up with a different offset, it will create an error. This used to make sure the memory layout matches +;; the original game. +;; Method Declarations: Any methods which are defined in this type but not the parent must be declared here. +;; you may optionally declare methods defined only in the parent, or defined in both the parent and child (overridden methods) +;; the method declarations is (method-name (arg-list) return-type [optional-id-assert]) +;; the optional id assert is used to check that the compiler places the method in the given slot of the method table. +;; like the offset-assert, it's used to make sure the type hierarchy matches the game. +;; Note that the special type "_type_" can be used in methods args/returns to indicate "the type of the object method is called on". +;; this is used for 2 things: +;; 1. Child who overrides it can use their own type as an argument, rather than a less specific parent type. +;; 2. Caller who calls an overriden method and knows it at compile time can know a return type more specifically. + + +(deftype bfloat (basic) + ;; fields + ((data float :offset-assert 4)) ;; field "data" is a float. + ;; methods + (:methods (print (_type_) _type_ 2) ;; we will override print later on. This is optional to include + (inspect (_type_) _type_ 3) ;; this is a parent method we won't override. This is also optional to inlcude + ) + + ;; options + + ;; make sure the size of the type is correct (compare to value from game) + :size-assert 8 + ;; make sure method count is correct (again, compare to value from game) + :method-count-assert 9 + ;; flags passed to the new_type function in the runtime, compare from game + :flag-assert #x900000008 + ) + + +(defmethod print bfloat ((obj bfloat)) + "Override the default print method to print a bfloat like a normal float" + (format #t "~f" (-> obj data)) + obj + ) \ No newline at end of file diff --git a/goal_src/test/test-add-function-returns.gc b/goal_src/test/test-add-function-returns.gc new file mode 100644 index 0000000000..85989a928d --- /dev/null +++ b/goal_src/test/test-add-function-returns.gc @@ -0,0 +1,16 @@ +(defun return-one () + 1) + +(defun return-sum ((a integer) (b integer)) + (+ a b) + ) + +(defun return-plus-two ((in integer)) + (+ in 2) + ) + +(defun return-plus-three ((in integer)) + (+ 3 in) + ) + +(+ 2 (return-one) (return-plus-three 2) (return-plus-two 3) (return-sum 1 2) (return-plus-two 3)) \ No newline at end of file diff --git a/goal_src/test/test-add-int-constants.gc b/goal_src/test/test-add-int-constants.gc new file mode 100644 index 0000000000..422b5923f5 --- /dev/null +++ b/goal_src/test/test-add-int-constants.gc @@ -0,0 +1 @@ +(+ 15 -2) \ No newline at end of file diff --git a/goal_src/test/test-add-int-multiple-2.gc b/goal_src/test/test-add-int-multiple-2.gc new file mode 100644 index 0000000000..82a3684b08 --- /dev/null +++ b/goal_src/test/test-add-int-multiple-2.gc @@ -0,0 +1,11 @@ +(defun add-five-v2 ((a int32) (b int32) (c int32) (d int32) (e int32)) + (let* ((total-1 a) + (also-d d) + (total-2 (+ total-1 b)) + (total-3 (+ c total-2)) + ) + (+ total-3 e also-d) + ) + ) + +(add-five 1 2 3 4 5) diff --git a/goal_src/test/test-add-int-multiple.gc b/goal_src/test/test-add-int-multiple.gc new file mode 100644 index 0000000000..5e13e42805 --- /dev/null +++ b/goal_src/test/test-add-int-multiple.gc @@ -0,0 +1,5 @@ +(defun add-five ((a integer) (b integer) (c integer) (d integer) (e integer)) + (+ c d (+ e a) b) + ) + +(add-five 1 2 3 4 5) \ No newline at end of file diff --git a/goal_src/test/test-add-int-vars.gc b/goal_src/test/test-add-int-vars.gc new file mode 100644 index 0000000000..3de3bff655 --- /dev/null +++ b/goal_src/test/test-add-int-vars.gc @@ -0,0 +1,5 @@ +(defun add-two ((x integer) (y integer)) + (+ x y) + ) + +(add-two 3 4) \ No newline at end of file diff --git a/goal_src/test/test-application-lambda-1.gc b/goal_src/test/test-application-lambda-1.gc new file mode 100644 index 0000000000..b52a9418c9 --- /dev/null +++ b/goal_src/test/test-application-lambda-1.gc @@ -0,0 +1 @@ +((lambda :inline-only #t (x y z) y) 1 2 3) \ No newline at end of file diff --git a/goal_src/test/test-ash.gc b/goal_src/test/test-ash.gc new file mode 100644 index 0000000000..e93ae2c3ec --- /dev/null +++ b/goal_src/test/test-ash.gc @@ -0,0 +1,10 @@ +(defun ash ((value integer) (shift-amount integer)) + (declare (inline)) + (if (> shift-amount 0) + (shlv value shift-amount) + (sarv value (- shift-amount)) + ) + ) + + +(+ (ash (+ 1 2) (/ 6 2)) (ash (- 12) (- 1))) \ No newline at end of file diff --git a/goal_src/test/test-build-game.gc b/goal_src/test/test-build-game.gc new file mode 100644 index 0000000000..fe473f2e15 --- /dev/null +++ b/goal_src/test/test-build-game.gc @@ -0,0 +1,4 @@ +(build-game) +(dgo-load "kernel" global #xf #x200000) ; todo, remove once kernel loads itself. +(dgo-load "game" global #xf #x200000) +1 \ No newline at end of file diff --git a/goal_src/test/test-conditional-compilation-1.gc b/goal_src/test/test-conditional-compilation-1.gc new file mode 100644 index 0000000000..27e30228fb --- /dev/null +++ b/goal_src/test/test-conditional-compilation-1.gc @@ -0,0 +1,12 @@ +;; test the use of #cond to evaluate goos expressions at compile time + +(#cond + ((> 2 (+ 2 1)) + 1 + (invalid-code) + ) + + ((< 2 (+ 1 2)) + 3 + ) + ) \ No newline at end of file diff --git a/goal_src/test/test-declare-inline.gc b/goal_src/test/test-declare-inline.gc new file mode 100644 index 0000000000..1ca61f64ed --- /dev/null +++ b/goal_src/test/test-declare-inline.gc @@ -0,0 +1,7 @@ +(defun inline-test-function-1 ((x int)) + ;; inline this function by default. + (declare (inline)) + (* 4 x) + ) + +(inline-test-function-1 8) \ No newline at end of file diff --git a/goal_src/test/test-defglobalconstant-1.gc b/goal_src/test/test-defglobalconstant-1.gc new file mode 100644 index 0000000000..ce99baba6b --- /dev/null +++ b/goal_src/test/test-defglobalconstant-1.gc @@ -0,0 +1,5 @@ +(defglobalconstant my-constant 12) +(defglobalconstant my-constant 17) +(defglobalconstant not-my-consant 13) + +my-constant \ No newline at end of file diff --git a/goal_src/test/test-defglobalconstant-2.gc b/goal_src/test/test-defglobalconstant-2.gc new file mode 100644 index 0000000000..781eae4074 --- /dev/null +++ b/goal_src/test/test-defglobalconstant-2.gc @@ -0,0 +1,8 @@ +(defmacro get-goos-by-name (name) + ;; do the lookup in the goos global environment + (eval name) + ) + +(defglobalconstant my-constant 18) + +(get-goos-by-name my-constant) \ No newline at end of file diff --git a/goal_src/test/test-define-1.gc b/goal_src/test/test-define-1.gc new file mode 100644 index 0000000000..2dfc9036fe --- /dev/null +++ b/goal_src/test/test-define-1.gc @@ -0,0 +1,10 @@ +(define first-var 1) +(define second-var 2) +(define first-var 12) + +(begin + (define first-var 13) + (define second-var 12) + (define first-var 17) + second-var + first-var) diff --git a/goal_src/test/test-defsmacro-defgmacro.gc b/goal_src/test/test-defsmacro-defgmacro.gc new file mode 100644 index 0000000000..8917115af9 --- /dev/null +++ b/goal_src/test/test-defsmacro-defgmacro.gc @@ -0,0 +1,13 @@ +(defsmacro test-macro (x) + ;; goos macro + `(+ ,x 2) + ) + +(defmacro test-macro (x) + ;; goal macro which calls a goos macro of the same name + (let ((goos-expansion (test-macro x))) + `(+ ,goos-expansion 3) + ) + ) + +(test-macro 15) \ No newline at end of file diff --git a/goal_src/test/test-defun-return-constant.gc b/goal_src/test/test-defun-return-constant.gc new file mode 100644 index 0000000000..c23ae10ec6 --- /dev/null +++ b/goal_src/test/test-defun-return-constant.gc @@ -0,0 +1,10 @@ +(defun return-13 () + 13) + +(defun return-12 () + 12) + +(defun return-11 () + 11) + +(return-12) \ No newline at end of file diff --git a/goal_src/test/test-defun-return-symbol.gc b/goal_src/test/test-defun-return-symbol.gc new file mode 100644 index 0000000000..727b5e0d7e --- /dev/null +++ b/goal_src/test/test-defun-return-symbol.gc @@ -0,0 +1,8 @@ +(define my-number 36) + +(defun return-my-number () + my-number) + +(define my-number 42) + +(return-my-number) \ No newline at end of file diff --git a/goal_src/test/test-deref-simple.gc b/goal_src/test/test-deref-simple.gc new file mode 100644 index 0000000000..58057af93b --- /dev/null +++ b/goal_src/test/test-deref-simple.gc @@ -0,0 +1 @@ +(print (-> type parent parent)) 0 \ No newline at end of file diff --git a/goal_src/test/test-desfun.gc b/goal_src/test/test-desfun.gc new file mode 100644 index 0000000000..5e8f8f100f --- /dev/null +++ b/goal_src/test/test-desfun.gc @@ -0,0 +1,10 @@ +(desfun square (x) + (* x x) + ) + + +(defmacro call-square (x) + (square x) + ) + +(call-square 2) \ No newline at end of file diff --git a/goal_src/test/test-div-1.gc b/goal_src/test/test-div-1.gc new file mode 100644 index 0000000000..d8b1fc6cb5 --- /dev/null +++ b/goal_src/test/test-div-1.gc @@ -0,0 +1 @@ +(+ 1 2 (/ (* 2 5) (+ 1 2))) \ No newline at end of file diff --git a/goal_src/test/test-div-2.gc b/goal_src/test/test-div-2.gc new file mode 100644 index 0000000000..bb66062495 --- /dev/null +++ b/goal_src/test/test-div-2.gc @@ -0,0 +1,3 @@ +(let ((x 30)) + (+ (/ x 10) 4) + ) \ No newline at end of file diff --git a/goal_src/test/test-factorial-loop.gc b/goal_src/test/test-factorial-loop.gc new file mode 100644 index 0000000000..5eac59b436 --- /dev/null +++ b/goal_src/test/test-factorial-loop.gc @@ -0,0 +1,11 @@ +(defun factorial-iterative ((x integer)) + (let ((result 1)) + (while (!= x 1) + (set! result (* result x)) + (set! x (- x 1)) + ) + result + ) + ) + +(factorial-iterative 10) \ No newline at end of file diff --git a/goal_src/test/test-factorial-recursive.gc b/goal_src/test/test-factorial-recursive.gc new file mode 100644 index 0000000000..3909817669 --- /dev/null +++ b/goal_src/test/test-factorial-recursive.gc @@ -0,0 +1,12 @@ +;; for now, recursive functions need to forward declare so they have their +;; return type. +;(defun-extern factorial-recursive ((x integer)) integer) +(define-extern factorial-recursive (function integer integer)) + +(defun factorial-recursive ((x integer)) + (cond ((= x 1) x) + (else (* x (factorial-recursive (- x 1)))) + ) + ) + +(factorial-recursive 10) \ No newline at end of file diff --git a/goal_src/test/test-float-function.gc b/goal_src/test/test-float-function.gc new file mode 100644 index 0000000000..bc8f695f88 --- /dev/null +++ b/goal_src/test/test-float-function.gc @@ -0,0 +1,8 @@ +(defun float-testing-function ((x float) (y float)) + (* x y (* x x)) + ) + +(let ((x (float-testing-function (* 1.2 1.2) 3.4))) + (format #t "~,,3f~%" x) + ) +0 \ No newline at end of file diff --git a/goal_src/test/test-float-in-symbol.gc b/goal_src/test/test-float-in-symbol.gc new file mode 100644 index 0000000000..4f1f42467e --- /dev/null +++ b/goal_src/test/test-float-in-symbol.gc @@ -0,0 +1,2 @@ +(define float-symbol 2345.6) +(format #t "~f~%" float-symbol) \ No newline at end of file diff --git a/goal_src/test/test-float-pow-function.gc b/goal_src/test/test-float-pow-function.gc new file mode 100644 index 0000000000..eeb7db8973 --- /dev/null +++ b/goal_src/test/test-float-pow-function.gc @@ -0,0 +1,12 @@ +(defun pow-test ((base float) (exponent integer)) + (let ((result base)) + (while (> exponent 1) + (*! result base) + (-! exponent 1) + ) + result + ) + ) + +(format #t "~,,0f~%" (pow-test 2.0 8)) +0 \ No newline at end of file diff --git a/goal_src/test/test-float-product.gc b/goal_src/test/test-float-product.gc new file mode 100644 index 0000000000..76f414e72f --- /dev/null +++ b/goal_src/test/test-float-product.gc @@ -0,0 +1 @@ +(format #t "~f~%" (* 1.2 25.0 4.0)) \ No newline at end of file diff --git a/goal_src/test/test-floating-point-1.gc b/goal_src/test/test-floating-point-1.gc new file mode 100644 index 0000000000..96660318c1 --- /dev/null +++ b/goal_src/test/test-floating-point-1.gc @@ -0,0 +1 @@ +1.234 \ No newline at end of file diff --git a/goal_src/test/test-format-reg-order.gc b/goal_src/test/test-format-reg-order.gc new file mode 100644 index 0000000000..4339feca42 --- /dev/null +++ b/goal_src/test/test-format-reg-order.gc @@ -0,0 +1,5 @@ +(define-extern _format function) +(define format _format) + +(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6) +0 \ No newline at end of file diff --git a/goal_src/test/test-format.gc b/goal_src/test/test-format.gc new file mode 100644 index 0000000000..88c4df9bd9 --- /dev/null +++ b/goal_src/test/test-format.gc @@ -0,0 +1,49 @@ +(define-extern _format function) +(define format _format) + +(format #t "test newline~%newline~%") +(format #t "test tilde ~~ ~%") + +;; test g, G + +(format #t "test A print boxed-string: ~a~%" "boxed string!") +(format #t "test A print symbol: ~A~%" 'a-symbol) +(format #t "test A make boxed object longer: ~17A!~%" "srt") +(format #t "test A non-default pad: ~12,'za~%" 'pad-me) +(format #t "test A shorten(4): ~4a~%" 'a234567) +(format #t "test A don'tchange(4): ~4a~%" 'a234) +(format #t "test A shorten with pad(4): ~4,'za~%" 'shorten-me) +(format #t "test A a few things ~a ~a ~a ~a~%" "one thing" 'a-second integer print) + + +(format #t "test S ~s ~s ~s~%" "a string" 'a-symbol "another string!") + +(format #t "test C ~c ~c~%" 41 #x5d) + +(format #t "test P (no type) ~p~%" print) +(format #t "test P (with type) ~`integer`p~%" print) + +(format #t "test I (no type) ~i" inspect) +(format #t "test I (with type) ~`integer`i" inspect) + +(format #t "test X ~x ~10x ~10,'.x ~3x ~3,'.x~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef) + +(format #t "test D ~d ~10d ~10,'.d ~3d ~3,'.d~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef) + +(format #t "test B ~b ~10b ~10,'.b ~3b ~3,'.b~%" #xbaadbeef #x2 #x3 #x0badbeef #x0badbeef) + +;; test f, F, r, R, m, M + +(format #t "test E ~E ~e ~e~%" 1 20 301) +(format #t "test pass through ~10,'b,2W~%") + +(format #t "test tab~Taftertab~%") +(format #t "test many ~d ~d ~d ~d ~d~%" 1 2 3 4 5) + + +;; test floats +(format #t "test f ~F ~12,'0,2f ~f~%" -1.234 1.234 -1.234) +(format #t "test r ~R ~12,'0,2r ~r~%" 1.234 1.567 1.987) +(format #t "test m ~M ~12,'0,2m ~m~%" 1.234 1.567 1.987) +(format #t "test as float ~`float`P~%" -99987623.23123) +0 \ No newline at end of file diff --git a/goal_src/test/test-function-return-arg.gc b/goal_src/test/test-function-return-arg.gc new file mode 100644 index 0000000000..dba9c6aeff --- /dev/null +++ b/goal_src/test/test-function-return-arg.gc @@ -0,0 +1,4 @@ +(defun return-second-arg (one two three) + two) + +(return-second-arg 1 23 4) \ No newline at end of file diff --git a/goal_src/test/test-function-return-constant-float.gc b/goal_src/test/test-function-return-constant-float.gc new file mode 100644 index 0000000000..521fbd537f --- /dev/null +++ b/goal_src/test/test-function-return-constant-float.gc @@ -0,0 +1,5 @@ +(defun return-const-float () + 3.1415 + ) +(format #t "~,,5f~%" (return-const-float)) +0 \ No newline at end of file diff --git a/goal_src/test/test-get-symbol-1.gc b/goal_src/test/test-get-symbol-1.gc new file mode 100644 index 0000000000..3ac5f6a806 --- /dev/null +++ b/goal_src/test/test-get-symbol-1.gc @@ -0,0 +1 @@ +'#f \ No newline at end of file diff --git a/goal_src/test/test-get-symbol-2.gc b/goal_src/test/test-get-symbol-2.gc new file mode 100644 index 0000000000..8a7c3981c4 --- /dev/null +++ b/goal_src/test/test-get-symbol-2.gc @@ -0,0 +1 @@ +'#t \ No newline at end of file diff --git a/goal_src/test/test-goto-1.gc b/goal_src/test/test-goto-1.gc new file mode 100644 index 0000000000..b310f67891 --- /dev/null +++ b/goal_src/test/test-goto-1.gc @@ -0,0 +1,9 @@ + + +(block my-block + 1 + (goto skip-early-return) + (return-from my-block 2) + (label skip-early-return) + 3 + ) diff --git a/goal_src/test/test-inline-call.gc b/goal_src/test/test-inline-call.gc new file mode 100644 index 0000000000..bc69c9f118 --- /dev/null +++ b/goal_src/test/test-inline-call.gc @@ -0,0 +1,7 @@ +(defun inline-test-function-2 ((x integer)) + ;; inline this function by default. + (declare (allow-inline)) + (* 4 x) + ) + +(+ (inline-test-function-2 8) ((inline inline-test-function-2) 3)) \ No newline at end of file diff --git a/goal_src/test/test-let-1.gc b/goal_src/test/test-let-1.gc new file mode 100644 index 0000000000..edeb37cec0 --- /dev/null +++ b/goal_src/test/test-let-1.gc @@ -0,0 +1,4 @@ + (let ((x 1) + (y (test-function 1 2 3 4)) + (z 3)) + y) \ No newline at end of file diff --git a/goal_src/test/test-let-star-1.gc b/goal_src/test/test-let-star-1.gc new file mode 100644 index 0000000000..9623ac0b68 --- /dev/null +++ b/goal_src/test/test-let-star-1.gc @@ -0,0 +1,13 @@ +(define *test-result* + (let* ((x 1) + (y 2) + (z 3) + (a 4) + (b (test-function x y z a)) + (d 5) + ) + b + ) + ) + +*test-result* \ No newline at end of file diff --git a/goal_src/test/test-logand.gc b/goal_src/test/test-logand.gc new file mode 100644 index 0000000000..b6bf10077b --- /dev/null +++ b/goal_src/test/test-logand.gc @@ -0,0 +1 @@ +(logand #b100100 #b011100) ; 4 \ No newline at end of file diff --git a/goal_src/test/test-logior.gc b/goal_src/test/test-logior.gc new file mode 100644 index 0000000000..a85f87c8de --- /dev/null +++ b/goal_src/test/test-logior.gc @@ -0,0 +1 @@ +(logior #b100100 #b011100) \ No newline at end of file diff --git a/goal_src/test/test-logxor.gc b/goal_src/test/test-logxor.gc new file mode 100644 index 0000000000..d7109eb44e --- /dev/null +++ b/goal_src/test/test-logxor.gc @@ -0,0 +1 @@ +(logxor #b100100 #b011100) \ No newline at end of file diff --git a/goal_src/test/test-min-max.gc b/goal_src/test/test-min-max.gc new file mode 100644 index 0000000000..f0ae6c12ee --- /dev/null +++ b/goal_src/test/test-min-max.gc @@ -0,0 +1 @@ +(+ (min 1 2) (min 2 (min -1 -4)) (max 10 (min 2 23)) (max 3 1)) \ No newline at end of file diff --git a/goal_src/test/test-mlet.gc b/goal_src/test/test-mlet.gc new file mode 100644 index 0000000000..0b034d8cde --- /dev/null +++ b/goal_src/test/test-mlet.gc @@ -0,0 +1,7 @@ +(defglobalconstant c1 3) + +(+ c1 (mlet ((c1 4)) + c1 + ) + c1 + ) diff --git a/goal_src/test/test-mod.gc b/goal_src/test/test-mod.gc new file mode 100644 index 0000000000..18dfb311da --- /dev/null +++ b/goal_src/test/test-mod.gc @@ -0,0 +1,3 @@ +(let ((x 33)) + (+ (mod x 10) 4) + ) diff --git a/goal_src/test/test-mul-1.gc b/goal_src/test/test-mul-1.gc new file mode 100644 index 0000000000..40a27765ad --- /dev/null +++ b/goal_src/test/test-mul-1.gc @@ -0,0 +1,5 @@ +(defun product ((a integer) (b integer)) + (* b a) + ) + +(product 4 -3) \ No newline at end of file diff --git a/goal_src/test/test-negative-integer-symbol.gc b/goal_src/test/test-negative-integer-symbol.gc new file mode 100644 index 0000000000..bc0082ea7c --- /dev/null +++ b/goal_src/test/test-negative-integer-symbol.gc @@ -0,0 +1,2 @@ +(define a-negative-symbol -123) +a-negative-symbol \ No newline at end of file diff --git a/goal_src/test/test-nested-blocks-1.gc b/goal_src/test/test-nested-blocks-1.gc new file mode 100644 index 0000000000..00bc13bca7 --- /dev/null +++ b/goal_src/test/test-nested-blocks-1.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from inner-block 7) + 5 + 6 + ) + ) \ No newline at end of file diff --git a/goal_src/test/test-nested-blocks-2.gc b/goal_src/test/test-nested-blocks-2.gc new file mode 100644 index 0000000000..f421a20275 --- /dev/null +++ b/goal_src/test/test-nested-blocks-2.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from inner-block 7) + 5 + ) + 8 + ) \ No newline at end of file diff --git a/goal_src/test/test-nested-blocks-3.gc b/goal_src/test/test-nested-blocks-3.gc new file mode 100644 index 0000000000..cd1e3b1889 --- /dev/null +++ b/goal_src/test/test-nested-blocks-3.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from outer-block 7) + 5 + ) + 8 + ) \ No newline at end of file diff --git a/goal_src/test/test-nested-float-functions.gc b/goal_src/test/test-nested-float-functions.gc new file mode 100644 index 0000000000..d00a5d7936 --- /dev/null +++ b/goal_src/test/test-nested-float-functions.gc @@ -0,0 +1,15 @@ +(define-extern _format function) +(define format _format) + +(defun float-testing-function-2 ((x float) (y float)) + (format #t "i ~f ~f~%" x y) + (let ((result (* x y (* x x)))) + (format #t "r ~f~%" result) + result + ) + ) + +(let ((x (float-testing-function-2 (* 1.2 1.2) 3.4))) + (format #t "~,,3f ~,,3f~%" (float-testing-function-2 1.2000 x) x) + ) +0 \ No newline at end of file diff --git a/goal_src/test/test-nested-function-call-2.gc b/goal_src/test/test-nested-function-call-2.gc new file mode 100644 index 0000000000..ef7719679b --- /dev/null +++ b/goal_src/test/test-nested-function-call-2.gc @@ -0,0 +1,16 @@ +(defun first-function ((a integer)) + (+ a 2) + (+ a 3) + (+ a 4) + (+ a 6) + (+ a 10) + ) + +(defun second-function ((b integer)) + (if (> 2 b) + 0 + 10 + ) + ) + +(second-function (second-function (second-function 3))) diff --git a/goal_src/test/test-nested-function-call.gc b/goal_src/test/test-nested-function-call.gc new file mode 100644 index 0000000000..13e5749515 --- /dev/null +++ b/goal_src/test/test-nested-function-call.gc @@ -0,0 +1,8 @@ +(defun r2 (one two three) + two) + +(defun r1 (one two three) + one) + + +(r2 1 (r1 2 3 4) 5) \ No newline at end of file diff --git a/goal_src/test/test-protect.gc b/goal_src/test/test-protect.gc new file mode 100644 index 0000000000..7c7025e243 --- /dev/null +++ b/goal_src/test/test-protect.gc @@ -0,0 +1,15 @@ +(let ((var1 10) + (var2 20) + (sum 0) + ) + + (protect (var1 var2) + (set! var1 1) + (set! var2 2) + (+! sum var1) + (+! sum var2) + ) + (+! sum var1) + (+! sum var2) + sum + ) diff --git a/goal_src/test/test-quote-symbol.gc b/goal_src/test/test-quote-symbol.gc new file mode 100644 index 0000000000..c97a0473ea --- /dev/null +++ b/goal_src/test/test-quote-symbol.gc @@ -0,0 +1,4 @@ +(define my-thing 'apple) +(set! my-thing 'banana) +(format #t "~A~%" my-thing) +0 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-1.gc b/goal_src/test/test-return-integer-1.gc new file mode 100644 index 0000000000..6185b4d3b4 --- /dev/null +++ b/goal_src/test/test-return-integer-1.gc @@ -0,0 +1,2 @@ +; simply return an integer +#x123456789 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-2.gc b/goal_src/test/test-return-integer-2.gc new file mode 100644 index 0000000000..254b43b371 --- /dev/null +++ b/goal_src/test/test-return-integer-2.gc @@ -0,0 +1 @@ +#x17 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-3.gc b/goal_src/test/test-return-integer-3.gc new file mode 100644 index 0000000000..e85c88864c --- /dev/null +++ b/goal_src/test/test-return-integer-3.gc @@ -0,0 +1 @@ +-17 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-4.gc b/goal_src/test/test-return-integer-4.gc new file mode 100644 index 0000000000..f6712382ee --- /dev/null +++ b/goal_src/test/test-return-integer-4.gc @@ -0,0 +1 @@ +-2147483648 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-5.gc b/goal_src/test/test-return-integer-5.gc new file mode 100644 index 0000000000..0a0dce03c3 --- /dev/null +++ b/goal_src/test/test-return-integer-5.gc @@ -0,0 +1 @@ +-2147483649 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-6.gc b/goal_src/test/test-return-integer-6.gc new file mode 100644 index 0000000000..c227083464 --- /dev/null +++ b/goal_src/test/test-return-integer-6.gc @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-7.gc b/goal_src/test/test-return-integer-7.gc new file mode 100644 index 0000000000..bf79039cdd --- /dev/null +++ b/goal_src/test/test-return-integer-7.gc @@ -0,0 +1 @@ +-123 \ No newline at end of file diff --git a/goal_src/test/test-set-symbol.gc b/goal_src/test/test-set-symbol.gc new file mode 100644 index 0000000000..910d3eef60 --- /dev/null +++ b/goal_src/test/test-set-symbol.gc @@ -0,0 +1,18 @@ + +(defun burp ((thing integer)) + (* thing 3) + ) + +(define thing 2) + +(set! thing 3) + + +(+ (let ((thing 4)) + (set! thing (+ thing 1)) + thing + ) + (burp thing) + (set! thing 4) + thing + ) diff --git a/goal_src/test/test-shiftvs.gc b/goal_src/test/test-shiftvs.gc new file mode 100644 index 0000000000..09f438eafa --- /dev/null +++ b/goal_src/test/test-shiftvs.gc @@ -0,0 +1,2 @@ +(+ (shlv 2 3) (shlv 1 0) (shlv 0 4) (shrv 2 3) (shrv 10 2) (shlv -2 1) (sarv -16 2)) +;; 16 1 0 0 2 -4 -4 \ No newline at end of file diff --git a/goal_src/test/test-simple-function-call.gc b/goal_src/test/test-simple-function-call.gc new file mode 100644 index 0000000000..f98a80b7cc --- /dev/null +++ b/goal_src/test/test-simple-function-call.gc @@ -0,0 +1,2 @@ +(define-extern test-function (function int int int int int)) +(test-function 1 2 3 4) \ No newline at end of file diff --git a/goal_src/test/test-string-constant-1.gc b/goal_src/test/test-string-constant-1.gc new file mode 100644 index 0000000000..3f3443b559 --- /dev/null +++ b/goal_src/test/test-string-constant-1.gc @@ -0,0 +1,5 @@ + +(define-extern inspect (function object object)) +(inspect "this is a string") +(define x "this is also a string") +(inspect x) \ No newline at end of file diff --git a/goal_src/test/test-string-constant-2.gc b/goal_src/test/test-string-constant-2.gc new file mode 100644 index 0000000000..8a9164bfea --- /dev/null +++ b/goal_src/test/test-string-constant-2.gc @@ -0,0 +1,2 @@ +(define-extern print (function object object)) +(print "test string!") \ No newline at end of file diff --git a/goal_src/test/test-string-symbol.gc b/goal_src/test/test-string-symbol.gc new file mode 100644 index 0000000000..a4dd8b68fb --- /dev/null +++ b/goal_src/test/test-string-symbol.gc @@ -0,0 +1 @@ +(print (string->symbol "test-string")) \ No newline at end of file diff --git a/goal_src/test/test-sub-1.gc b/goal_src/test/test-sub-1.gc new file mode 100644 index 0000000000..a0556c9610 --- /dev/null +++ b/goal_src/test/test-sub-1.gc @@ -0,0 +1,16 @@ +(defun negative ((a integer)) + (- a) + ) + +(defun minus-two ((a integer)) + (- a 2) + ) + + +(defun sub-test ((a integer) (b integer)) + (- 1 a b) + ) + +(let ((x (sub-test 1 (minus-two 7)))) + (- 1 x (- -2)) + ) diff --git a/goal_src/test/test-sub-2.gc b/goal_src/test/test-sub-2.gc new file mode 100644 index 0000000000..321c930931 --- /dev/null +++ b/goal_src/test/test-sub-2.gc @@ -0,0 +1,17 @@ +(defun negative ((a integer)) + (- a) + ) + +(defun minus-two ((a integer)) + (- a 2) + ) + + +(defun sub-test ((a integer) (b integer)) + (- 1 a b) + ) + +(let ((x (sub-test 1 (minus-two 7)))) + (- 1 (negative -2) x) + ) + diff --git a/goal_src/test/test-three-reg-add.gc b/goal_src/test/test-three-reg-add.gc new file mode 100644 index 0000000000..16d3aa5265 --- /dev/null +++ b/goal_src/test/test-three-reg-add.gc @@ -0,0 +1,3 @@ +(let ((x 3)) + (+ (+ x 1) x) + ) \ No newline at end of file diff --git a/goal_src/test/test-three-reg-mult.gc b/goal_src/test/test-three-reg-mult.gc new file mode 100644 index 0000000000..5dc9a1ce43 --- /dev/null +++ b/goal_src/test/test-three-reg-mult.gc @@ -0,0 +1,6 @@ + +(let ((x 3)) + (* x 4) + (* x) + x + ) diff --git a/goal_src/test/test-three-reg-sub.gc b/goal_src/test/test-three-reg-sub.gc new file mode 100644 index 0000000000..994d0619cc --- /dev/null +++ b/goal_src/test/test-three-reg-sub.gc @@ -0,0 +1,5 @@ +(let ((x 3)) + (- x 1) + (- x) + x + ) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 5365a1f48c..e21bec115e 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,5 +1,3 @@ -add_subdirectory(util) -add_subdirectory(goos) add_subdirectory(listener) add_library(compiler @@ -12,7 +10,19 @@ add_library(compiler compiler/Env.cpp compiler/Val.cpp compiler/IR.cpp + compiler/CompilerSettings.cpp compiler/CodeGenerator.cpp + compiler/StaticObject.cpp + compiler/compilation/Atoms.cpp + compiler/compilation/CompilerControl.cpp + compiler/compilation/Block.cpp + compiler/compilation/Macro.cpp + compiler/compilation/Math.cpp + compiler/compilation/Define.cpp + compiler/compilation/Function.cpp + compiler/compilation/ControlFlow.cpp + compiler/compilation/Type.cpp + compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp regalloc/Allocator.cpp @@ -22,9 +32,10 @@ add_library(compiler add_executable(goalc main.cpp) IF (WIN32) - target_link_libraries(compiler fmt mman type_system) -ELSE () - target_link_libraries(compiler fmt type_system) -ENDIF() + target_link_libraries(compiler goos type_system listener mman common_util) -target_link_libraries(goalc util goos compiler) \ No newline at end of file +ELSE () + target_link_libraries(compiler goos type_system listener common_util) +ENDIF () + +target_link_libraries(goalc goos compiler type_system) diff --git a/goalc/compiler/CodeGenerator.cpp b/goalc/compiler/CodeGenerator.cpp index 2f78d6224d..c7513e21cf 100644 --- a/goalc/compiler/CodeGenerator.cpp +++ b/goalc/compiler/CodeGenerator.cpp @@ -1,3 +1,137 @@ - - #include "CodeGenerator.h" +#include "goalc/emitter/IGen.h" +#include "IR.h" + +using namespace emitter; +constexpr int GPR_SIZE = 8; +constexpr int XMM_SIZE = 16; + +CodeGenerator::CodeGenerator(FileEnv* env) : m_fe(env) {} + +std::vector CodeGenerator::run() { + for (auto& f : m_fe->functions()) { + m_gen.add_function_to_seg(f->segment); + } + + // todo, static objects + for (auto& static_obj : m_fe->statics()) { + static_obj->generate(&m_gen); + } + + for (size_t i = 0; i < m_fe->functions().size(); i++) { + do_function(m_fe->functions().at(i).get(), i); + } + // for (auto& f : m_fe->functions()) { + // do_function(f.get()); + // } + + return m_gen.generate_data_v3().to_vector(); +} + +void CodeGenerator::do_function(FunctionEnv* env, int f_idx) { + auto f_rec = m_gen.get_existing_function_record(f_idx); + // auto f_rec = m_gen.add_function_to_seg(env->segment); // todo, extra alignment settings + + auto& ri = emitter::gRegInfo; + const auto& allocs = env->alloc_result(); + + // compute how much stack we will use + int stack_offset = 0; + + // back up xmms + for (auto& saved_reg : allocs.used_saved_regs) { + if (saved_reg.is_xmm()) { + m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm8s(RSP, XMM_SIZE)); + m_gen.add_instr_no_ir(f_rec, IGen::store128_gpr64_xmm128(RSP, saved_reg)); + stack_offset += XMM_SIZE; + } + } + + // back up gprs + for (auto& saved_reg : allocs.used_saved_regs) { + if (saved_reg.is_gpr()) { + m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(saved_reg)); + stack_offset += GPR_SIZE; + } + } + + bool bonus_push = false; + int manually_added_stack_offset = GPR_SIZE * allocs.stack_slots; + stack_offset += manually_added_stack_offset; + + if (manually_added_stack_offset || allocs.needs_aligned_stack_for_spills || + env->needs_aligned_stack()) { + if (!(stack_offset & 15)) { + if (manually_added_stack_offset) { + manually_added_stack_offset += 8; + } else { + bonus_push = true; + m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(ri.get_saved_gpr(0))); + } + stack_offset += 8; + } + + assert(stack_offset & 15); + + if (manually_added_stack_offset) { + m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(RSP, manually_added_stack_offset)); + } + } + + // TODO EMIT FUNCTIONS + for (int ir_idx = 0; ir_idx < int(env->code().size()); ir_idx++) { + auto& ir = env->code().at(ir_idx); + auto i_rec = m_gen.add_ir(f_rec); + + auto& bonus = allocs.stack_ops.at(ir_idx); + for (auto& op : bonus.ops) { + if (op.load) { + if (op.reg.is_gpr()) { + m_gen.add_instr(IGen::load64_gpr64_plus_s32(op.reg, op.slot * GPR_SIZE, RSP), i_rec); + } else { + assert(false); + } + } + } + ir->do_codegen(&m_gen, allocs, i_rec); + for (auto& op : bonus.ops) { + if (op.store) { + if (op.reg.is_gpr()) { + m_gen.add_instr(IGen::store64_gpr64_plus_s32(RSP, op.slot * GPR_SIZE, op.reg), i_rec); + } else { + assert(false); + } + } + } + } + + // EPILOGUE + if (manually_added_stack_offset || allocs.needs_aligned_stack_for_spills || + env->needs_aligned_stack()) { + if (manually_added_stack_offset) { + m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(RSP, manually_added_stack_offset)); + } + + if (bonus_push) { + assert(!manually_added_stack_offset); + m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(ri.get_saved_gpr(0))); + } + } + + for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) { + auto& saved_reg = allocs.used_saved_regs.at(i); + if (saved_reg.is_gpr()) { + m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(saved_reg)); + } + } + + for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) { + auto& saved_reg = allocs.used_saved_regs.at(i); + if (saved_reg.is_xmm()) { + m_gen.add_instr_no_ir(f_rec, IGen::load128_xmm128_gpr64(saved_reg, RSP)); + m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm8s(RSP, XMM_SIZE)); + } + } + + m_gen.add_instr_no_ir(f_rec, IGen::ret()); +} \ No newline at end of file diff --git a/goalc/compiler/CodeGenerator.h b/goalc/compiler/CodeGenerator.h index 6d1ca719f7..125ee3b677 100644 --- a/goalc/compiler/CodeGenerator.h +++ b/goalc/compiler/CodeGenerator.h @@ -1,8 +1,20 @@ - +#pragma once #ifndef JAK_CODEGENERATOR_H #define JAK_CODEGENERATOR_H -class CodeGenerator {}; +#include "Env.h" +#include "goalc/emitter/ObjectGenerator.h" + +class CodeGenerator { + public: + CodeGenerator(FileEnv* env); + std::vector run(); + + private: + void do_function(FunctionEnv* env, int f_idx); + emitter::ObjectGenerator m_gen; + FileEnv* m_fe; +}; #endif // JAK_CODEGENERATOR_H diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index e1353df059..7c45a5824d 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -1,19 +1,73 @@ #include "Compiler.h" #include "goalc/logger/Logger.h" +#include "common/link_types.h" +#include "IR.h" +#include "goalc/regalloc/allocate.h" +#include +#include + +using namespace goos; Compiler::Compiler() { init_logger(); + init_settings(); m_ts.add_builtin_types(); + m_global_env = std::make_unique(); + m_none = std::make_unique(m_ts.make_typespec("none")); + + // todo - compile library + Object library_code = m_goos.reader.read_from_file({"goal_src", "goal-lib.gc"}); + compile_object_file("goal-lib", library_code, false); } -void Compiler::execute_repl() {} +void Compiler::execute_repl() { + while (!m_want_exit) { + try { + // 1). get a line from the user (READ) + std::string prompt; + if (m_listener.is_connected()) { + prompt = "gc"; + } else { + prompt = "g"; + } + Object code = m_goos.reader.read_from_stdin(prompt); + + // 2). compile + auto obj_file = compile_object_file("repl", code, m_listener.is_connected()); + if (m_settings.debug_print_ir) { + obj_file->debug_print_tl(); + } + + if (!obj_file->is_empty()) { + // 3). color + color_object_file(obj_file); + + // 4). codegen + auto data = codegen_object_file(obj_file); + + // 4). send! + if (m_listener.is_connected()) { + m_listener.send_code(data); + if (!m_listener.most_recent_send_was_acked()) { + gLogger.log(MSG_ERR, "Runtime is not responding. Did it crash?\n"); + } + } + } + + } catch (std::exception& e) { + gLogger.log(MSG_WARN, "REPL Error: %s\n", e.what()); + } + } + + m_listener.disconnect(); +} Compiler::~Compiler() { gLogger.close(); } void Compiler::init_logger() { - gLogger.set_file("compiler.txt"); + gLogger.set_file("compiler.txt"); // todo, a better file than this... gLogger.config[MSG_COLOR].kind = LOG_FILE; gLogger.config[MSG_DEBUG].kind = LOG_IGNORE; gLogger.config[MSG_TGT].color = COLOR_GREEN; @@ -21,4 +75,158 @@ void Compiler::init_logger() { gLogger.config[MSG_WARN].color = COLOR_RED; gLogger.config[MSG_ICE].color = COLOR_RED; gLogger.config[MSG_ERR].color = COLOR_RED; +} + +void Compiler::init_settings() {} + +FileEnv* Compiler::compile_object_file(const std::string& name, + goos::Object code, + bool allow_emit) { + auto file_env = m_global_env->add_file(name); + Env* compilation_env = file_env; + if (!allow_emit) { + compilation_env = file_env->add_no_emit_env(); + } + + file_env->add_top_level_function( + compile_top_level_function("top-level", std::move(code), compilation_env)); + + if (!allow_emit && !file_env->is_empty()) { + throw std::runtime_error("Compilation generated code, but wasn't supposed to"); + } + + return file_env; +} + +std::unique_ptr Compiler::compile_top_level_function(const std::string& name, + const goos::Object& code, + Env* env) { + auto fe = std::make_unique(env, name); + fe->set_segment(TOP_LEVEL_SEGMENT); + + auto result = compile_error_guard(code, fe.get()); + + // only move to return register if we actually got a result + if (!dynamic_cast(result)) { + fe->emit(std::make_unique(fe->make_gpr(result->type()), result->to_gpr(fe.get()))); + } + + fe->finish(); + return fe; +} + +Val* Compiler::compile_error_guard(const goos::Object& code, Env* env) { + try { + return compile(code, env); + } catch (std::runtime_error& e) { + printf( + "------------------------------------------------------------------------------------------" + "-\n"); + auto obj_print = code.print(); + if (obj_print.length() > 80) { + obj_print = obj_print.substr(0, 80); + obj_print += "..."; + } + printf("object: %s\nfrom : %s\n", obj_print.c_str(), + m_goos.reader.db.get_info_for(code).c_str()); + throw e; + } +} + +void Compiler::throw_compile_error(const goos::Object& o, const std::string& err) { + gLogger.log(MSG_ERR, "[Error] Could not compile %s!\nReason: %s\n", o.print().c_str(), + err.c_str()); + throw std::runtime_error(err); +} + +void Compiler::ice(const std::string& error) { + gLogger.log(MSG_ICE, "[ICE] %s\n", error.c_str()); + throw std::runtime_error("ICE"); +} + +void Compiler::color_object_file(FileEnv* env) { + for (auto& f : env->functions()) { + AllocationInput input; + for (auto& i : f->code()) { + input.instructions.push_back(i->to_rai()); + input.debug_instruction_names.push_back(i->print()); + } + + // temp hack + { + for (auto& arg : f->params) { + input.instructions.front().write.push_back(arg.second->ireg()); + } + } + input.max_vars = f->max_vars(); + input.constraints = f->constraints(); + + if (m_settings.debug_print_regalloc) { + input.debug_settings.print_input = true; + input.debug_settings.print_result = true; + input.debug_settings.print_analysis = true; + input.debug_settings.allocate_log_level = 2; + } + + f->set_allocations(allocate_registers(input)); + } +} + +std::vector Compiler::codegen_object_file(FileEnv* env) { + CodeGenerator gen(env); + return gen.run(); +} + +std::vector Compiler::run_test(const std::string& source_code) { + try { + if (!m_listener.is_connected()) { + for (int i = 0; i < 1000; i++) { + m_listener.connect_to_target(); + std::this_thread::sleep_for(std::chrono::microseconds(10000)); + if (m_listener.is_connected()) { + break; + } + } + if (!m_listener.is_connected()) { + throw std::runtime_error("Compiler::run_test couldn't connect!"); + } + } + + auto code = m_goos.reader.read_from_file({source_code}); + auto compiled = compile_object_file("test-code", code, true); + if (compiled->is_empty()) { + return {}; + } + color_object_file(compiled); + auto data = codegen_object_file(compiled); + m_listener.record_messages(ListenerMessageKind::MSG_PRINT); + m_listener.send_code(data); + if (!m_listener.most_recent_send_was_acked()) { + gLogger.log(MSG_ERR, "Runtime is not responding after sending test code. Did it crash?\n"); + } + return m_listener.stop_recording_messages(); + } catch (std::exception& e) { + fmt::print("[Compiler] Failed to compile test program {}: {}\n", source_code, e.what()); + throw e; + } +} + +std::vector Compiler::run_test_no_load(const std::string& source_code) { + auto code = m_goos.reader.read_from_file({source_code}); + compile_object_file("test-code", code, true); + return {}; +} + +void Compiler::shutdown_target() { + if (m_listener.is_connected()) { + m_listener.send_reset(true); + } +} + +void Compiler::typecheck(const goos::Object& form, + const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_message) { + (void)form; + m_ts.typecheck(expected, actual, error_message, true, true); } \ No newline at end of file diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 12781a408d..cebdc0dabe 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -1,18 +1,177 @@ +#pragma once + #ifndef JAK_COMPILER_H #define JAK_COMPILER_H #include "common/type_system/TypeSystem.h" +#include "Env.h" +#include "goalc/listener/Listener.h" +#include "common/goos/Interpreter.h" +#include "goalc/compiler/IR.h" +#include "CompilerSettings.h" + +enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID }; class Compiler { public: Compiler(); ~Compiler(); void execute_repl(); + goos::Interpreter& get_goos() { return m_goos; } + FileEnv* compile_object_file(const std::string& name, goos::Object code, bool allow_emit); + std::unique_ptr compile_top_level_function(const std::string& name, + const goos::Object& code, + Env* env); + Val* compile(const goos::Object& code, Env* env); + Val* compile_error_guard(const goos::Object& code, Env* env); + void throw_compile_error(const goos::Object& o, const std::string& err); + void ice(const std::string& err); + None* get_none() { return m_none.get(); } + + std::vector run_test(const std::string& source_code); + std::vector run_test_no_load(const std::string& source_code); + void shutdown_target(); private: void init_logger(); + void init_settings(); + bool try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest); + Val* compile_goos_macro(const goos::Object& o, + const goos::Object& macro_obj, + const goos::Object& rest, + Env* env); + Val* compile_pair(const goos::Object& code, Env* env); + Val* compile_integer(const goos::Object& code, Env* env); + Val* compile_integer(s64 value, Env* env); + Val* compile_float(const goos::Object& code, Env* env); + Val* compile_float(float value, Env* env, int seg); + Val* compile_symbol(const goos::Object& form, Env* env); + Val* compile_string(const goos::Object& form, Env* env); + Val* compile_string(const std::string& str, Env* env, int seg = MAIN_SEGMENT); + Val* compile_get_symbol_value(const std::string& name, Env* env); + Val* compile_function_or_method_call(const goos::Object& form, Env* env); + SymbolVal* compile_get_sym_obj(const std::string& name, Env* env); + void color_object_file(FileEnv* env); + std::vector codegen_object_file(FileEnv* env); + + void for_each_in_list(const goos::Object& list, + const std::function& f); + + goos::Arguments get_va(const goos::Object& form, const goos::Object& rest); + void va_check( + const goos::Object& form, + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& named); + std::string as_string(const goos::Object& o); + std::string symbol_string(const goos::Object& o); + const goos::Object& pair_car(const goos::Object& o); + const goos::Object& pair_cdr(const goos::Object& o); + void expect_empty_list(const goos::Object& o); + void typecheck(const goos::Object& form, + const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_message = ""); + + TypeSpec parse_typespec(const goos::Object& src); + bool is_local_symbol(const goos::Object& obj, Env* env); + emitter::RegKind get_preferred_reg_kind(const TypeSpec& ts); + Val* compile_real_function_call(const goos::Object& form, + RegVal* function, + const std::vector& args, + Env* env); TypeSystem m_ts; + std::unique_ptr m_global_env = nullptr; + std::unique_ptr m_none = nullptr; + bool m_want_exit = false; + listener::Listener m_listener; + goos::Interpreter m_goos; + std::unordered_map m_symbol_types; + std::unordered_map, goos::Object> m_global_constants; + std::unordered_map, LambdaVal*> m_inlineable_functions; + CompilerSettings m_settings; + MathMode get_math_mode(const TypeSpec& ts); + bool is_number(const TypeSpec& ts); + bool is_float(const TypeSpec& ts); + bool is_integer(const TypeSpec& ts); + bool is_binteger(const TypeSpec& ts); + bool is_singed_integer_or_binteger(const TypeSpec& ts); + Val* number_to_integer(Val* in, Env* env); + Val* number_to_float(Val* in, Env* env); + Val* number_to_binteger(Val* in, Env* env); + Val* to_math_type(Val* in, MathMode mode, Env* env); + bool is_none(Val* in); + + Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind); + + RegVal* compile_get_method_of_type(const TypeSpec& type, + const std::string& method_name, + Env* env); + + public: + // Atoms + + // Block + Val* compile_begin(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_block(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_return_from(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_label(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_goto(const goos::Object& form, const goos::Object& rest, Env* env); + + // CompilerControl + Val* compile_seval(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_exit(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_poke(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_gs(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_set_config(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env); + + // ControlFlow + Condition compile_condition(const goos::Object& condition, Env* env, bool invert); + Val* compile_condition_as_bool(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_when_goto(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_cond(const goos::Object& form, const goos::Object& rest, Env* env); + + // Define + Val* compile_define(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_define_extern(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_set(const goos::Object& form, const goos::Object& rest, Env* env); + + // Macro + Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_defglobalconstant(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env); + + // Math + Val* compile_add(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_sub(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_mul(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_div(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_shlv(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_sarv(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_shrv(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_mod(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_logxor(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_lognot(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_logand(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_logior(const goos::Object& form, const goos::Object& rest, Env* env); + + // Function + Val* compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_inline(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_declare(const goos::Object& form, const goos::Object& rest, Env* env); + + // Type + Val* compile_deftype(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_defmethod(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_deref(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/CompilerSettings.cpp b/goalc/compiler/CompilerSettings.cpp new file mode 100644 index 0000000000..eec56ac010 --- /dev/null +++ b/goalc/compiler/CompilerSettings.cpp @@ -0,0 +1,24 @@ +#include "CompilerSettings.h" + +CompilerSettings::CompilerSettings() { + m_settings["print-ir"].kind = SettingKind::BOOL; + m_settings["print-ir"].boolp = &debug_print_ir; + + m_settings["print-regalloc"].kind = SettingKind::BOOL; + m_settings["print-regalloc"].boolp = &debug_print_regalloc; + + m_settings["disable-math-const-prop"].kind = SettingKind::BOOL; + m_settings["disable-math-const-prop"].boolp = &disable_math_const_prop; +} + +void CompilerSettings::set(const std::string& name, const goos::Object& value) { + auto kv = m_settings.find(name); + if (kv == m_settings.end()) { + throw std::runtime_error("Compiler setting \"" + name + "\" was not recognized"); + } + + kv->second.value = value; + if (kv->second.boolp) { + *kv->second.boolp = !(value.is_symbol() && value.as_symbol()->name == "#f"); + } +} \ No newline at end of file diff --git a/goalc/compiler/CompilerSettings.h b/goalc/compiler/CompilerSettings.h new file mode 100644 index 0000000000..caecf4e179 --- /dev/null +++ b/goalc/compiler/CompilerSettings.h @@ -0,0 +1,32 @@ +#pragma once + +#ifndef JAK_COMPILERSETTINGS_H +#define JAK_COMPILERSETTINGS_H + +#include +#include +#include "common/goos/Object.h" + +class CompilerSettings { + public: + CompilerSettings(); + bool debug_print_ir = false; + bool debug_print_regalloc = false; + bool disable_math_const_prop = false; + bool emit_move_after_return = true; + + void set(const std::string& name, const goos::Object& value); + + private: + enum class SettingKind { BOOL, INVALID }; + + struct SettingsEntry { + SettingKind kind = SettingKind::INVALID; + goos::Object value; + bool* boolp = nullptr; + }; + + std::unordered_map m_settings; +}; + +#endif // JAK_COMPILERSETTINGS_H diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 785a75a876..1e2b5ebd0a 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -1,3 +1,282 @@ - - +#include #include "Env.h" +#include "IR.h" + +/////////////////// +// Env +/////////////////// + +/*! + * Emit IR into the function currently being compiled. + */ +void Env::emit(std::unique_ptr ir) { + // by default, we don't know how, so pass it up and hope for the best. + m_parent->emit(std::move(ir)); +} + +/*! + * Allocate an IRegister with the given type. + */ +RegVal* Env::make_ireg(TypeSpec ts, emitter::RegKind kind) { + return m_parent->make_ireg(std::move(ts), kind); +} + +/*! + * Apply a register constraint to the current function. + */ +void Env::constrain_reg(IRegConstraint constraint) { + m_parent->constrain_reg(std::move(constraint)); +} + +/*! + * Lookup the given symbol object as a lexical variable. + */ +RegVal* Env::lexical_lookup(goos::Object sym) { + return m_parent->lexical_lookup(std::move(sym)); +} + +BlockEnv* Env::find_block(const std::string& name) { + return m_parent->find_block(name); +} + +RegVal* Env::make_gpr(TypeSpec ts) { + return make_ireg(std::move(ts), emitter::RegKind::GPR); +} + +RegVal* Env::make_xmm(TypeSpec ts) { + return make_ireg(std::move(ts), emitter::RegKind::XMM); +} + +std::unordered_map& Env::get_label_map() { + return parent()->get_label_map(); +} + +/////////////////// +// GlobalEnv +/////////////////// + +// Because this is the top of the environment chain, all these end the parent calls and provide +// errors, or return that the items were not found. + +GlobalEnv::GlobalEnv() : Env(nullptr) {} + +std::string GlobalEnv::print() { + return "global-env"; +} + +/*! + * Emit IR into the function currently being compiled. + */ +void GlobalEnv::emit(std::unique_ptr ir) { + // by default, we don't know how, so pass it up and hope for the best. + (void)ir; + throw std::runtime_error("cannot emit to GlobalEnv"); +} + +/*! + * Allocate an IRegister with the given type. + */ +RegVal* GlobalEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { + (void)ts; + (void)kind; + throw std::runtime_error("cannot alloc reg in GlobalEnv"); +} + +/*! + * Apply a register constraint to the current function. + */ +void GlobalEnv::constrain_reg(IRegConstraint constraint) { + (void)constraint; + throw std::runtime_error("cannot constrain reg in GlobalEnv"); +} + +/*! + * Lookup the given symbol object as a lexical variable. + */ +RegVal* GlobalEnv::lexical_lookup(goos::Object sym) { + (void)sym; + return nullptr; +} + +BlockEnv* GlobalEnv::find_block(const std::string& name) { + (void)name; + return nullptr; +} + +FileEnv* GlobalEnv::add_file(std::string name) { + m_files.push_back(std::make_unique(this, std::move(name))); + return m_files.back().get(); +} + +/////////////////// +// NoEmitEnv +/////////////////// + +/*! + * Get the name of a NoEmitEnv + */ +std::string NoEmitEnv::print() { + return "no-emit-env"; +} + +/*! + * Emit - which is invalid - into a NoEmitEnv and throw an exception. + */ +void NoEmitEnv::emit(std::unique_ptr ir) { + (void)ir; + throw std::runtime_error("emit into a no-emit env!"); +} + +/////////////////// +// BlockEnv +/////////////////// + +BlockEnv::BlockEnv(Env* parent, std::string _name) : Env(parent), name(std::move(_name)) {} + +std::string BlockEnv::print() { + return "block-" + name; +} + +BlockEnv* BlockEnv::find_block(const std::string& block) { + if (name == block) { + return this; + } else { + return parent()->find_block(block); + } +} + +/////////////////// +// FileEnv +/////////////////// + +FileEnv::FileEnv(Env* parent, std::string name) : Env(parent), m_name(std::move(name)) {} + +std::string FileEnv::print() { + return "file-" + m_name; +} + +void FileEnv::add_function(std::unique_ptr fe) { + assert(fe->idx_in_file == -1); + fe->idx_in_file = m_functions.size(); + m_functions.push_back(std::move(fe)); +} + +void FileEnv::add_static(std::unique_ptr s) { + m_statics.push_back(std::move(s)); +} + +void FileEnv::add_top_level_function(std::unique_ptr fe) { + // todo, set FE as top level segment + m_functions.push_back(std::move(fe)); + m_top_level_func = m_functions.back().get(); +} + +NoEmitEnv* FileEnv::add_no_emit_env() { + assert(!m_no_emit_env); + m_no_emit_env = std::make_unique(this); + return m_no_emit_env.get(); +} + +void FileEnv::debug_print_tl() { + if (m_top_level_func) { + for (auto& code : m_top_level_func->code()) { + fmt::print("{}\n", code->print()); + } + } else { + printf("no top level function.\n"); + } +} + +bool FileEnv::is_empty() { + return m_functions.size() == 1 && m_functions.front().get() == m_top_level_func && + m_top_level_func->code().empty(); +} +/////////////////// +// FunctionEnv +/////////////////// + +FunctionEnv::FunctionEnv(Env* parent, std::string name) + : DeclareEnv(parent), m_name(std::move(name)) {} + +std::string FunctionEnv::print() { + return "function-" + m_name; +} + +void FunctionEnv::emit(std::unique_ptr ir) { + ir->add_constraints(&m_constraints, m_code.size()); + m_code.push_back(std::move(ir)); +} +void FunctionEnv::finish() { + resolve_gotos(); +} + +void FunctionEnv::resolve_gotos() { + for (auto& gt : unresolved_gotos) { + auto kv_label = m_labels.find(gt.label); + if (kv_label == m_labels.end()) { + throw std::runtime_error("Invalid goto " + gt.label); + } + gt.ir->resolve(&kv_label->second); + } + + for (auto& gt : unresolved_cond_gotos) { + auto kv_label = m_labels.find(gt.label); + if (kv_label == m_labels.end()) { + throw std::runtime_error("invalid when-goto destination " + gt.label); + } + gt.ir->label = kv_label->second; + gt.ir->mark_as_resolved(); + } +} + +RegVal* FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { + IRegister ireg; + ireg.kind = kind; + ireg.id = m_iregs.size(); + auto rv = std::make_unique(ireg, ts); + m_iregs.push_back(std::move(rv)); + assert(kind != emitter::RegKind::INVALID); + return m_iregs.back().get(); +} + +std::unordered_map& FunctionEnv::get_label_map() { + return m_labels; +} + +std::unordered_map& LabelEnv::get_label_map() { + return m_labels; +} + +RegVal* FunctionEnv::lexical_lookup(goos::Object sym) { + if (!sym.is_symbol()) { + throw std::runtime_error("invalid symbol in lexical_lookup"); + } + + auto kv = params.find(sym.as_symbol()->name); + if (kv == params.end()) { + return parent()->lexical_lookup(sym); + } + + return kv->second; +} + +/////////////////// +// LexicalEnv +/////////////////// + +std::string LexicalEnv::print() { + return "lexical"; +} + +RegVal* LexicalEnv::lexical_lookup(goos::Object sym) { + if (!sym.is_symbol()) { + throw std::runtime_error("invalid symbol in lexical_lookup"); + } + + auto kv = vars.find(sym.as_symbol()->name); + if (kv == vars.end()) { + return parent()->lexical_lookup(sym); + } + + return kv->second; +} diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index e7c85e61d9..a1f5a822ec 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -1,14 +1,268 @@ +#pragma once +/*! + * @file Env.h + * The Env tree. The stores all of the nested scopes/contexts during compilation and also + * manages the memory for stuff generated during compiling. + */ #ifndef JAK_ENV_H #define JAK_ENV_H -class Env {}; +#include +#include +#include +#include "common/type_system/TypeSpec.h" +#include "goalc/regalloc/allocate.h" +#include "common/goos/Object.h" +#include "StaticObject.h" +#include "Label.h" +#include "Val.h" -// global -// noemit -// objectfile -// configuration +class FileEnv; +class BlockEnv; +class IR; + +/*! + * Parent class for Env's + */ +class Env { + public: + explicit Env(Env* parent) : m_parent(parent) {} + virtual std::string print() = 0; + virtual void emit(std::unique_ptr ir); + virtual RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind); + virtual void constrain_reg(IRegConstraint constraint); // todo, remove! + virtual RegVal* lexical_lookup(goos::Object sym); + virtual BlockEnv* find_block(const std::string& name); + virtual std::unordered_map& get_label_map(); + RegVal* make_gpr(TypeSpec ts); + RegVal* make_xmm(TypeSpec ts); + virtual ~Env() = default; + + Env* parent() { return m_parent; } + + protected: + Env* m_parent = nullptr; +}; + +/*! + * The top-level Env. Holds FileEnvs for all files. + */ +class GlobalEnv : public Env { + public: + GlobalEnv(); + std::string print() override; + void emit(std::unique_ptr ir) override; + RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override; + void constrain_reg(IRegConstraint constraint) override; + RegVal* lexical_lookup(goos::Object sym) override; + BlockEnv* find_block(const std::string& name) override; + ~GlobalEnv() = default; + + FileEnv* add_file(std::string name); + + private: + std::vector> m_files; +}; + +/*! + * An Env that doesn't allow emitting to go past it. Used to make sure source code that shouldn't + * generate machine code actually does this. + */ +class NoEmitEnv : public Env { + public: + explicit NoEmitEnv(Env* parent) : Env(parent) {} + std::string print() override; + void emit(std::unique_ptr ir) override; + ~NoEmitEnv() = default; +}; + +/*! + * An Env for an entire file (or input to the REPL) + */ +class FileEnv : public Env { + public: + FileEnv(Env* parent, std::string name); + std::string print() override; + void add_function(std::unique_ptr fe); + void add_top_level_function(std::unique_ptr fe); + void add_static(std::unique_ptr s); + NoEmitEnv* add_no_emit_env(); + void debug_print_tl(); + const std::vector>& functions() { return m_functions; } + const std::vector>& statics() { return m_statics; } + const FunctionEnv& top_level_function() { + assert(m_top_level_func); + return *m_top_level_func; + } + + bool is_empty(); + ~FileEnv() = default; + + protected: + std::string m_name; + std::vector> m_functions; + std::vector> m_statics; + std::unique_ptr m_no_emit_env = nullptr; + + // statics + FunctionEnv* m_top_level_func = nullptr; +}; + +/*! + * An Env which manages the scope for (declare ...) statements. + */ +class DeclareEnv : public Env { + public: + explicit DeclareEnv(Env* parent) : Env(parent) {} + virtual std::string print() = 0; + ~DeclareEnv() = default; + + struct Settings { + bool is_set = false; // has the user set these with a (declare)? + bool inline_by_default = false; // if a function, inline when possible? + bool save_code = true; // if a function, should we save the code? + bool allow_inline = false; // should we allow the user to use this an inline function + } settings; +}; + +class IR_GotoLabel; + +struct UnresolvedGoto { + IR_GotoLabel* ir = nullptr; + std::string label; +}; + +class IR_ConditionalBranch; + +struct UnresolvedConditionalGoto { + IR_ConditionalBranch* ir = nullptr; + std::string label; +}; + +class FunctionEnv : public DeclareEnv { + public: + FunctionEnv(Env* parent, std::string name); + std::string print() override; + std::unordered_map& get_label_map() override; + void set_segment(int seg) { segment = seg; } + void emit(std::unique_ptr ir) override; + void finish(); + RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override; + const std::vector>& code() const { return m_code; } + int max_vars() const { return m_iregs.size(); } + const std::vector& constraints() { return m_constraints; } + void constrain(const IRegConstraint& c) { m_constraints.push_back(c); } + void set_allocations(const AllocationResult& result) { m_regalloc_result = result; } + RegVal* lexical_lookup(goos::Object sym) override; + + const AllocationResult& alloc_result() { return m_regalloc_result; } + + bool needs_aligned_stack() const { return m_aligned_stack_required; } + void require_aligned_stack() { m_aligned_stack_required = true; } + + Label* alloc_unnamed_label() { + m_unnamed_labels.emplace_back(std::make_unique