diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ab9843521..0e5b212f40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,13 @@ set(CMAKE_CXX_STANDARD 17) # 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 - "${CMAKE_CXX_FLAGS} \ + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ -Wall \ -Winit-self \ + -ggdb \ -Wextra \ -Wcast-align \ -Wcast-qual \ @@ -22,16 +23,16 @@ 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") +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/) @@ -79,5 +80,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/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index a7f2827070..f4c89df7e8 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 } diff --git a/common/listener_common.h b/common/listener_common.h index f18c41b4a5..a589919ba6 100644 --- a/common/listener_common.h +++ b/common/listener_common.h @@ -33,7 +33,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 +47,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/util/BinaryWriter.h b/common/util/BinaryWriter.h new file mode 100644 index 0000000000..8140e1ac59 --- /dev/null +++ b/common/util/BinaryWriter.h @@ -0,0 +1,74 @@ +#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/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/kernel/asm_funcs.asm b/game/kernel/asm_funcs.asm index 2b41efe59d..c43173753a 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 r14, [rsp + 144] ;; symbol table + mov r15, [rsp + 152] ;; offset call r13 diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 1cd2cc1a13..1b54215ada 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,28 @@ 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) { + auto cptr = Ptr(ListenerFunction->value).c(); + for (int i = 0; i < 40; i++) { + printf("%x ", cptr[i]); + } + printf("\n"); + auto result = + call_goal(Ptr(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem); +#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..3ac75533e8 100644 --- a/game/kernel/kboot.h +++ b/game/kernel/kboot.h @@ -73,4 +73,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/klisten.cpp b/game/kernel/klisten.cpp index 4f1d733bbe..322d049e33 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"); 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/kscheme.cpp b/game/kernel/kscheme.cpp index d2c41a96cb..816918ff0f 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -958,7 +958,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); @@ -1828,25 +1830,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/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/fake_iso.cpp b/game/overlord/fake_iso.cpp index 97f85fa18b..45603a03c6 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -97,10 +97,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 +140,7 @@ int FS_Init(u8* buffer) { ptr++; i++; } + e->file_path[i] = 0; fake_iso_entry_count++; } @@ -194,9 +198,7 @@ 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__ strcat(path_buffer, "/"); -#endif strcat(path_buffer, fake_iso_entries[fr->location].file_path); return path_buffer; } diff --git a/game/runtime.cpp b/game/runtime.cpp index f851e11a82..160ca28cbc 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -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..f2121d2c4b 100644 --- a/game/runtime.h +++ b/game/runtime.h @@ -9,6 +9,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/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 4d7f466aab..ca7f1b0f63 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -263,6 +263,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/goal_src/goal-lib.gc b/goal_src/goal-lib.gc new file mode 100644 index 0000000000..09f71dab94 --- /dev/null +++ b/goal_src/goal-lib.gc @@ -0,0 +1,53 @@ +;; 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) + ) + +(defmacro e () + `(:exit) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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) + ) + ) \ 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-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-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-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-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/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 5365a1f48c..2e8ab64256 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -13,6 +13,12 @@ add_library(compiler compiler/Val.cpp compiler/IR.cpp compiler/CodeGenerator.cpp + compiler/compilation/Atoms.cpp + compiler/compilation/CompilerControl.cpp + compiler/compilation/Block.cpp + compiler/compilation/Macro.cpp + compiler/compilation/Define.cpp + compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp regalloc/Allocator.cpp @@ -22,9 +28,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 util goos type_system listener mman) -target_link_libraries(goalc util goos compiler) \ No newline at end of file +ELSE () + target_link_libraries(compiler util goos type_system listener) +ENDIF () + +target_link_libraries(goalc util goos compiler type_system) diff --git a/goalc/compiler/CodeGenerator.cpp b/goalc/compiler/CodeGenerator.cpp index 2f78d6224d..f196de9d40 100644 --- a/goalc/compiler/CodeGenerator.cpp +++ b/goalc/compiler/CodeGenerator.cpp @@ -1,3 +1,117 @@ - - #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() { + // todo, static objects + + for (auto& f : m_fe->functions()) { + do_function(f.get()); + } + + return m_gen.generate_data_v3().to_vector(); +} + +void CodeGenerator::do_function(FunctionEnv* env) { + 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) { + assert(false); + } + } + ir->do_codegen(&m_gen, allocs, i_rec); + for (auto& op : bonus.ops) { + if (op.store) { + 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..cbd1f4ee25 100644 --- a/goalc/compiler/CodeGenerator.h +++ b/goalc/compiler/CodeGenerator.h @@ -3,6 +3,18 @@ #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); + 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..1edede33bd 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -1,19 +1,70 @@ #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(); 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()); + 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 +72,138 @@ 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; +} + +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()); + } + input.max_vars = f->max_vars(); + input.constraints = f->constraints(); + + // for now... + input.debug_settings.print_input = true; + input.debug_settings.print_result = true; + input.debug_settings.print_analysis = true; + + 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); + 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()); + 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..215ee61f0b 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -2,17 +2,102 @@ #define JAK_COMPILER_H #include "common/type_system/TypeSystem.h" +#include "Env.h" +#include "goalc/listener/Listener.h" +#include "goalc/goos/Interpreter.h" +#include "goalc/compiler/IR.h" class Compiler { public: Compiler(); ~Compiler(); void execute_repl(); + 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); + void shutdown_target(); private: void init_logger(); + 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_symbol(const goos::Object& form, Env* env); + Val* compile_get_symbol_value(const std::string& name, 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); 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; + + void typecheck(const goos::Object& form, + const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_message = ""); + + 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); + + // Define + Val* compile_define(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); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 785a75a876..0114dca609 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -1,3 +1,232 @@ - - +#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. + */ +Val* 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. + */ +Val* 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) { + m_functions.push_back(std::move(fe)); +} + +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); + } +} + +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)); + 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; +} \ No newline at end of file diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index e7c85e61d9..9837ac15f6 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -1,14 +1,227 @@ - +/*! + * @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 "goalc/goos/Object.h" +//#include "IR.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); + virtual Val* 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; + Val* 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); + NoEmitEnv* add_no_emit_env(); + void debug_print_tl(); + const std::vector>& functions() { return m_functions; } + + bool is_empty(); + ~FileEnv() = default; + + protected: + std::string m_name; + std::vector> m_functions; + 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; + 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() { return m_code; } + int max_vars() const { return m_iregs.size(); } + const std::vector& constraints() { return m_constraints; } + void set_allocations(const AllocationResult& result) { m_regalloc_result = result; } + + const AllocationResult& alloc_result() { return m_regalloc_result; } + + bool needs_aligned_stack() const { return m_aligned_stack_required; } + + template + T* alloc_val(Args&&... args) { + std::unique_ptr new_obj = std::make_unique(std::forward(args)...); + m_vals.push_back(std::move(new_obj)); + return (T*)m_vals.back().get(); + } + + template + T* alloc_env(Args&&... args) { + std::unique_ptr new_obj = std::make_unique(std::forward(args)...); + m_envs.push_back(std::move(new_obj)); + return (T*)m_envs.back().get(); + } + + int segment = -1; + std::string method_of_type_name = "#f"; + + std::vector unresolved_gotos; + + protected: + void resolve_gotos(); + std::string m_name; + std::vector> m_code; + std::vector> m_iregs; + std::vector> m_vals; + std::vector> m_envs; + std::vector m_constraints; + // todo, unresolved gotos + AllocationResult m_regalloc_result; + bool m_is_asm_func = false; + + bool m_aligned_stack_required = false; + + std::unordered_map m_params; + std::unordered_map m_labels; +}; + +class BlockEnv : public Env { + public: + BlockEnv(Env* parent, std::string name); + std::string print() override; + BlockEnv* find_block(const std::string& name) override; + + std::string name; + Label end_label = nullptr; + RegVal* return_value = nullptr; + std::vector return_types; +}; + +class LexicalEnv : public Env { + public: + LexicalEnv(Env* parent); + std::string print() override; +}; + +class LabelEnv : public Env { + public: + std::unordered_map& get_label_map() override; + + protected: + std::unordered_map m_labels; +}; + +class WithInlineEnv : public Env {}; + +class SymbolMacroEnv : public Env {}; + +template +T* get_parent_env_of_type(Env* in) { + for (;;) { + auto attempt = dynamic_cast(in); + if (attempt) + return attempt; + if (dynamic_cast(in)) { + return nullptr; + } + in = in->parent(); + } +} // function // block // lexical diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 479292ef2e..236662ba1e 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -1,3 +1,242 @@ - - #include "IR.h" + +#include +#include "goalc/emitter/IGen.h" + +using namespace emitter; + +namespace { +Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_Record irec) { + auto& ass = allocs.ass_as_ranges.at(rv->ireg().id).get(irec.ir_id); + assert(ass.kind == Assignment::Kind::REGISTER); + return ass.reg; +} +} // namespace + +/////////// +// Return +/////////// +IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value) + : m_return_reg(return_reg), m_value(value) {} +std::string IR_Return::print() { + return fmt::format("ret {} {}", m_return_reg->print(), m_value->print()); +} + +RegAllocInstr IR_Return::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_return_reg->ireg()); + rai.read.push_back(m_value->ireg()); + if (m_value->ireg().kind == m_return_reg->ireg().kind) { + rai.is_move = true; // only true if we aren't moving from register kind to register kind + } + return rai; +} + +void IR_Return::add_constraints(std::vector* constraints, int my_id) { + IRegConstraint c; + if (dynamic_cast(m_return_reg)) { + return; + } + + c.ireg = m_return_reg->ireg(); + c.instr_idx = my_id; + c.desired_register = emitter::RAX; + constraints->push_back(c); +} + +void IR_Return::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto val_reg = get_reg(m_value, allocs, irec); + auto dest_reg = get_reg(m_return_reg, allocs, irec); + + if (val_reg == dest_reg) { + gen->add_instr(IGen::null(), irec); + } else { + gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); + } +} + +///////////////////// +// LoadConstant64 +///////////////////// +IR_LoadConstant64::IR_LoadConstant64(const RegVal* dest, u64 value) + : m_dest(dest), m_value(value) {} + +std::string IR_LoadConstant64::print() { + return fmt::format("mov-ic {}, {}", m_dest->print(), m_value); +} + +RegAllocInstr IR_LoadConstant64::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + return rai; +} + +void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto dest_reg = get_reg(m_dest, allocs, irec); + gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec); +} + +///////////////////// +// LoadSymbolPointer +///////////////////// +IR_LoadSymbolPointer::IR_LoadSymbolPointer(const RegVal* dest, std::string name) + : m_dest(dest), m_name(std::move(name)) {} + +std::string IR_LoadSymbolPointer::print() { + return fmt::format("mov-symptr {}, '{}", m_dest->print(), m_name); +} + +RegAllocInstr IR_LoadSymbolPointer::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + return rai; +} + +void IR_LoadSymbolPointer::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto dest_reg = get_reg(m_dest, allocs, irec); + // todo, could be single lea opcode + gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, gRegInfo.get_st_reg()), irec); + auto add = gen->add_instr(IGen::add_gpr64_imm32s(dest_reg, 0x0afecafe), irec); + gen->link_instruction_symbol_ptr(add, m_name); +} + +///////////////////// +// SetSymbolValue +///////////////////// + +IR_SetSymbolValue::IR_SetSymbolValue(const SymbolVal* dest, const RegVal* src) + : m_dest(dest), m_src(src) {} + +std::string IR_SetSymbolValue::print() { + return fmt::format("mov '{}, {}", m_dest->name(), m_src->print()); +} + +RegAllocInstr IR_SetSymbolValue::to_rai() { + RegAllocInstr rai; + rai.read.push_back(m_src->ireg()); + return rai; +} + +void IR_SetSymbolValue::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto src_reg = get_reg(m_src, allocs, irec); + auto instr = + gen->add_instr(IGen::store32_gpr64_gpr64_plus_gpr64_plus_s32( + gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), src_reg, 0x0badbeef), + irec); + gen->link_instruction_symbol_mem(instr, m_dest->name()); +} + +///////////////////// +// GetSymbolValue +///////////////////// + +IR_GetSymbolValue::IR_GetSymbolValue(const RegVal* dest, const SymbolVal* src, bool sext) + : m_dest(dest), m_src(src), m_sext(sext) {} + +std::string IR_GetSymbolValue::print() { + return fmt::format("mov {}, '{}", m_dest->print(), m_src->name()); +} + +RegAllocInstr IR_GetSymbolValue::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + return rai; +} + +void IR_GetSymbolValue::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto dst_reg = get_reg(m_dest, allocs, irec); + if (m_sext) { + auto instr = + gen->add_instr(IGen::load32s_gpr64_gpr64_plus_gpr64_plus_s32( + dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), 0x0badbeef), + irec); + gen->link_instruction_symbol_mem(instr, m_src->name()); + } else { + 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); + gen->link_instruction_symbol_mem(instr, m_src->name()); + } +} + +///////////////////// +// RegSet +///////////////////// + +IR_RegSet::IR_RegSet(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} + +RegAllocInstr IR_RegSet::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + rai.read.push_back(m_src->ireg()); + if (m_dest->ireg().kind == m_src->ireg().kind) { + rai.is_move = true; // only true if we aren't moving from register kind to register kind + } + return rai; +} + +void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto val_reg = get_reg(m_src, allocs, irec); + auto dest_reg = get_reg(m_dest, allocs, irec); + + if (val_reg == dest_reg) { + gen->add_instr(IGen::null(), irec); + } else { + gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); + } +} + +std::string IR_RegSet::print() { + return fmt::format("mov {}, {}", m_dest->print(), m_src->print()); +} + +///////////////////// +// GotoLabel +///////////////////// + +IR_GotoLabel::IR_GotoLabel(const Label* dest) : m_dest(dest) { + m_resolved = true; +} + +IR_GotoLabel::IR_GotoLabel() { + m_resolved = false; +} + +std::string IR_GotoLabel::print() { + return fmt::format("goto {}", m_dest->print()); +} + +RegAllocInstr IR_GotoLabel::to_rai() { + assert(m_resolved); + RegAllocInstr rai; + rai.jumps.push_back(m_dest->idx); + rai.fallthrough = false; + return rai; +} + +void IR_GotoLabel::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + (void)allocs; + auto instr = gen->add_instr(IGen::jmp_32(), irec); + gen->link_instruction_jump(instr, gen->get_future_ir_record_in_same_func(irec, m_dest->idx)); +} + +void IR_GotoLabel::resolve(const Label* dest) { + assert(!m_resolved); + m_dest = dest; + m_resolved = true; +} \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index f379466466..65e3747426 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -4,18 +4,129 @@ #include #include "CodeGenerator.h" #include "goalc/regalloc/allocate.h" +#include "Val.h" +#include "goalc/emitter/ObjectGenerator.h" class IR { public: virtual std::string print() = 0; virtual RegAllocInstr to_rai() = 0; - virtual void do_codegen(CodeGenerator* gen) = 0; + virtual void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) = 0; + virtual void add_constraints(std::vector* constraints, int my_id) { + (void)constraints; + (void)my_id; + } }; -class IR_Set : public IR { +// class IR_Set : public IR { +// public: +// std::string print() override; +// RegAllocInstr to_rai() override; +//}; + +class IR_Return : public IR { public: + IR_Return(const RegVal* return_reg, const RegVal* value); std::string print() override; RegAllocInstr to_rai() override; + void add_constraints(std::vector* constraints, int my_id) override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + const RegVal* value() { return m_value; } + + protected: + const RegVal* m_return_reg = nullptr; + const RegVal* m_value = nullptr; +}; + +class IR_LoadConstant64 : public IR { + public: + IR_LoadConstant64(const RegVal* dest, u64 value); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + u64 m_value = 0; +}; + +class IR_LoadSymbolPointer : public IR { + public: + IR_LoadSymbolPointer(const RegVal* dest, std::string name); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + std::string m_name; +}; + +class IR_SetSymbolValue : public IR { + public: + IR_SetSymbolValue(const SymbolVal* dest, const RegVal* src); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const SymbolVal* m_dest = nullptr; + const RegVal* m_src = nullptr; +}; + +class IR_GetSymbolValue : public IR { + public: + IR_GetSymbolValue(const RegVal* dest, const SymbolVal* src, bool sext); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + const SymbolVal* m_src = nullptr; + bool m_sext = false; +}; + +class IR_RegSet : public IR { + public: + IR_RegSet(const RegVal* dest, const RegVal* src); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + const RegVal* m_src = nullptr; +}; + +class IR_GotoLabel : public IR { + public: + IR_GotoLabel(); + void resolve(const Label* dest); + explicit IR_GotoLabel(const Label* dest); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const Label* m_dest = nullptr; + bool m_resolved = false; }; #endif // JAK_IR_H diff --git a/goalc/compiler/Label.h b/goalc/compiler/Label.h new file mode 100644 index 0000000000..61336994ab --- /dev/null +++ b/goalc/compiler/Label.h @@ -0,0 +1,13 @@ +#ifndef JAK_LABEL_H +#define JAK_LABEL_H + +class FunctionEnv; +struct Label { + Label() = default; + Label(FunctionEnv* f, int _idx = -1) : func(f), idx(_idx) {} + FunctionEnv* func = nullptr; + int idx = -1; + std::string print() const { return "label-" + std::to_string(idx); } +}; + +#endif // JAK_LABEL_H diff --git a/goalc/compiler/Lambda.h b/goalc/compiler/Lambda.h new file mode 100644 index 0000000000..21a92ba820 --- /dev/null +++ b/goalc/compiler/Lambda.h @@ -0,0 +1,10 @@ + + +#ifndef JAK_LAMBDA_H +#define JAK_LAMBDA_H + +struct Lambda { + std::string debug_name; +}; + +#endif // JAK_LAMBDA_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp new file mode 100644 index 0000000000..c891c5be29 --- /dev/null +++ b/goalc/compiler/Util.cpp @@ -0,0 +1,120 @@ +#include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" + +goos::Arguments Compiler::get_va(const goos::Object& form, const goos::Object& rest) { + goos::Arguments args; + // loop over forms in list + goos::Object current = rest; + while (!current.is_empty_list()) { + auto arg = current.as_pair()->car; + + // did we get a ":keyword" + if (arg.is_symbol() && arg.as_symbol()->name.at(0) == ':') { + auto key_name = arg.as_symbol()->name.substr(1); + + // check for multiple definition of key + if (args.named.find(key_name) != args.named.end()) { + throw_compile_error(form, "Key argument " + key_name + " multiply defined"); + } + + // check for well-formed :key value expression + current = current.as_pair()->cdr; + if (current.is_empty_list()) { + throw_compile_error(form, "Key argument didn't have a value"); + } + + args.named[key_name] = current.as_pair()->car; + } else { + // not a keyword. Add to unnamed or rest, depending on what we expect + args.unnamed.push_back(arg); + } + current = current.as_pair()->cdr; + } + + return args; +} + +void Compiler::va_check( + const goos::Object& form, + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& + named) { + assert(args.rest.empty()); + if (unnamed.size() != args.unnamed.size()) { + throw_compile_error(form, "Got " + std::to_string(args.unnamed.size()) + + " arguments, but expected " + std::to_string(unnamed.size())); + } + + for (size_t i = 0; i < unnamed.size(); i++) { + if (unnamed[i] != args.unnamed[i].type) { + assert(!unnamed[i].is_wildcard); + throw_compile_error(form, "Argument " + std::to_string(i) + " has type " + + object_type_to_string(args.unnamed[i].type) + " but " + + object_type_to_string(unnamed[i].value) + " was expected"); + } + } + + for (const auto& kv : named) { + auto kv2 = args.named.find(kv.first); + if (kv2 == args.named.end()) { + // argument not given. + if (kv.second.first) { + // but was required + throw_compile_error(form, "Required named argument \"" + kv.first + "\" was not found"); + } + } else { + // argument given. + if (kv.second.second != kv2->second.type) { + // but is wrong type + assert(!kv.second.second.is_wildcard); + throw_compile_error(form, "Argument \"" + kv.first + "\" has type " + + object_type_to_string(kv2->second.type) + " but " + + object_type_to_string(kv.second.second.value) + + " was expected"); + } + } + } + + for (const auto& kv : args.named) { + if (named.find(kv.first) == named.end()) { + throw_compile_error(form, "Got unrecognized keyword argument \"" + kv.first + "\""); + } + } +} + +void Compiler::for_each_in_list(const goos::Object& list, + const std::function& 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_compile_error(list, "invalid list in for_each_in_list"); + } +} + +std::string Compiler::as_string(const goos::Object& o) { + return o.as_string()->data; +} + +std::string Compiler::symbol_string(const goos::Object& o) { + return o.as_symbol()->name; +} + +const goos::Object& Compiler::pair_car(const goos::Object& o) { + return o.as_pair()->car; +} + +const goos::Object& Compiler::pair_cdr(const goos::Object& o) { + return o.as_pair()->cdr; +} + +void Compiler::expect_empty_list(const goos::Object& o) { + if (!o.is_empty_list()) { + throw_compile_error(o, "expected to be an empty list"); + } +} \ No newline at end of file diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index ef5ba521fd..06c3e20302 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -1,22 +1,64 @@ #include "Val.h" +#include "Env.h" +#include "IR.h" /*! * Fallback to_gpr if a more optimized one is not provided. */ -RegVal* Val::to_gpr(FunctionEnv* fe) const { - (void)fe; - throw std::runtime_error("Val::to_gpr NYI"); +RegVal* Val::to_gpr(Env* fe) { + auto rv = to_reg(fe); + if (rv->ireg().kind == emitter::RegKind::GPR) { + return rv; + } else { + throw std::runtime_error("Val::to_gpr NYI"); // todo + } } /*! * Fallback to_xmm if a more optimized one is not provided. */ -RegVal* Val::to_xmm(FunctionEnv* fe) const { +RegVal* Val::to_xmm(Env* fe) { (void)fe; - throw std::runtime_error("Val::to_xmm NYI"); + throw std::runtime_error("Val::to_xmm NYI"); // todo } -RegVal* None::to_reg(FunctionEnv* fe) const { +RegVal* RegVal::to_reg(Env* fe) { (void)fe; - throw std::runtime_error("Cannot put None into a register."); + return this; } + +RegVal* RegVal::to_gpr(Env* fe) { + (void)fe; + if (m_ireg.kind == emitter::RegKind::GPR) { + return this; + } else { + throw std::runtime_error("RegVal::to_gpr NYI"); // todo + } +} + +RegVal* RegVal::to_xmm(Env* fe) { + (void)fe; + if (m_ireg.kind == emitter::RegKind::XMM) { + return this; + } else { + throw std::runtime_error("RegVal::to_xmm NYI"); // todo + } +} + +RegVal* IntegerConstantVal::to_reg(Env* fe) { + auto rv = fe->make_gpr(m_ts); + fe->emit(std::make_unique(rv, m_value)); + return rv; +} + +RegVal* SymbolVal::to_reg(Env* fe) { + auto re = fe->make_gpr(m_ts); + fe->emit(std::make_unique(re, m_name)); + return re; +} + +RegVal* SymbolValueVal::to_reg(Env* fe) { + auto re = fe->make_gpr(m_ts); + fe->emit(std::make_unique(re, m_sym, m_sext)); + return re; +} \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index a95f9f285f..1313aa6427 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -12,8 +12,10 @@ #include "third-party/fmt/core.h" #include "common/type_system/TypeSystem.h" #include "goalc/regalloc/IRegister.h" +#include "Lambda.h" class RegVal; +class Env; class FunctionEnv; /*! @@ -30,11 +32,15 @@ class Val { } virtual std::string print() const = 0; - virtual RegVal* to_reg(FunctionEnv* fe) const = 0; - virtual RegVal* to_gpr(FunctionEnv* fe) const; - virtual RegVal* to_xmm(FunctionEnv* fe) const; + virtual RegVal* to_reg(Env* fe) { + (void)fe; + throw std::runtime_error("to_reg called on invalid Val: " + print()); + } + virtual RegVal* to_gpr(Env* fe); + virtual RegVal* to_xmm(Env* fe); const TypeSpec& type() const { return m_ts; } + void set_type(TypeSpec ts) { m_ts = std::move(ts); } protected: TypeSpec m_ts; @@ -44,10 +50,10 @@ class Val { * Special None Val used for the value of anything returning (none). */ class None : public Val { + public: explicit None(TypeSpec _ts) : Val(std::move(_ts)) {} explicit None(const TypeSystem& _ts) : Val(_ts.make_typespec("none")) {} std::string print() const override { return "none"; } - RegVal* to_reg(FunctionEnv* fe) const override; }; /*! @@ -59,22 +65,72 @@ class RegVal : public Val { bool is_register() const override { return true; } IRegister ireg() const override { return m_ireg; } std::string print() const override { return m_ireg.to_string(); }; - RegVal* to_reg(FunctionEnv* fe) const override; - RegVal* to_gpr(FunctionEnv* fe) const override; - RegVal* to_xmm(FunctionEnv* fe) const override; + RegVal* to_reg(Env* fe) override; + RegVal* to_gpr(Env* fe) override; + RegVal* to_xmm(Env* fe) override; protected: IRegister m_ireg; }; -// Symbol -// Lambda +/*! + * A Val representing a symbol. This is confusing but it's not actually the value of the symbol, + * but instead the symbol itself. + */ +class SymbolVal : public Val { + public: + SymbolVal(std::string name, TypeSpec ts) : Val(std::move(ts)), m_name(std::move(name)) {} + const std::string& name() const { return m_name; } + std::string print() const override { return "<" + m_name + ">"; } + RegVal* to_reg(Env* fe) override; + + protected: + std::string m_name; +}; + +class SymbolValueVal : public Val { + public: + SymbolValueVal(const SymbolVal* sym, TypeSpec ts, bool sext) + : Val(std::move(ts)), m_sym(sym), m_sext(sext) {} + const std::string& name() const { return m_sym->name(); } + std::string print() const override { return "[<" + name() + ">]"; } + RegVal* to_reg(Env* fe) override; + + protected: + const SymbolVal* m_sym = nullptr; + bool m_sext = false; +}; + +/*! + * A Val representing a GOAL lambda. It can be a "real" x86-64 function, in which case the + * FunctionEnv is set. Otherwise, just contains a Lambda. + */ +class LambdaVal : public Val { + public: + LambdaVal(TypeSpec ts, Lambda lam) : Val(ts), m_lam(lam) {} + std::string print() const override { return "lambda-" + m_lam.debug_name; } + FunctionEnv* func = nullptr; + + protected: + Lambda m_lam; +}; + // Static // MemOffConstant // MemOffVar // MemDeref // PairEntry // Alias + +class IntegerConstantVal : public Val { + public: + IntegerConstantVal(TypeSpec ts, s64 value) : Val(ts), m_value(value) {} + std::string print() const override { return "integer-constant-" + std::to_string(m_value); } + RegVal* to_reg(Env* fe) override; + + protected: + s64 m_value = -1; +}; // IntegerConstant // FloatConstant // Bitfield diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp new file mode 100644 index 0000000000..9f3be74ce7 --- /dev/null +++ b/goalc/compiler/compilation/Atoms.cpp @@ -0,0 +1,233 @@ +#include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" + +/*! + * Main table for compiler forms + */ +static const std::unordered_map< + std::string, + Val* (Compiler::*)(const goos::Object& form, const goos::Object& rest, Env* env)> + goal_forms = { + // // inline asm + // {".ret", &Compiler::compile_asm}, + // {".push", &Compiler::compile_asm}, + // {".pop", &Compiler::compile_asm}, + // {".jmp", &Compiler::compile_asm}, + // {".sub", &Compiler::compile_asm}, + // {".ret-reg", &Compiler::compile_asm}, + // + // // BLOCK FORMS + {"top-level", &Compiler::compile_top_level}, + {"begin", &Compiler::compile_begin}, + {"block", &Compiler::compile_block}, + {"return-from", &Compiler::compile_return_from}, + {"label", &Compiler::compile_label}, + {"goto", &Compiler::compile_goto}, + // + // // COMPILER CONTROL + // {"gs", &Compiler::compile_gs}, + {":exit", &Compiler::compile_exit}, + {"asm-file", &Compiler::compile_asm_file}, + {"listen-to-target", &Compiler::compile_listen_to_target}, + {"reset-target", &Compiler::compile_reset_target}, + {":status", &Compiler::compile_poke}, + // {"test", &Compiler::compile_test}, + // {"in-package", &Compiler::compile_in_package}, + // + // // CONDITIONAL COMPILATION + {"#cond", &Compiler::compile_gscond}, + {"defglobalconstant", &Compiler::compile_defglobalconstant}, + {"seval", &Compiler::compile_seval}, + // + // // CONTROL FLOW + // {"cond", &Compiler::compile_cond}, + // {"when-goto", &Compiler::compile_when_goto}, + // + // // DEFINITION + {"define", &Compiler::compile_define}, + // {"define-extern", &Compiler::compile_define_extern}, + // {"set!", &Compiler::compile_set}, + // {"defun-extern", &Compiler::compile_defun_extern}, + // {"declare-method", &Compiler::compile_declare_method}, + // + // // DEFTYPE + // {"deftype", &Compiler::compile_deftype}, + // + // // ENUM + // {"defenum", &Compiler::compile_defenum}, + // + // // Field Access + // {"->", &Compiler::compile_deref}, + // {"&", &Compiler::compile_addr_of}, + // + // + // // LAMBDA + // {"lambda", &Compiler::compile_lambda}, + // {"inline", &Compiler::compile_inline}, + // {"with-inline", &Compiler::compile_with_inline}, + // {"rlet", &Compiler::compile_rlet}, + // {"mlet", &Compiler::compile_mlet}, + // {"get-ra-ptr", &Compiler::compile_get_ra_ptr}, + // + // + // + // // MACRO + // {"print-type", &Compiler::compile_print_type}, + {"quote", &Compiler::compile_quote}, + // {"defconstant", &Compiler::compile_defconstant}, + // + // {"declare", &Compiler::compile_declare}, + // + // + // + // + // // OBJECT + // + // {"the", &Compiler::compile_the}, + // {"the-as", &Compiler::compile_the_as}, + // + // {"defmethod", &Compiler::compile_defmethod}, + // + // {"current-method-type", &Compiler::compile_current_method_type}, + // {"new", &Compiler::compile_new}, + // {"method", &Compiler::compile_method}, + // + // // PAIR + // {"car", &Compiler::compile_car}, + // {"cdr", &Compiler::compile_cdr}, + // + // // IT IS MATH + // {"+", &Compiler::compile_add}, + // {"-", &Compiler::compile_sub}, + // {"*", &Compiler::compile_mult}, + // {"/", &Compiler::compile_divide}, + // {"shlv", &Compiler::compile_shlv}, + // {"shrv", &Compiler::compile_shrv}, + // {"sarv", &Compiler::compile_sarv}, + // {"shl", &Compiler::compile_shl}, + // {"shr", &Compiler::compile_shr}, + // {"sar", &Compiler::compile_sar}, + // {"mod", &Compiler::compile_mod}, + // {"logior", &Compiler::compile_logior}, + // {"logxor", &Compiler::compile_logxor}, + // {"logand", &Compiler::compile_logand}, + // {"lognot", &Compiler::compile_lognot}, + // {"=", &Compiler::compile_condition_as_bool}, + // {"!=", &Compiler::compile_condition_as_bool}, + // {"eq?", &Compiler::compile_condition_as_bool}, + // {"not", &Compiler::compile_condition_as_bool}, + // {"<=", &Compiler::compile_condition_as_bool}, + // {">=", &Compiler::compile_condition_as_bool}, + // {"<", &Compiler::compile_condition_as_bool}, + // {">", &Compiler::compile_condition_as_bool}, + // + // // BUILDER + // {"builder", &Compiler::compile_builder}, + // + // // UTIL + // {"set-config!", &Compiler::compile_set_config}, + // + // + // + + // + // // temporary testing hacks... + // {"send-test", &Compiler::compile_send_test_data}, +}; + +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); + default: + ice("Don't know how to compile " + code.print()); + } + return get_none(); +} + +Val* Compiler::compile_pair(const goos::Object& code, Env* env) { + auto pair = code.as_pair(); + auto head = pair->car; + auto rest = pair->cdr; + + if (head.is_symbol()) { + auto head_sym = head.as_symbol(); + // first try as a goal compiler form + auto kv_gfs = goal_forms.find(head_sym->name); + if (kv_gfs != goal_forms.end()) { + return ((*this).*(kv_gfs->second))(code, rest, env); + } + + goos::Object macro_obj; + if (try_getting_macro_from_goos(head, ¯o_obj)) { + return compile_goos_macro(code, macro_obj, rest, env); + } + + // todo enum + } + + // todo function or method call + ice("unhandled compile_pair on " + code.print()); + return nullptr; +} + +Val* Compiler::compile_integer(const goos::Object& code, Env* env) { + return compile_integer(code.integer_obj.value, env); +} + +Val* Compiler::compile_integer(s64 value, Env* env) { + auto fe = get_parent_env_of_type(env); + return fe->alloc_val(m_ts.make_typespec("int"), value); +} + +SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) { + auto fe = get_parent_env_of_type(env); + return fe->alloc_val(name, m_ts.make_typespec("symbol")); +} + +Val* Compiler::compile_symbol(const goos::Object& form, Env* env) { + auto name = symbol_string(form); + + if (name == "none") { + return get_none(); + } + + // todo mlet + // todo lexical + // todo global constant + + auto global_constant = m_global_constants.find(form.as_symbol()); + auto existing_symbol = m_symbol_types.find(form.as_symbol()->name); + + if (global_constant != m_global_constants.end()) { + // check there is no symbol with the same name + if (existing_symbol != m_symbol_types.end()) { + throw_compile_error(form, + "symbol is both a runtime symbol and a global constant. Something is " + "likely very wrong."); + } + + // got a global constant + return compile_error_guard(global_constant->second, env); + } + + return compile_get_symbol_value(name, env); +} + +Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) { + auto existing_symbol = m_symbol_types.find(name); + if (existing_symbol == m_symbol_types.end()) { + throw std::runtime_error("The symbol " + name + " was not defined"); + } + + auto ts = existing_symbol->second; + auto sext = m_ts.lookup_type(ts)->get_load_signed(); + auto fe = get_parent_env_of_type(env); + auto sym = fe->alloc_val(name, m_ts.make_typespec("symbol")); + auto re = fe->alloc_val(sym, ts, sext); + return re; +} \ No newline at end of file diff --git a/goalc/compiler/compilation/Block.cpp b/goalc/compiler/compilation/Block.cpp new file mode 100644 index 0000000000..1db6980419 --- /dev/null +++ b/goalc/compiler/compilation/Block.cpp @@ -0,0 +1,144 @@ +#include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" + +using namespace goos; + +Val* Compiler::compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env) { + return compile_begin(form, rest, env); +} + +Val* Compiler::compile_begin(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)form; + Val* result = get_none(); + for_each_in_list(rest, [&](const Object& o) { result = compile_error_guard(o, env); }); + return result; +} + +Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest, Env* env) { + auto rest = &_rest; + auto name = pair_car(*rest); + rest = &pair_cdr(*rest); + + if (!rest->is_pair()) { + throw_compile_error(form, "Block form has an empty or invliad body"); + } + + auto fe = get_parent_env_of_type(env); + + // create environment + auto block_env = fe->alloc_env(env, symbol_string(name)); + + // we need to create a return value register, as a "return-from" statement inside the block may + // set it. for now it has a type of none, but we will set it more accurate after compiling the + // block. + // block_env->return_value = env->alloc_reg(get_base_typespec("none")); + block_env->return_value = env->make_gpr(m_ts.make_typespec("none")); + + // create label to the end of the block (we don't yet know where it is...) + block_env->end_label = Label(fe); + + // compile everything in the body + Val* result = get_none(); + for_each_in_list(*rest, [&](const Object& o) { result = compile_error_guard(o, block_env); }); + + // if no return-from's were used, we can ignore the return_value register, and basically turn this + // into a begin. this allows a block which returns a floating point value to return the value in + // an xmm register, which is likely to eliminate a gpr->xmm move. + if (block_env->return_types.empty()) { + return result; + } + + // determine return type as the lowest common ancestor of the block's last form and any + // return-from's + auto& return_types = block_env->return_types; + return_types.push_back(result->type()); + auto return_type = m_ts.lowest_common_ancestor(return_types); + block_env->return_value->set_type(return_type); + + // an IR to move the result of the block into the block's return register (if no return-from's are + // taken) + auto ir_move_rv = std::make_unique(block_env->return_value, result->to_gpr(fe)); + + // note - one drawback of doing this single pass is that a block always evaluates to a gpr. + // so we may have an unneeded xmm -> gpr move that could have been an xmm -> xmm that could have + // been eliminated. + env->emit(std::move(ir_move_rv)); + + // now we know the end of the block, so we set the label index to be on whatever comes after the + // return move. functions always end with a "null" IR and "null" instruction, so this is safe. + block_env->end_label.idx = block_env->end_label.func->code().size(); + + return block_env->return_value; +} + +Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object& _rest, Env* env) { + const Object* rest = &_rest; + auto block_name = symbol_string(pair_car(*rest)); + rest = &pair_cdr(*rest); + auto value_expression = pair_car(*rest); + expect_empty_list(pair_cdr(*rest)); + + // evaluate expression to return + auto result = compile_error_guard(value_expression, env); + auto fe = get_parent_env_of_type(env); + + // find block to return from + auto block = dynamic_cast(env->find_block(block_name)); + if (!block) { + throw_compile_error(form, + "The return-from form was unable to find a block named " + block_name); + } + + // move result into return register + auto ir_move_rv = std::make_unique(block->return_value, result->to_gpr(fe)); + + // inform block of our possible return type + block->return_types.push_back(result->type()); + + env->emit(std::move(ir_move_rv)); + + // jump to end of block + auto ir_jump = std::make_unique(&block->end_label); + // we know this label is a real label. even though end_label doesn't know where it is, there is an + // actual label object. this means we won't try to resolve this label _by name_ later on when the + // block is done. + // ir_jump->resolved = true; + env->emit(std::move(ir_jump)); + + // In the real GOAL, there is likely a bug here where a non-none value is returned. + return get_none(); +} + +Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest, Env* env) { + auto label_name = symbol_string(pair_car(rest)); + expect_empty_list(pair_cdr(rest)); + + // make sure we don't have a label with this name already + auto& labels = env->get_label_map(); + auto kv = labels.find(label_name); + if (kv != labels.end()) { + throw_compile_error( + form, "There are two labels named " + label_name + " in the same label environment"); + } + + // make a label pointing to the end of the current function env. + auto func_env = get_parent_env_of_type(env); + labels[label_name] = Label(func_env, func_env->code().size()); + return get_none(); +} + +Val* Compiler::compile_goto(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)form; + auto label_name = symbol_string(pair_car(rest)); + expect_empty_list(pair_cdr(rest)); + + auto ir_goto = std::make_unique(); + // this requires looking up the label by name after, as it may be a goto to a label which has not + // yet been defined. + + // add this goto to the list of gotos to resolve after the function is done. + // it's safe to have this reference, as the FunctionEnv also owns the goto. + get_parent_env_of_type(env)->unresolved_gotos.push_back({ir_goto.get(), label_name}); + env->emit(std::move(ir_goto)); + return get_none(); +} \ No newline at end of file diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp new file mode 100644 index 0000000000..6d09bf9067 --- /dev/null +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -0,0 +1,169 @@ +#include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" +#include "goalc/util/Timer.h" +#include "goalc/util/file_io.h" + +Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + auto args = get_va(form, rest); + va_check(form, args, {}, {}); + if (m_listener.is_connected()) { + m_listener.send_reset(false); + } + m_want_exit = true; + return get_none(); +} + +Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + try { + for_each_in_list(rest, [&](const goos::Object& o) { + m_goos.eval_with_rewind(o, m_goos.global_environment.as_env()); + }); + } catch (std::runtime_error& e) { + throw_compile_error(form, std::string("seval error: ") + e.what()); + } + return get_none(); +} + +Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + int i = 0; + std::string filename; + bool load = false; + bool color = false; + bool write = false; + bool no_code = false; + + std::vector> timing; + Timer total_timer; + + for_each_in_list(rest, [&](const goos::Object& o) { + if (i == 0) { + filename = as_string(o); + } else { + auto setting = symbol_string(o); + if (setting == ":load") { + load = true; + } else if (setting == ":color") { + color = true; + } else if (setting == ":write") { + write = true; + } else if (setting == ":no-code") { + no_code = true; + } else { + throw_compile_error(form, "invalid option " + setting + " in asm-file form"); + } + } + i++; + }); + + Timer reader_timer; + auto code = m_goos.reader.read_from_file(filename); + timing.emplace_back("read", reader_timer.getMs()); + + Timer compile_timer; + std::string obj_file_name = filename; + for (int idx = int(filename.size()) - 1; idx-- > 0;) { + if (filename.at(idx) == '\\' || filename.at(idx) == '/') { + obj_file_name = filename.substr(idx + 1); + } + } + + obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.')); + auto obj_file = compile_object_file(obj_file_name, code, !no_code); + timing.emplace_back("compile", compile_timer.getMs()); + + if (color) { + Timer color_timer; + color_object_file(obj_file); + timing.emplace_back("color", color_timer.getMs()); + + Timer codegen_timer; + auto data = codegen_object_file(obj_file); + timing.emplace_back("codegen", codegen_timer.getMs()); + + if (load) { + if (m_listener.is_connected()) { + m_listener.send_code(data); + } else { + printf("WARNING - couldn't load because listener isn't connected\n"); + } + } + + if (write) { + // auto output_dir = as_string(get_constant_or_error(form, "*compiler-output-path*")); + // todo, change extension based on v3/v4 + auto output_name = m_goos.reader.get_source_dir() + "/out/" + obj_file_name + ".o"; + util::write_binary_file(output_name, (void*)data.data(), data.size()); + } + } else { + if (load) { + printf("WARNING - couldn't load because coloring is not enabled\n"); + } + + if (write) { + printf("WARNING - couldn't write because coloring is not enabled\n"); + } + } + + // if(truthy(get_config("print-asm-file-time"))) { + for (auto& e : timing) { + printf(" %12s %4.2f\n", e.first.c_str(), e.second); + } + // } + + return get_none(); +} + +Val* Compiler::compile_listen_to_target(const goos::Object& form, + const goos::Object& rest, + Env* env) { + (void)env; + std::string ip = "127.0.0.1"; + int port = 8112; + bool got_port = false, got_ip = false; + + for_each_in_list(rest, [&](const goos::Object& o) { + if (o.is_string()) { + if (got_ip) { + throw_compile_error(form, "got multiple strings!"); + } + got_ip = true; + ip = o.as_string()->data; + } else if (o.is_int()) { + if (got_port) { + throw_compile_error(form, "got multiple ports!"); + } + got_port = true; + port = o.integer_obj.value; + } else { + throw_compile_error(form, "invalid argument to listen-to-target"); + } + }); + + m_listener.connect_to_target(30, ip, port); + return get_none(); +} + +Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + bool shutdown = false; + for_each_in_list(rest, [&](const goos::Object& o) { + if (o.is_symbol() && symbol_string(o) == ":shutdown") { + shutdown = true; + } else { + throw_compile_error(form, "invalid argument to reset-target"); + } + }); + m_listener.send_reset(shutdown); + return get_none(); +} + +Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + auto args = get_va(form, rest); + va_check(form, args, {}, {}); + m_listener.send_poke(); + return get_none(); +} \ No newline at end of file diff --git a/goalc/compiler/compilation/Define.cpp b/goalc/compiler/compilation/Define.cpp new file mode 100644 index 0000000000..336d711a7b --- /dev/null +++ b/goalc/compiler/compilation/Define.cpp @@ -0,0 +1,41 @@ +#include "goalc/compiler/Compiler.h" + +Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {}); + auto& sym = args.unnamed.at(0); + auto& val = args.unnamed.at(1); + + // check we aren't duplicated a name as both a symbol and global constant + auto global_constant = m_global_constants.find(sym.as_symbol()); + if (global_constant != m_global_constants.end()) { + throw_compile_error( + form, "it is illegal to define a GOAL symbol with the same name as a GOAL global constant"); + } + + auto fe = get_parent_env_of_type(env); + auto sym_val = fe->alloc_val(symbol_string(sym), m_ts.make_typespec("symbol")); + auto compiled_val = compile_error_guard(val, env); + auto as_lambda = dynamic_cast(compiled_val); + if (as_lambda) { + // there are two cases in which we save a function body that is passed to a define: + // 1. It generated code [so went through the compiler] and the allow_inline flag is set. + // 2. It didn't generate code [so explicitly with :inline-only lambdas] + // The third case - immediate lambdas - don't get passed to a define, + // so this won't cause those to live for longer than they should + if ((as_lambda->func && as_lambda->func->settings.allow_inline) || !as_lambda->func) { + m_inlineable_functions[sym.as_symbol()] = as_lambda; + } + } + + auto in_gpr = compiled_val->to_gpr(fe); + auto existing_type = m_symbol_types.find(sym.as_symbol()->name); + if (existing_type == m_symbol_types.end()) { + m_symbol_types[sym.as_symbol()->name] = in_gpr->type(); + } else { + typecheck(form, existing_type->second, in_gpr->type(), "define on existing symbol"); + } + + fe->emit(std::make_unique(sym_val, in_gpr)); + return in_gpr; +} diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp new file mode 100644 index 0000000000..89b5afb265 --- /dev/null +++ b/goalc/compiler/compilation/Macro.cpp @@ -0,0 +1,117 @@ +#include "goalc/compiler/Compiler.h" + +using namespace goos; + +bool Compiler::try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest) { + Object macro_obj; + bool got_macro = false; + try { + macro_obj = m_goos.eval_symbol(macro_name, m_goos.goal_env.as_env()); + if (macro_obj.is_macro()) { + got_macro = true; + } + } catch (std::runtime_error& e) { + got_macro = false; + } + + if (got_macro) { + *dest = macro_obj; + } + return got_macro; +} + +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(); + 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); + m_goos.goal_to_goos.enclosing_method_type = + get_parent_env_of_type(env)->method_of_type_name; + auto goos_result = m_goos.eval_list_return_last(macro->body, macro->body, mac_env); + m_goos.goal_to_goos.reset(); + return compile_error_guard(goos_result, env); +} + +Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env) { + if (!rest.is_pair()) { + throw_compile_error(form, "#cond must have at least one clause, which must be a form"); + } + Val* result = nullptr; + + Object lst = rest; + for (;;) { + if (lst.is_pair()) { + Object current_case = lst.as_pair()->car; + if (!current_case.is_pair()) { + throw_compile_error(lst, "Bad case in #cond"); + } + + // check condition: + Object condition_result = + m_goos.eval_with_rewind(current_case.as_pair()->car, m_goos.global_environment.as_env()); + if (m_goos.truthy(condition_result)) { + if (current_case.as_pair()->cdr.is_empty_list()) { + return get_none(); + } + // got a match! + result = get_none(); + + for_each_in_list(current_case.as_pair()->cdr, + [&](Object o) { result = compile_error_guard(o, env); }); + return result; + } else { + // no match, continue. + lst = lst.as_pair()->cdr; + } + } else if (lst.is_empty_list()) { + return get_none(); + } else { + throw_compile_error(form, "malformed #cond"); + } + } +} + +Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {{}}, {}); + auto thing = args.unnamed.at(0); + switch (thing.type) { + case goos::ObjectType::SYMBOL: + return compile_get_sym_obj(thing.as_symbol()->name, env); + default: + throw_compile_error(form, "Can't quote this"); + } + return get_none(); +} + +Val* Compiler::compile_defglobalconstant(const goos::Object& form, + const goos::Object& _rest, + Env* env) { + auto rest = &_rest; + (void)env; + if (!rest->is_pair()) { + throw_compile_error(form, "invalid defglobalconstant"); + } + + auto sym = pair_car(*rest).as_symbol(); + rest = &pair_cdr(*rest); + auto value = pair_car(*rest); + + rest = &rest->as_pair()->cdr; + if (!rest->is_empty_list()) { + throw_compile_error(form, "invalid defglobalconstant"); + } + + // GOAL constant + m_global_constants[sym] = value; + + // GOOS constant + m_goos.global_environment.as_env()->vars[sym] = value; + + return get_none(); +} \ No newline at end of file diff --git a/goalc/emitter/ObjectFileData.cpp b/goalc/emitter/ObjectFileData.cpp index b18e1468cc..cf338f4b8b 100644 --- a/goalc/emitter/ObjectFileData.cpp +++ b/goalc/emitter/ObjectFileData.cpp @@ -14,6 +14,11 @@ std::vector ObjectFileData::to_vector() const { // data (code + static objects, by segment) for (int seg = N_SEG; seg-- > 0;) { result.insert(result.end(), segment_data[seg].begin(), segment_data[seg].end()); + // printf("seg %d data\n", seg); + // for (auto x : segment_data[seg]) { + // printf("%02x ", x); + // } + // printf("\n"); } return result; } diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 988d61bd78..0c14eb1e37 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -136,6 +136,15 @@ IR_Record ObjectGenerator::get_future_ir_record(const FunctionRecord& func, int return rec; } +IR_Record ObjectGenerator::get_future_ir_record_in_same_func(const IR_Record& irec, int ir_id) { + assert(irec.func_id == int(m_function_data_by_seg.at(irec.seg).size()) - 1); + IR_Record rec; + rec.seg = irec.seg; + rec.func_id = irec.func_id; + rec.ir_id = ir_id; + return rec; +} + /*! * Add a new Instruction for the given IR instruction. */ @@ -156,6 +165,11 @@ InstructionRecord ObjectGenerator::add_instr(Instruction inst, IR_Record ir) { return rec; } +void ObjectGenerator::add_instr_no_ir(FunctionRecord func, Instruction inst) { + assert(func.func_id == int(m_function_data_by_seg.at(func.seg).size()) - 1); + m_function_data_by_seg.at(func.seg).at(func.func_id).instructions.push_back(inst); +} + /*! * Create a new static object in the given segment. */ @@ -461,7 +475,7 @@ std::vector ObjectGenerator::generate_header_v3() { offset += push_data(N_SEG, result); offset += sizeof(u32) * N_SEG * 4; // 4 u32's per segment - + offset += 4; struct SizeOffset { uint32_t offset, size; }; diff --git a/goalc/emitter/ObjectGenerator.h b/goalc/emitter/ObjectGenerator.h index c7ee449d25..4d3c436c16 100644 --- a/goalc/emitter/ObjectGenerator.h +++ b/goalc/emitter/ObjectGenerator.h @@ -43,7 +43,9 @@ class ObjectGenerator { int min_align = 16); // should align and insert function tag IR_Record add_ir(const FunctionRecord& func); IR_Record get_future_ir_record(const FunctionRecord& func, int ir_id); + IR_Record get_future_ir_record_in_same_func(const IR_Record& irec, int ir_id); InstructionRecord add_instr(Instruction inst, IR_Record ir); + void add_instr_no_ir(FunctionRecord func, Instruction inst); StaticRecord add_static_to_seg(int seg, int min_align = 16); void link_instruction_jump(InstructionRecord jump_instr, IR_Record destination); void link_static_type_ptr(StaticRecord rec, int offset, const std::string& type_name); diff --git a/goalc/goos/Interpreter.cpp b/goalc/goos/Interpreter.cpp index 285f607fbe..7e1c411315 100644 --- a/goalc/goos/Interpreter.cpp +++ b/goalc/goos/Interpreter.cpp @@ -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/goalc/goos/Interpreter.h index b19903e68e..3d58a5c369 100644 --- a/goalc/goos/Interpreter.h +++ b/goalc/goos/Interpreter.h @@ -23,6 +23,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; @@ -47,14 +57,9 @@ class Interpreter { 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 +211,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/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt index adbaeb88e3..4cc5c90330 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,3 +1,7 @@ add_library(listener SHARED Listener.cpp) -target_link_libraries(listener cross_sockets) \ No newline at end of file +IF (WIN32) + target_link_libraries(listener cross_sockets) +ELSE () + target_link_libraries(listener cross_sockets pthread) +ENDIF () \ No newline at end of file diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index c47a0610d3..454dcd46f3 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -1,9 +1,12 @@ /*! * @file Listener.cpp * The Listener can connect to a Deci2Server for debugging. + * + * TODO - msg ID? */ -#ifdef __linux +#ifdef __linux__ +#include #include #include #include @@ -11,6 +14,10 @@ #define WIN32_LEAN_AND_MEAN #include #include + +// remove the evil windows min/max macros! +#undef min +#undef max #endif // TODO - i think im not including the dependency right..? @@ -18,10 +25,14 @@ #include #include +#include +#include +#include #include "Listener.h" #include "common/versions.h" using namespace versions; +constexpr bool debug_listener = false; namespace listener { Listener::Listener() { @@ -54,9 +65,10 @@ bool Listener::is_connected() const { * Attempt to connect to the target. If the target isn't running, this should fail quickly. * Returns true if successfully connected. */ -bool Listener::connect_to_target(const std::string& ip, int port) { +bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) { if (m_connected) { - throw std::runtime_error("attempted a Listener::connect_to_target when already connected!"); + printf("already connected!\n"); + return true; } if (listen_socket >= 0) { @@ -71,7 +83,7 @@ bool Listener::connect_to_target(const std::string& ip, int port) { return false; } - if (set_socket_timeout(listen_socket, 100000) < 0) { + if (set_socket_timeout(listen_socket, 500000) < 0) { close_socket(listen_socket); listen_socket = -1; return false; @@ -97,12 +109,21 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } // connect! - int rv = connect(listen_socket, (sockaddr*)&server_address, sizeof(server_address)); + int rv, i; + for (i = 0; i < n_tries; i++) { + rv = connect(listen_socket, (sockaddr*)&server_address, sizeof(server_address)); + if (rv >= 0) { + break; + } + std::this_thread::sleep_for(std::chrono::microseconds(100000)); + } if (rv < 0) { printf("[Listener] Failed to connect\n"); close_socket(listen_socket); listen_socket = -1; return false; + } else { + printf("[Listener] Socket connected established! (took %d tries)\n", i); } // get the GOAL version number, to make sure we connected to the right thing @@ -244,4 +265,126 @@ void Listener::receive_func() { } } +void Listener::record_messages(ListenerMessageKind kind) { + if (filter != ListenerMessageKind::MSG_INVALID) { + printf("[Listener] Already recording!\n"); + } + filter = kind; +} + +std::vector Listener::stop_recording_messages() { + filter = ListenerMessageKind::MSG_INVALID; + auto result = message_record; + message_record.clear(); + return result; +} + +void Listener::send_code(std::vector& code) { + got_ack = false; + int total_size = code.size() + sizeof(ListenerMessageHeader); + if (total_size > BUFFER_SIZE) { + printf("[ERROR] Listener send_code got too big of a message\n"); + return; + } + + auto* header = (ListenerMessageHeader*)m_buffer; + auto* buffer_data = (char*)(header + 1); + header->deci2_header.rsvd = 0; + header->deci2_header.len = total_size; + header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.src = 'H'; + header->deci2_header.dst = 'E'; + header->msg_size = code.size(); + header->ltt_msg_kind = LTT_MSG_CODE; + header->u6 = 0; + header->u8 = 0; + memcpy(buffer_data, code.data(), code.size()); + send_buffer(total_size); +} + +void Listener::send_reset(bool shutdown) { + if (!m_connected) { + printf("Not connected, so cannot reset target.\n"); + return; + } + auto* header = (ListenerMessageHeader*)m_buffer; + header->deci2_header.rsvd = 0; + header->deci2_header.len = sizeof(ListenerMessageHeader); + header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.src = 'H'; + header->deci2_header.dst = 'E'; + header->msg_size = 0; + header->ltt_msg_kind = LTT_MSG_RESET; + header->u6 = 0; + header->u8 = shutdown ? UINT64_MAX : 0; + send_buffer(sizeof(ListenerMessageHeader)); + disconnect(); + close_socket(listen_socket); + printf("closed connection to target\n"); +} + +void Listener::send_poke() { + if (!m_connected) { + printf("Not connected, so cannot poke target.\n"); + return; + } + auto* header = (ListenerMessageHeader*)m_buffer; + header->deci2_header.rsvd = 0; + header->deci2_header.len = sizeof(ListenerMessageHeader); + header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.src = 'H'; + header->deci2_header.dst = 'E'; + header->msg_size = 0; + header->ltt_msg_kind = LTT_MSG_POKE; + header->u6 = 0; + header->u8 = 0; + send_buffer(sizeof(ListenerMessageHeader)); +} + +void Listener::send_buffer(int sz) { + int wrote = 0; + + if (debug_listener) { + printf("[L -> T] sending %d bytes...\n", sz); + } + + got_ack = false; + waiting_for_ack = true; + while (wrote < sz) { + auto to_send = std::min(512, sz - wrote); + auto x = write_to_socket(listen_socket, m_buffer + wrote, to_send); + wrote += x; + } + + if (debug_listener) { + printf(" waiting for ack...\n"); + } + + if (wait_for_ack()) { + if (debug_listener) { + printf("ack buff:\n"); + printf("%s\n", ack_recv_buff); + printf(" OK\n"); + } + } else { + printf(" NG - target has timed out. If it has died, disconnect with (disconnect-target)\n"); + } +} + +bool Listener::wait_for_ack() { + if (!m_connected) { + printf("wait_for_ack called when not connected!\n"); + return false; + } + + for (int i = 0; i < 2000; i++) { + if (got_ack) + return true; + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + } + + waiting_for_ack = false; + return false; +} + } // namespace listener diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 8d8fd25982..0c16d3028a 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -19,13 +19,22 @@ class Listener { static constexpr int BUFFER_SIZE = 32 * 1024 * 1024; Listener(); ~Listener(); - bool connect_to_target(const std::string& ip = "127.0.0.1", int port = DECI2_PORT); + bool connect_to_target(int n_tries = 1, + const std::string& ip = "127.0.0.1", + int port = DECI2_PORT); void record_messages(ListenerMessageKind kind); - void stop_recording_messages(); + std::vector stop_recording_messages(); bool is_connected() const; + void send_reset(bool shutdown); + void send_poke(); void disconnect(); + void send_code(std::vector& code); + bool most_recent_send_was_acked() { return got_ack; } private: + void send_buffer(int sz); + bool wait_for_ack(); + char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? bool receive_thread_running = false; //! is the receive thread unjoined? diff --git a/goalc/logger/Logger.cpp b/goalc/logger/Logger.cpp index 684d406a6a..63e3596d22 100644 --- a/goalc/logger/Logger.cpp +++ b/goalc/logger/Logger.cpp @@ -4,6 +4,7 @@ void Logger::close() { if (fp) { fclose(fp); + fp = nullptr; } } diff --git a/goalc/main.cpp b/goalc/main.cpp index 57d145c069..b86bda8981 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -1,13 +1,13 @@ #include -#include "goalc/goos/Interpreter.h" +#include "goalc/compiler/Compiler.h" int main(int argc, char** argv) { (void)argc; (void)argv; printf("goal compiler\n"); - goos::Interpreter interp; - interp.execute_repl(); + Compiler compiler; + compiler.execute_repl(); return 0; } diff --git a/goalc/regalloc/allocate.cpp b/goalc/regalloc/allocate.cpp index 8e6bf798ea..8b4f5a26f3 100644 --- a/goalc/regalloc/allocate.cpp +++ b/goalc/regalloc/allocate.cpp @@ -12,10 +12,12 @@ namespace { * Print out the input data for debugging. */ void print_allocate_input(const AllocationInput& in) { - fmt::print("[RegAlloc] Debug Input:\n"); + fmt::print("[RegAlloc] Debug Input Program:\n"); if (in.instructions.size() == in.debug_instruction_names.size()) { for (size_t i = 0; i < in.instructions.size(); i++) { - fmt::print(" [{:3d}] {:30} -> {:30}\n", in.debug_instruction_names.at(i), + // fmt::print(" [{}] {} -> {}\n", in.debug_instruction_names.at(i), + // in.instructions.at(i).print()); + fmt::print(" [{:3d}] {:30} -> {:30}\n", i, in.debug_instruction_names.at(i), in.instructions.at(i).print()); } } else { @@ -23,6 +25,7 @@ void print_allocate_input(const AllocationInput& in) { fmt::print(" [{:3d}] {}\n", instruction.print()); } } + fmt::print("[RegAlloc] Debug Input Constraints:\n"); for (const auto& c : in.constraints) { fmt::print(" {}\n", c.to_string()); } diff --git a/goalc/util/CMakeLists.txt b/goalc/util/CMakeLists.txt index 609a1fbb68..fdca90d0c6 100644 --- a/goalc/util/CMakeLists.txt +++ b/goalc/util/CMakeLists.txt @@ -1 +1 @@ -add_library(util SHARED text_util.cpp file_io.cpp) \ No newline at end of file +add_library(util SHARED text_util.cpp file_io.cpp Timer.cpp) \ No newline at end of file diff --git a/goalc/util/Timer.cpp b/goalc/util/Timer.cpp new file mode 100644 index 0000000000..4ac44ab25c --- /dev/null +++ b/goalc/util/Timer.cpp @@ -0,0 +1,54 @@ +#include "Timer.h" + +#ifdef _WIN32 +#include +#define MS_PER_SEC 1000ULL // MS = milliseconds +#define US_PER_MS 1000ULL // US = microseconds +#define HNS_PER_US 10ULL // HNS = hundred-nanoseconds (e.g., 1 hns = 100 ns) +#define NS_PER_US 1000ULL + +#define HNS_PER_SEC (MS_PER_SEC * US_PER_MS * HNS_PER_US) +#define NS_PER_HNS (100ULL) // NS = nanoseconds +#define NS_PER_SEC (MS_PER_SEC * US_PER_MS * NS_PER_US) + +int Timer::clock_gettime_monotonic(struct timespec* tv) { + static LARGE_INTEGER ticksPerSec; + LARGE_INTEGER ticks; + double seconds; + + if (!ticksPerSec.QuadPart) { + QueryPerformanceFrequency(&ticksPerSec); + if (!ticksPerSec.QuadPart) { + errno = ENOTSUP; + return -1; + } + } + + QueryPerformanceCounter(&ticks); + + seconds = (double)ticks.QuadPart / (double)ticksPerSec.QuadPart; + tv->tv_sec = (time_t)seconds; + tv->tv_nsec = (long)((ULONGLONG)(seconds * NS_PER_SEC) % NS_PER_SEC); + + return 0; +} +#endif + +void Timer::start() { +#ifdef __linux__ + clock_gettime(CLOCK_MONOTONIC, &_startTime); +#elif _WIN32 + clock_gettime_monotonic(&_startTime); +#endif +} + +int64_t Timer::getNs() { + struct timespec now = {}; +#ifdef __linux__ + clock_gettime(CLOCK_MONOTONIC, &now); +#elif _WIN32 + clock_gettime_monotonic(&now); +#endif + return (int64_t)(now.tv_nsec - _startTime.tv_nsec) + + 1000000000 * (now.tv_sec - _startTime.tv_sec); +} diff --git a/goalc/util/Timer.h b/goalc/util/Timer.h new file mode 100644 index 0000000000..5972020339 --- /dev/null +++ b/goalc/util/Timer.h @@ -0,0 +1,47 @@ +#ifndef JAK_V2_TIMER_H +#define JAK_V2_TIMER_H + +#include +#include +#include + +/*! + * Timer for measuring time elapsed with clock_monotonic + */ +class Timer { + public: + /*! + * Construct and start timer + */ + explicit Timer() { start(); } + +#ifdef _WIN32 + int clock_gettime_monotonic(struct timespec* tv); +#endif + + /*! + * Start the timer + */ + void start(); + + /*! + * Get milliseconds elapsed + */ + double getMs() { return (double)getNs() / 1.e6; } + + double getUs() { return (double)getNs() / 1.e3; } + + /*! + * Get nanoseconds elapsed + */ + int64_t getNs(); + + /*! + * Get seconds elapsed + */ + double getSeconds() { return (double)getNs() / 1.e9; } + + struct timespec _startTime = {}; +}; + +#endif // JAK_V2_TIMER_H diff --git a/goalc/util/file_io.cpp b/goalc/util/file_io.cpp index d6b35e1735..095ad933a8 100644 --- a/goalc/util/file_io.cpp +++ b/goalc/util/file_io.cpp @@ -29,4 +29,17 @@ std::string combine_path(std::vector path) { return result; } +void 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); +} + } // namespace util diff --git a/goalc/util/file_io.h b/goalc/util/file_io.h index aae7340ee7..9fa7802040 100644 --- a/goalc/util/file_io.h +++ b/goalc/util/file_io.h @@ -8,6 +8,7 @@ namespace util { std::string read_text_file(const std::string& path); std::string combine_path(const std::string& parent, const std::string& child); std::string combine_path(std::vector path); +void write_binary_file(const std::string& name, void* data, size_t size); } // namespace util #endif // JAK1_FILE_IO_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e699fbfc03..40367fff70 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(goalc-test test_emitter_loads_and_store.cpp test_emitter_xmm32.cpp test_emitter_integer_math.cpp + test_compiler_and_runtime.cpp ) enable_testing() diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp new file mode 100644 index 0000000000..172b13bac1 --- /dev/null +++ b/test/test_compiler_and_runtime.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include "gtest/gtest.h" +#include "game/runtime.h" +#include "goalc/listener/Listener.h" +#include "goalc/compiler/Compiler.h" + +TEST(CompilerAndRuntime, ConstructCompiler) { + Compiler compiler; +} + +TEST(CompilerAndRuntime, StartRuntime) { + std::thread runtime_thread([]() { exec_runtime(0, nullptr); }); + + listener::Listener listener; + while (!listener.is_connected()) { + listener.connect_to_target(); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + } + + listener.send_reset(true); + runtime_thread.join(); +} + +TEST(CompilerAndRuntime, SendProgram) { + std::thread runtime_thread([]() { exec_runtime(0, nullptr); }); + Compiler compiler; + compiler.run_test("goal_src/test/test-return-integer-1.gc"); + compiler.shutdown_target(); + runtime_thread.join(); +} + +namespace { +std::string escaped_string(const std::string& in) { + std::string result; + for (auto x : in) { + switch (x) { + case '\n': + result.append("\\n"); + break; + case '\t': + result.append("\\t"); + break; + default: + result.push_back(x); + } + } + return result; +} + +struct CompilerTestRunner { + Compiler* c = nullptr; + + struct Test { + std::vector expected, actual; + std::string test_name; + }; + + std::vector tests; + + void run_test(const std::string& test_file, const std::vector& expected) { + auto result = c->run_test("goal_src/test/" + test_file); + EXPECT_EQ(result, expected); + tests.push_back({expected, result, test_file}); + } + + void print_summary() { + fmt::print("~~ Compiler Test Summary for {} tests... ~~\n", tests.size()); + int passed = 0; + for (auto& test : tests) { + if (test.expected == test.actual) { + fmt::print("[{:40}] PASS!\n", test.test_name); + passed++; + } else { + fmt::print("[{:40}] FAIL!\n", test.test_name); + fmt::print("expected:\n"); + for (auto& x : test.expected) { + fmt::print(" \"{}\"\n", escaped_string(x)); + } + fmt::print("result:\n"); + for (auto& x : test.actual) { + fmt::print(" \"{}\"\n", escaped_string(x)); + } + } + } + fmt::print("Total: passed {}/{} tests\n", passed, tests.size()); + } +}; + +} // namespace + +TEST(CompilerAndRuntime, CompilerTests) { + std::thread runtime_thread([]() { exec_runtime(0, nullptr); }); + Compiler compiler; + CompilerTestRunner runner; + runner.c = &compiler; + + runner.run_test("test-return-integer-1.gc", {"4886718345\n"}); + runner.run_test("test-return-integer-2.gc", {"23\n"}); + runner.run_test("test-return-integer-3.gc", {"-17\n"}); + runner.run_test("test-return-integer-4.gc", {"-2147483648\n"}); + runner.run_test("test-return-integer-5.gc", {"-2147483649\n"}); + runner.run_test("test-return-integer-6.gc", {"0\n"}); + runner.run_test("test-return-integer-7.gc", {"-123\n"}); + runner.run_test("test-conditional-compilation-1.gc", {"3\n"}); + // todo, test-conditional-compilation-2.gc + // these numbers match the game's memory layout for where the symbol table lives. + // it's probably not 100% needed to get this exactly, but it's a good sign that the global + // heap lives in the right spot because there should be no divergence in memory layout when its + // built. This also checks that #t, #f get "hashed" to the correct spot. + runner.run_test("test-get-symbol-1.gc", {"1342756\n"}); // 0x147d24 in hex + runner.run_test("test-get-symbol-2.gc", {"1342764\n"}); // 0x147d2c in hex + runner.run_test("test-define-1.gc", {"17\n"}); + runner.run_test("test-nested-blocks-1.gc", {"7\n"}); + runner.run_test("test-nested-blocks-2.gc", {"8\n"}); + runner.run_test("test-nested-blocks-3.gc", {"7\n"}); + runner.run_test("test-goto-1.gc", {"3\n"}); + runner.run_test("test-defglobalconstant-1.gc", {"17\n"}); + runner.run_test("test-defglobalconstant-2.gc", {"18\n"}); + + compiler.shutdown_target(); + runtime_thread.join(); + runner.print_summary(); +} \ No newline at end of file diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 182d3dbd49..158a7afb7a 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -46,7 +46,7 @@ TEST(Listener, DeciCheckNoListener) { } TEST(Listener, DeciThenListener) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Deci2Server s(always_false); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); @@ -66,7 +66,7 @@ TEST(Listener, DeciThenListener) { } TEST(Listener, DeciThenListener2) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Deci2Server s(always_false); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); @@ -80,7 +80,7 @@ TEST(Listener, DeciThenListener2) { } TEST(Listener, ListenerThenDeci) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Listener l; EXPECT_FALSE(l.connect_to_target()); Deci2Server s(always_false);