diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index cd8278cf4c..59ceb6bb53 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -30,18 +30,23 @@ jobs: with: submodules: true - name: Get Package Dependencies - run: sudo apt install build-essential cmake gcc g++ make nasm + run: sudo apt install build-essential cmake gcc g++ lcov make nasm - name: CMake Generation run: | cmake --version - cmake -B build + cmake -B build -DCODE_COVERAGE=ON - name: Build Project run: | cd build - make -j2 + make -j - name: Run Tests timeout-minutes: 5 - run: ./test.sh + run: ./test_code_coverage.sh + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./build/goalc-test_coverage.info build-windows: # Very good resource - https://cristianadam.eu/20191222/using-github-actions-with-c-plus-plus-and-cmake/ name: (Windows) Build & Test diff --git a/CMakeLists.txt b/CMakeLists.txt index 11066c5f43..a4719b0e4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,16 +2,17 @@ cmake_minimum_required(VERSION 3.16) project(jak) -set(CMAKE_CXX_STANDARD 14) +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,27 @@ 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/) + +if(CMAKE_COMPILER_IS_GNUCXX AND CODE_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags() + message("Code Coverage build is enabled!") +else() + message("Code Coverage build is disabled!") +endif() # includes relative to top level jak-project folder include_directories(./) @@ -45,6 +57,9 @@ add_subdirectory(common/type_system) # build common_util library add_subdirectory(common/util) +# build cross platform socket library +add_subdirectory(common/cross_sockets) + # build decompiler add_subdirectory(decompiler) @@ -68,5 +83,5 @@ add_subdirectory(third-party/fmt) # windows memory management lib IF (WIN32) - add_subdirectory(third-party/mman) -ENDIF() + add_subdirectory(third-party/mman) +ENDIF () diff --git a/README.md b/README.md index 6b33344a30..6f953c768b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Jak Project ![Build](https://github.com/water111/jak-project/workflows/Build/badge.svg) +[![Coverage Status](https://coveralls.io/repos/github/water111/jak-project/badge.svg?branch=master)](https://coveralls.io/github/water111/jak-project?branch=master) ## Table of Contents @@ -154,13 +155,9 @@ where the `~~ HACK ~~` message is from code in `KERNEL.CGO`. ## TODOs - Build on Windows! - - Networking - File paths - Timer - - CMake? - - Assembly - Windows calling convention for assembly stuff - - pthreads (can probably replace with `std::thread`, I don't remember why I used `pthread`s) - performance stats for `SystemThread` (probably just get rid of these performance stats completely) - `mmap`ing executable memory - line input library (appears windows compatible?) diff --git a/cmake/modules/CodeCoverage.cmake b/cmake/modules/CodeCoverage.cmake new file mode 100644 index 0000000000..27e7d3d404 --- /dev/null +++ b/cmake/modules/CodeCoverage.cmake @@ -0,0 +1,436 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags: +# append_coverage_compiler_flags() +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() +elseif(NOT CMAKE_COMPILER_IS_GNUCXX) + if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") + # Do nothing; exit conditional without error if true + elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + # Do nothing; exit conditional without error if true + else() + message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + endif() +endif() + +set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" + CACHE INTERNAL "") + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup lcov + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . -b ${BASEDIR} --zerocounters + # Create baseline to make sure untouched files show up in the report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b ${BASEDIR} -o ${Coverage_NAME}.base + + # Run tests + COMMAND ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + # add baseline counters + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + # filter collected data to final coverage report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + + # Generate HTML output + COMMAND ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${Coverage_NAME}.info + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${Coverage_NAME} # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Running gcovr + COMMAND ${GCOVR_PATH} --xml + -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}.xml + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Create folder + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + + # Running gcovr + COMMAND ${GCOVR_PATH} --html --html-details + -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}/index.html + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME} # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags diff --git a/common/cross_sockets/CMakeLists.txt b/common/cross_sockets/CMakeLists.txt new file mode 100644 index 0000000000..f97ddd0f83 --- /dev/null +++ b/common/cross_sockets/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(cross_sockets + SHARED + "xsocket.h" + "xsocket.cpp") + +IF (WIN32) + # set stuff for windows + target_link_libraries(cross_sockets wsock32 ws2_32) +ELSE() + # set stuff for other systems + target_link_libraries(cross_sockets) +ENDIF() diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp new file mode 100644 index 0000000000..763cf596f4 --- /dev/null +++ b/common/cross_sockets/xsocket.cpp @@ -0,0 +1,97 @@ +#ifdef __linux +#include +#include +#include +#elif _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif +#include +#include +#include + +int open_socket(int af, int type, int protocol) { +#ifdef __linux + return socket(af, type, protocol); +#elif _WIN32 + WSADATA wsaData = {0}; + int iResult = 0; + // Initialize Winsock + iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + printf("WSAStartup failed: %d\n", iResult); + return 1; + } + return socket(af, type, protocol); +#endif +} + +void close_socket(int sock) { + if (sock < 0) { + return; + } +#ifdef __linux + close(sock); +#elif _WIN32 + closesocket(sock); + WSACleanup(); +#endif +} + +int set_socket_option(int socket, int level, int optname, const void* optval, int 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)); + } +#ifdef _WIN32 + if (ret < 0) { + int err = WSAGetLastError(); + printf("WSAGetLastError: %d\n", err); + } +#endif + return ret; +} + +int set_socket_timeout(int socket, long microSeconds) { +#ifdef __linux + struct timeval timeout = {}; + timeout.tv_sec = 0; + timeout.tv_usec = microSeconds; + int ret = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (struct timeval*)&timeout, sizeof(timeout)); + if (ret < 0) { + printf("Failed to setsockopt(%d, %d, %d, _, _) - Error: %s\n", socket, SOL_SOCKET, SO_RCVTIMEO, + strerror(errno)); + } + return ret; +#elif _WIN32 + DWORD timeout = microSeconds / 1000; // milliseconds + return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); +#endif +} + +int write_to_socket(int socket, const char* buf, int len) { +#ifdef __linux + return write(socket, buf, len); +#elif _WIN32 + return send(socket, buf, len, 0); +#endif +} + +int read_from_socket(int socket, char* buf, int len) { +#ifdef __linux + return read(socket, buf, len); +#elif _WIN32 + return recv(socket, buf, len, 0); +#endif +} + +bool socket_timed_out() { +#ifdef __linux + return errno == EAGAIN; +#elif _WIN32 + auto err = WSAGetLastError(); + return err == WSAETIMEDOUT; +#endif +} \ No newline at end of file diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h new file mode 100644 index 0000000000..d0e4bb1c16 --- /dev/null +++ b/common/cross_sockets/xsocket.h @@ -0,0 +1,21 @@ +#ifdef __linux +#include +#include +#include +#elif _WIN32 +#include +#endif + +#ifdef __linux +const int TCP_SOCKET_LEVEL = SOL_TCP; +#elif _WIN32 +const int TCP_SOCKET_LEVEL = IPPROTO_TCP; +#endif + +int open_socket(int af, int type, int protocol); +void close_socket(int sock); +int set_socket_option(int socket, int level, int optname, const void* optval, int optlen); +int set_socket_timeout(int socket, long microSeconds); +int write_to_socket(int socket, const char* buf, int len); +int read_from_socket(int socket, char* buf, int len); +bool socket_timed_out(); \ No newline at end of file diff --git a/common/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/CMakeLists.txt b/game/CMakeLists.txt index 59cacdf7af..d73e4968fe 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -1,5 +1,5 @@ # We define our own compilation flags here. -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) # Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. @@ -76,11 +76,10 @@ add_executable(gk ${RUNTIME_SOURCE} main.cpp) # can be used to test other things. add_library(runtime ${RUNTIME_SOURCE}) - IF (WIN32) # set stuff for windows - target_link_libraries(gk mman) + target_link_libraries(gk cross_sockets mman) ELSE() # set stuff for other systems - target_link_libraries(gk pthread) + target_link_libraries(gk cross_sockets pthread) ENDIF() 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 9f6f6de06d..1b54215ada 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -5,6 +5,9 @@ */ #include +#include +#include + #include "common/common_types.h" #include "game/sce/libscf.h" #include "kboot.h" @@ -12,6 +15,7 @@ #include "kscheme.h" #include "ksocket.h" #include "klisten.h" +#include "kprint.h" #ifdef _WIN32 #include "Windows.h" @@ -133,22 +137,36 @@ 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-WINDOWS + + // 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__ - ClearPending(); + 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. if (MasterDebug && ListenerFunction->value != old_listener) { SendAck(); } -#ifdef _WIN32 - Sleep(1000); // todo - remove this -#elif __linux__ - usleep(1000); -#endif + std::this_thread::sleep_for(std::chrono::microseconds(1000)); } } 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/kdsnetm.cpp b/game/kernel/kdsnetm.cpp index 2d06a93160..da07a7a8bb 100644 --- a/game/kernel/kdsnetm.cpp +++ b/game/kernel/kdsnetm.cpp @@ -27,8 +27,6 @@ void kdsnetm_init_globals() { protoBlock.reset(); } -// TODO-WINDOWS -#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -222,4 +220,3 @@ void GoalProtoStatus() { Msg(6, "gproto: got %d %d\n", protoBlock.most_recent_event, protoBlock.most_recent_param); Msg(6, "gproto: %d %d\n", protoBlock.last_receive_size, protoBlock.send_remaining); } -#endif \ No newline at end of file diff --git a/game/kernel/kdsnetm.h b/game/kernel/kdsnetm.h index 51125e0c1f..bdbef66096 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/kdsnetm.h @@ -51,8 +51,6 @@ extern GoalProtoBlock protoBlock; */ void kdsnetm_init_globals(); -// TODO-WINDOWS -#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -65,8 +63,6 @@ void InitGoalProto(); */ void ShutdownGoalProto(); -#endif - /*! * Handle a DECI2 Protocol Event for the GOAL Proto. * Called by the DECI2 Protocol driver @@ -74,8 +70,6 @@ void ShutdownGoalProto(); */ void GoalProtoHandler(int event, int param, void* data); -// TODO-WINDOWS -#ifdef __linux__ /*! * Low level DECI2 send * Will block until send is complete. @@ -83,7 +77,6 @@ void GoalProtoHandler(int event, int param, void* data); * removed */ s32 SendFromBufferD(s32 p1, u64 p2, char* data, s32 size); -#endif /*! * Print GOAL Protocol status diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index 7f990bf27e..322d049e33 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -71,10 +71,7 @@ void ClearPending() { Ptr msg = OutputBufArea.cast() + sizeof(GoalMessageHeader); auto size = strlen(msg.c()); // note - if size is ever greater than 2^16 this will cause an issue. - // TODO-WINDOWS -#ifdef __linux__ SendFromBuffer(msg.c(), size); -#endif clear_output(); } @@ -87,10 +84,7 @@ void ClearPending() { if (send_size > 64000) { send_size = 64000; } -// TODO-WINDOWS -#ifdef __linux__ SendFromBufferD(2, 0, msg, send_size); -#endif size -= send_size; msg += send_size; } @@ -109,11 +103,9 @@ void ClearPending() { */ void SendAck() { if (MasterDebug) { -#ifdef __linux__ SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, AckBufArea + sizeof(GoalMessageHeader), strlen(AckBufArea + sizeof(GoalMessageHeader))); -#endif } } @@ -141,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"); @@ -161,4 +156,4 @@ void ProcessListenerMessage(Ptr msg) { break; } SendAck(); -} \ No newline at end of file +} diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 4752b90b93..f5b611b364 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -329,10 +329,7 @@ int InitMachine() { // } if (MasterDebug) { // connect to GOAL compiler -// TODO-WINDOWS -#ifdef __linux__ InitGoalProto(); -#endif } printf("InitSound\n"); @@ -362,10 +359,7 @@ int ShutdownMachine() { StopIOP(); CloseListener(); ShutdownSound(); -// TODO-WINDOWS -#ifdef __linux__ ShutdownGoalProto(); -#endif Msg(6, "kernel: machine shutdown"); return 0; } @@ -601,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/kernel/ksocket.cpp b/game/kernel/ksocket.cpp index b66fc1dd7e..4eb0194017 100644 --- a/game/kernel/ksocket.cpp +++ b/game/kernel/ksocket.cpp @@ -52,8 +52,6 @@ u32 ReceiveToBuffer(char* buff) { return msg_size; } -// TODO-WINDOWS -#ifdef __linux__ /*! * Do a DECI2 send and block until it is complete. * The message type is OUTPUT @@ -62,7 +60,6 @@ u32 ReceiveToBuffer(char* buff) { s32 SendFromBuffer(char* buff, s32 size) { return SendFromBufferD(u16(ListenerMessageKind::MSG_OUTPUT), 0, buff, size); } -#endif /*! * Just prepare the Ack buffer, doesn't actually connect. 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 379898a9a1..160ca28cbc 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -12,7 +12,9 @@ #include #endif +#include #include +#include #include "runtime.h" #include "system/SystemThread.h" @@ -45,11 +47,6 @@ u8* g_ee_main_mem = nullptr; -/*! - * TODO-WINDOWS - * runtime.cpp - Deci2Listener has been disabled for now, pending rewriting for Windows. - */ - namespace { /*! @@ -57,8 +54,6 @@ namespace { */ void deci2_runner(SystemThreadInterface& iface) { -// TODO-WINDOWS -#ifdef __linux__ // callback function so the server knows when to give up and shutdown std::function shutdown_callback = [&]() { return iface.get_want_exit(); }; @@ -89,10 +84,9 @@ void deci2_runner(SystemThreadInterface& iface) { server.run(); } else { // no connection yet. Do a sleep so we don't spam checking the listener. - usleep(50000); + std::this_thread::sleep_for(std::chrono::microseconds(50000)); } } -#endif } // EE System @@ -229,17 +223,14 @@ 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; // step 1: sce library prep iop::LIBRARY_INIT(); ee::LIBRARY_INIT_sceCd(); -// TODO-WINDOWS -#ifdef __linux__ ee::LIBRARY_INIT_sceDeci2(); -#endif ee::LIBRARY_INIT_sceSif(); // step 2: system prep @@ -262,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/sce/deci2.cpp b/game/sce/deci2.cpp index 6cb5a84058..99b12cbcb3 100644 --- a/game/sce/deci2.cpp +++ b/game/sce/deci2.cpp @@ -12,14 +12,11 @@ namespace ee { namespace { -// TODO-WINDOWS -#ifdef __linux__ constexpr int MAX_DECI2_PROTOCOLS = 4; Deci2Driver protocols[MAX_DECI2_PROTOCOLS]; // info for each deci2 protocol registered int protocol_count; // number of registered protocols Deci2Driver* sending_driver; // currently sending protocol driver ::Deci2Server* server; // the server to send data to -#endif } // namespace @@ -27,8 +24,6 @@ Deci2Driver* sending_driver; // currently sending protocol drive * Initialize the library. */ void LIBRARY_INIT_sceDeci2() { -// TODO-WINDOWS -#ifdef __linux__ // reset protocols for (auto& p : protocols) { p = Deci2Driver(); @@ -36,15 +31,12 @@ void LIBRARY_INIT_sceDeci2() { protocol_count = 0; server = nullptr; sending_driver = nullptr; -#endif } /*! * Run any pending requested sends. */ void LIBRARY_sceDeci2_run_sends() { -// TODO-WINDOWS -#ifdef __linux__ for (auto& prot : protocols) { if (prot.active && prot.pending_send == 'H') { sending_driver = &prot; @@ -54,17 +46,13 @@ void LIBRARY_sceDeci2_run_sends() { (prot.handler)(DECI2_WRITEDONE, 0, prot.opt); } } -#endif } /*! * Register a Deci2Server with this library. */ void LIBRARY_sceDeci2_register(::Deci2Server* s) { -// TODO-WINDOWS -#ifdef __linux__ server = s; -#endif } /*! @@ -73,8 +61,6 @@ void LIBRARY_sceDeci2_register(::Deci2Server* s) { * I don't know why it's like this. */ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, void* opt)) { -// TODO-WINDOWS -#ifdef __linux__ server->lock(); Deci2Driver drv; drv.protocol = protocol; @@ -93,20 +79,14 @@ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, } return drv.id; -#elif _WIN32 - return 0; -#endif } /*! * Deactivate a DECI2 protocol by socket descriptor. */ s32 sceDeci2Close(s32 s) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].active = false; -#endif return 1; } @@ -114,12 +94,9 @@ s32 sceDeci2Close(s32 s) { * Start a send. */ s32 sceDeci2ReqSend(s32 s, char dest) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); auto& proto = protocols[s - 1]; proto.pending_send = dest; -#endif return 0; } @@ -128,8 +105,6 @@ s32 sceDeci2ReqSend(s32 s, char dest) { * Returns after data is copied. */ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].recv_size = len; auto avail = protocols[s - 1].available_to_receive; @@ -140,17 +115,12 @@ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { printf("[DECI2] Error: ExRecv %d, only %d available!\n", len, avail); return -1; } -#elif _WIN32 - return 0; -#endif } /*! * Do a send. */ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); if (!sending_driver) { printf("sceDeci2ExSend called at illegal time!\n"); @@ -162,8 +132,5 @@ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { server->send_data(buf, len); return len; -#elif _WIN32 - return 0; -#endif } } // namespace ee diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 600f34462e..ca7f1b0f63 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -4,15 +4,22 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ -// TODO-WINDOWS -#ifdef __linux__ - #include +#include +#include + +// TODO - i think im not including the dependency right..? +#include "common/cross_sockets/xsocket.h" + +#ifdef __linux #include #include #include -#include -#include +#elif _WIN32 +#include +#include +#include +#endif #include "common/listener_common.h" #include "common/versions.h" @@ -33,67 +40,58 @@ Deci2Server::~Deci2Server() { delete[] buffer; - if (server_fd >= 0) { - close(server_fd); - } - - if (new_sock >= 0) { - close(new_sock); - } + close_server_socket(); + close_socket(new_sock); } /*! * Start waiting for the Listener to connect */ bool Deci2Server::init() { - server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) { - server_fd = -1; + server_socket = open_socket(AF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { + server_socket = -1; return false; } +#ifdef __linux + int server_socket_opt = SO_REUSEADDR | SO_REUSEPORT; +#elif _WIN32 + int server_socket_opt = SO_EXCLUSIVEADDRUSE; +#endif + int opt = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { - printf("[Deci2Server] Failed to setsockopt 1\n"); - close(server_fd); - server_fd = -1; + if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, &opt, sizeof(opt)) < 0) { + close_server_socket(); return false; } + printf("[Deci2Server] Created Socket Options\n"); - int one = 1; - if (setsockopt(server_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) { - printf("[Deci2Server] Failed to setsockopt 2\n"); - close(server_fd); - server_fd = -1; + if (set_socket_option(server_socket, TCP_SOCKET_LEVEL, TCP_NODELAY, &opt, sizeof(opt)) < 0) { + close_server_socket(); return false; } + printf("[Deci2Server] Created TCP Socket Options\n"); - timeval timeout = {}; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - - if (setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { - printf("[Deci2Server] Failed to setsockopt 3\n"); - close(server_fd); - server_fd = -1; + if (set_socket_timeout(server_socket, 100000) < 0) { + close_server_socket(); return false; } + printf("[Deci2Server] Created Socket Timeout\n"); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(DECI2_PORT); - if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { + if (bind(server_socket, (sockaddr*)&addr, sizeof(addr)) < 0) { printf("[Deci2Server] Failed to bind\n"); - close(server_fd); - server_fd = -1; + close_server_socket(); return false; } - if (listen(server_fd, 0) < 0) { + if (listen(server_socket, 0) < 0) { printf("[Deci2Server] Failed to listen\n"); - close(server_fd); - server_fd = -1; + close_server_socket(); return false; } @@ -104,6 +102,11 @@ bool Deci2Server::init() { return true; } +void Deci2Server::close_server_socket() { + close_socket(server_socket); + server_socket = -1; +} + /*! * Return true if the listener is connected. */ @@ -129,7 +132,7 @@ void Deci2Server::send_data(void* buf, u16 len) { } else { uint16_t prog = 0; while (prog < len) { - auto wrote = write(new_sock, (char*)(buf) + prog, len - prog); + int wrote = write_to_socket(new_sock, (char*)(buf) + prog, len - prog); prog += wrote; if (!server_connected || want_exit()) { unlock(); @@ -186,7 +189,7 @@ void Deci2Server::run() { while (got < desired_size) { assert(got + desired_size < BUFFER_SIZE); - auto x = read(new_sock, buffer + got, desired_size - got); + auto x = read_from_socket(new_sock, buffer + got, desired_size - got); if (want_exit()) { return; } @@ -237,7 +240,7 @@ void Deci2Server::run() { // receive from network if (hdr->rsvd < hdr->len) { - auto x = read(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd); + auto x = read_from_socket(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd); if (want_exit()) { return; } @@ -256,13 +259,15 @@ void Deci2Server::run() { void Deci2Server::accept_thread_func() { socklen_t l = sizeof(addr); while (!kill_accept_thread) { - new_sock = accept(server_fd, (sockaddr*)&addr, &l); + // TODO - might want to do a WSAStartUp call here as well, else it won't be balanced on the + // 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}; - send(new_sock, &versions, 8, 0); // todo, check result? + write_to_socket(new_sock, (char*)&versions, 8); // todo, check result? server_connected = true; return; } } } -#endif \ No newline at end of file diff --git a/game/system/Deci2Server.h b/game/system/Deci2Server.h index 8695ff020d..c8ddbaa1da 100644 --- a/game/system/Deci2Server.h +++ b/game/system/Deci2Server.h @@ -4,11 +4,14 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ -#ifdef __linux__ #ifndef JAK1_DECI2SERVER_H #define JAK1_DECI2SERVER_H +#ifdef __linux #include +#elif _WIN32 +#include +#endif #include #include #include @@ -32,11 +35,12 @@ class Deci2Server { void run(); private: + void close_server_socket(); void accept_thread_func(); bool kill_accept_thread = false; char* buffer = nullptr; - int server_fd = -1; - sockaddr_in addr; + int server_socket = -1; + struct sockaddr_in addr = {}; int new_sock = -1; bool server_initialized = false; bool accept_thread_running = false; @@ -52,4 +56,3 @@ class Deci2Server { }; #endif // JAK1_DECI2SERVER_H -#endif 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 21b3fcb92c..2e8ab64256 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,12 +1,6 @@ add_subdirectory(util) add_subdirectory(goos) - -IF (WIN32) - # TODO - implement windows listener - message("Windows Listener Not Implemented!") -ELSE() - add_subdirectory(listener) -ENDIF() +add_subdirectory(listener) add_library(compiler SHARED @@ -19,19 +13,25 @@ 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 regalloc/allocate.cpp - compiler/Compiler.cpp - ) + compiler/Compiler.cpp) -add_executable(goalc main.cpp - ) +add_executable(goalc main.cpp) IF (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) - target_link_libraries(compiler util goos type_system mman) -ENDIF() + target_link_libraries(compiler util goos type_system listener mman) + +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 f978b4d633..4cc5c90330 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,2 +1,7 @@ -add_library(listener SHARED - Listener.cpp) +add_library(listener SHARED Listener.cpp) + +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 2a53f26612..1fdb4bbe3d 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -1,20 +1,38 @@ /*! * @file Listener.cpp * The Listener can connect to a Deci2Server for debugging. + * + * TODO - msg ID? */ -// TODO-Windows #ifdef __linux__ - #include #include #include #include +#elif _WIN32 +#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..? +#include "common/cross_sockets/xsocket.h" + +#include #include +#include +#include +#include #include "Listener.h" #include "common/versions.h" using namespace versions; +constexpr bool debug_listener = false; namespace listener { Listener::Listener() { @@ -26,8 +44,8 @@ Listener::~Listener() { disconnect(); delete[] m_buffer; - if (socket_fd >= 0) { - close(socket_fd); + if (listen_socket >= 0) { + close_socket(listen_socket); } } @@ -47,40 +65,35 @@ 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 (socket_fd >= 0) { - close(socket_fd); + if (listen_socket >= 0) { + close_socket(listen_socket); } // construct socket - socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd < 0) { + listen_socket = open_socket(AF_INET, SOCK_STREAM, 0); + if (listen_socket < 0) { printf("[Listener] Failed to create socket.\n"); - socket_fd = -1; + listen_socket = -1; return false; } - // set timeout for receive - timeval timeout = {}; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { - printf("[Listener] setsockopt failed\n"); - close(socket_fd); - socket_fd = -1; + if (set_socket_timeout(listen_socket, 500000) < 0) { + close_socket(listen_socket); + listen_socket = -1; return false; } // set nodelay, which makes small rapid messages faster, but large messages slower int one = 1; - if (setsockopt(socket_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) { - printf("[Listener] failed to TCP_NODELAY\n"); - close(socket_fd); - socket_fd = -1; + if (set_socket_option(listen_socket, TCP_SOCKET_LEVEL, TCP_NODELAY, &one, sizeof(one))) { + close_socket(listen_socket); + listen_socket = -1; return false; } @@ -90,18 +103,27 @@ bool Listener::connect_to_target(const std::string& ip, int port) { server_address.sin_port = htons(port); if (inet_pton(AF_INET, ip.c_str(), &server_address.sin_addr) <= 0) { printf("[Listener] Invalid IP address.\n"); - close(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } // connect! - int rv = connect(socket_fd, (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_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; + } else { + printf("[Listener] Socket connected established! (took %d tries). Waiting for version...\n", i); } // get the GOAL version number, to make sure we connected to the right thing @@ -110,12 +132,9 @@ bool Listener::connect_to_target(const std::string& ip, int port) { int prog = 0; bool ok = true; while (prog < 8) { - auto r = read(socket_fd, version_buffer + prog, 8 - prog); - if (r < 0) { - ok = false; - break; - } - prog += r; + auto r = read_from_socket(listen_socket, (char*)version_buffer + prog, 8 - prog); + std::this_thread::sleep_for(std::chrono::microseconds(100000)); + prog += r > 0 ? r : 0; read_tries++; if (read_tries > 50) { ok = false; @@ -124,8 +143,8 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } if (!ok) { printf("[Listener] Failed to get version number\n"); - close(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } @@ -138,8 +157,8 @@ bool Listener::connect_to_target(const std::string& ip, int port) { return true; } else { printf(", expected %d.%d. Cannot connect.\n", GOAL_VERSION_MAJOR, GOAL_VERSION_MINOR); - close(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } } @@ -155,11 +174,11 @@ void Listener::receive_func() { int rcvd_desired = sizeof(ListenerMessageHeader); char buff[sizeof(ListenerMessageHeader)]; while (rcvd < rcvd_desired) { - auto got = read(socket_fd, buff + rcvd, rcvd_desired - rcvd); + auto got = read_from_socket(listen_socket, buff + rcvd, rcvd_desired - rcvd); rcvd += got > 0 ? got : 0; // kick us out if we got a bogus read result - if (got == 0 || (got == -1 && errno != EAGAIN)) { + if (got == 0 || (got == -1 && !socket_timed_out())) { m_connected = false; } @@ -188,7 +207,8 @@ void Listener::receive_func() { while (rcvd < hdr->deci2_header.len) { if (!m_connected) return; - int got = read(socket_fd, ack_recv_buff + ack_recv_prog, hdr->deci2_header.len - rcvd); + int got = read_from_socket(listen_socket, ack_recv_buff + ack_recv_prog, + hdr->deci2_header.len - rcvd); got = got > 0 ? got : 0; rcvd += got; ack_recv_prog += got; @@ -210,11 +230,12 @@ void Listener::receive_func() { return; } - int got = read(socket_fd, str_buff + msg_prog, hdr->deci2_header.len - rcvd); + int got = + read_from_socket(listen_socket, str_buff + msg_prog, hdr->deci2_header.len - rcvd); got = got > 0 ? got : 0; rcvd += got; msg_prog += got; - if (got == 0 || (got == -1 && errno != EAGAIN)) { + if (got == 0 || (got == -1 && !socket_timed_out())) { m_connected = false; } } @@ -241,5 +262,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 -#endif diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 5ae49e2f5f..0c16d3028a 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -19,17 +19,26 @@ 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? - int socket_fd = -1; //! socket + int listen_socket = -1; //! socket bool got_ack = false; bool waiting_for_ack = false; 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-cov.sh b/test-cov.sh new file mode 100644 index 0000000000..f5b98c515a --- /dev/null +++ b/test-cov.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Directory of this script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt +cd $DIR/build/test +make init +make gcov +make lcov \ No newline at end of file diff --git a/test.sh b/test.sh index 6370a8a2db..573dafc226 100755 --- a/test.sh +++ b/test.sh @@ -5,4 +5,4 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR export FAKE_ISO_PATH=/game/fake_iso.txt -$DIR/build/test/goalc-test --gtest_color=yes "$@" \ No newline at end of file +$DIR/build/test/goalc-test --gtest_color=yes "$@" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 55f1ea2f17..faf41a4da6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,3 @@ -enable_testing() - add_executable(goalc-test test_main.cpp test_test.cpp @@ -14,13 +12,26 @@ add_executable(goalc-test test_emitter_loads_and_store.cpp test_emitter_xmm32.cpp test_emitter_integer_math.cpp - test_common_util.cpp) + test_common_util.cpp + test_compiler_and_runtime.cpp + ) + +enable_testing() IF (WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - # TODO - implement windows listener - message("Windows Listener Not Implemented!") - target_link_libraries(goalc-test mman goos util common_util runtime compiler type_system gtest) + + # TODO - split out these declarations for platform specific includes + target_link_libraries(goalc-test cross_sockets listener mman goos util common_util runtime compiler type_system gtest) ELSE() - target_link_libraries(goalc-test goos util common_util listener runtime compiler type_system gtest) + target_link_libraries(goalc-test cross_sockets goos util common_util listener runtime compiler type_system gtest) ENDIF() + +if(CMAKE_COMPILER_IS_GNUCXX AND CODE_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags() + setup_target_for_coverage_lcov(NAME goalc-test_coverage + EXECUTABLE goalc-test --gtest_color=yes + DEPENDENCIES goalc-test + EXCLUDE "third-party/*" "/usr/include/*") +endif() 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 d7b65cba2a..ebc5aedeab 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -1,5 +1,3 @@ -#ifdef __linux__ - #include "gtest/gtest.h" #include "goalc/listener/Listener.h" #include "game/system/Deci2Server.h" @@ -25,12 +23,6 @@ TEST(Listener, DeciInit) { EXPECT_TRUE(s.init()); } -// TEST(Listener, TwoDeciServers) { -// Deci2Server s1, s2; -// EXPECT_TRUE(s1.init()); -// EXPECT_TRUE(s2.init()); -//} - /*! * Try to connect when no Deci2Server is running */ @@ -53,8 +45,26 @@ TEST(Listener, DeciCheckNoListener) { EXPECT_FALSE(s.check_for_listener()); } +TEST(Listener, CheckConnectionStaysAlive) { + Deci2Server s(always_false); + EXPECT_TRUE(s.init()); + EXPECT_FALSE(s.check_for_listener()); + Listener l; + EXPECT_FALSE(s.check_for_listener()); + bool connected = l.connect_to_target(); + EXPECT_TRUE(connected); + // TODO - some sort of backoff and retry would be better + while (connected && !s.check_for_listener()) { + } + + EXPECT_TRUE(s.check_for_listener()); + std::this_thread::sleep_for(std::chrono::seconds(2)); // sorry for making tests slow. + EXPECT_TRUE(s.check_for_listener()); + EXPECT_TRUE(l.is_connected()); +} + 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()); @@ -63,10 +73,10 @@ TEST(Listener, DeciThenListener) { Listener l; EXPECT_FALSE(s.check_for_listener()); EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); - // kind of a hack. - while (!s.check_for_listener()) { - // printf("...\n"); + bool connected = l.connect_to_target(); + EXPECT_TRUE(connected); + // TODO - some sort of backoff and retry would be better + while (connected && !s.check_for_listener()) { } EXPECT_TRUE(s.check_for_listener()); @@ -74,7 +84,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()); @@ -88,43 +98,16 @@ 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); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); - while (!s.check_for_listener()) { - // printf("...\n"); + bool connected = l.connect_to_target(); + EXPECT_TRUE(connected); + // TODO - some sort of backoff and retry would be better + while (connected && !s.check_for_listener()) { } } } - -TEST(Listener, ListenerMultipleDecis) { - Listener l; - EXPECT_FALSE(l.connect_to_target()); - { - Deci2Server s(always_false); - EXPECT_TRUE(s.init()); - EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); - while (!s.check_for_listener()) { - // printf("...\n"); - } - l.disconnect(); - } - - { - Deci2Server s(always_false); - EXPECT_TRUE(s.init()); - EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); - while (!s.check_for_listener()) { - // printf("...\n"); - } - l.disconnect(); - } -} - -#endif \ No newline at end of file diff --git a/test_code_coverage.sh b/test_code_coverage.sh new file mode 100755 index 0000000000..482651ed02 --- /dev/null +++ b/test_code_coverage.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Directory of this script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt +cd $DIR/build/ +make goalc-test_coverage