Merge branch 'master' into master

This commit is contained in:
blahpy
2020-09-11 09:18:12 +12:00
committed by GitHub
87 changed files with 3722 additions and 364 deletions
+9 -4
View File
@@ -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
+29 -14
View File
@@ -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 ()
+1 -4
View File
@@ -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?)
+436
View File
@@ -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
+12
View File
@@ -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()
+97
View File
@@ -0,0 +1,97 @@
#ifdef __linux
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#elif _WIN32
#define WIN32_LEAN_AND_MEAN
#include <WinSock2.h>
#include <WS2tcpip.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <string.h>
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
}
+21
View File
@@ -0,0 +1,21 @@
#ifdef __linux
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#elif _WIN32
#include <WinSock2.h>
#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();
+10 -6
View File
@@ -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?
+74
View File
@@ -0,0 +1,74 @@
#ifndef JAK_BINARYWRITER_H
#define JAK_BINARYWRITER_H
#include <cassert>
#include <stdexcept>
#include <vector>
#include <cstdint>
#include <cstring>
struct BinaryWriterRef {
size_t offset;
size_t write_size;
};
class BinaryWriter {
public:
BinaryWriter() = default;
template <typename T>
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 <typename T>
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<uint8_t> data;
};
#endif // JAK_BINARYWRITER_H
+19
View File
@@ -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
+3 -4
View File
@@ -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()
+4 -4
View File
@@ -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
+26 -8
View File
@@ -5,6 +5,9 @@
*/
#include <cstring>
#include <chrono>
#include <thread>
#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<Function>(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<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
} else {
if (ListenerFunction->value != s7.offset) {
auto cptr = Ptr<u8>(ListenerFunction->value).c();
for (int i = 0; i < 40; i++) {
printf("%x ", cptr[i]);
}
printf("\n");
auto result =
call_goal(Ptr<Function>(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));
}
}
+2
View File
@@ -73,4 +73,6 @@ void KernelCheckAndDispatch();
*/
void KernelShutdown();
constexpr bool MasterUseKernel = false;
#endif // RUNTIME_KBOOT_H
+2 -2
View File
@@ -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");
-3
View File
@@ -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
-7
View File
@@ -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
+4 -9
View File
@@ -71,10 +71,7 @@ void ClearPending() {
Ptr<char> msg = OutputBufArea.cast<char>() + 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<char> 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<char> msg) {
break;
}
SendAck();
}
}
+2 -7
View File
@@ -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,
+24 -19
View File
@@ -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<Function> 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.
-3
View File
@@ -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.
+3 -1
View File
@@ -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;
}
+5 -3
View File
@@ -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;
}
+6 -14
View File
@@ -12,7 +12,9 @@
#include <Windows.h>
#endif
#include <chrono>
#include <cstring>
#include <thread>
#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<bool()> 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;
}
+1 -1
View File
@@ -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
-33
View File
@@ -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
+49 -44
View File
@@ -4,15 +4,22 @@
* Works with deci2.cpp (sceDeci2) to implement the networking on target
*/
// TODO-WINDOWS
#ifdef __linux__
#include <cstdio>
#include <cassert>
#include <utility>
// TODO - i think im not including the dependency right..?
#include "common/cross_sockets/xsocket.h"
#ifdef __linux
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <cassert>
#include <utility>
#elif _WIN32
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#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
+7 -4
View File
@@ -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 <netinet/in.h>
#elif _WIN32
#include <Windows.h>
#endif
#include <thread>
#include <mutex>
#include <condition_variable>
@@ -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
+53
View File
@@ -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)
)
)
@@ -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
)
)
@@ -0,0 +1,5 @@
(defglobalconstant my-constant 12)
(defglobalconstant my-constant 17)
(defglobalconstant not-my-consant 13)
my-constant
@@ -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)
+10
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
'#f
+1
View File
@@ -0,0 +1 @@
'#t
+9
View File
@@ -0,0 +1,9 @@
(block my-block
1
(goto skip-early-return)
(return-from my-block 2)
(label skip-early-return)
3
)
+11
View File
@@ -0,0 +1,11 @@
(block outer-block
1
2
(block inner-block
3
4
(return-from inner-block 7)
5
6
)
)
+11
View File
@@ -0,0 +1,11 @@
(block outer-block
1
2
(block inner-block
3
4
(return-from inner-block 7)
5
)
8
)
+11
View File
@@ -0,0 +1,11 @@
(block outer-block
1
2
(block inner-block
3
4
(return-from outer-block 7)
5
)
8
)
+2
View File
@@ -0,0 +1,2 @@
; simply return an integer
#x123456789
+1
View File
@@ -0,0 +1 @@
#x17
+1
View File
@@ -0,0 +1 @@
-17
+1
View File
@@ -0,0 +1 @@
-2147483648
+1
View File
@@ -0,0 +1 @@
-2147483649
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
-123
+14 -14
View File
@@ -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)
+116 -2
View File
@@ -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<u8> 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());
}
+13 -1
View File
@@ -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<u8> run();
private:
void do_function(FunctionEnv* env);
emitter::ObjectGenerator m_gen;
FileEnv* m_fe;
};
#endif // JAK_CODEGENERATOR_H
+187 -2
View File
@@ -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 <chrono>
#include <thread>
using namespace goos;
Compiler::Compiler() {
init_logger();
m_ts.add_builtin_types();
m_global_env = std::make_unique<GlobalEnv>();
m_none = std::make_unique<None>(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<FunctionEnv> Compiler::compile_top_level_function(const std::string& name,
const goos::Object& code,
Env* env) {
auto fe = std::make_unique<FunctionEnv>(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<const None*>(result)) {
fe->emit(std::make_unique<IR_Return>(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<u8> Compiler::codegen_object_file(FileEnv* env) {
CodeGenerator gen(env);
return gen.run();
}
std::vector<std::string> 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);
}
+85
View File
@@ -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<FunctionEnv> 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<std::string> 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<u8> codegen_object_file(FileEnv* env);
void for_each_in_list(const goos::Object& list,
const std::function<void(const goos::Object&)>& 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<util::MatchParam<goos::ObjectType>>& unnamed,
const std::unordered_map<std::string, std::pair<bool, util::MatchParam<goos::ObjectType>>>&
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<GlobalEnv> m_global_env = nullptr;
std::unique_ptr<None> m_none = nullptr;
bool m_want_exit = false;
listener::Listener m_listener;
goos::Interpreter m_goos;
std::unordered_map<std::string, TypeSpec> m_symbol_types;
std::unordered_map<std::shared_ptr<goos::SymbolObject>, goos::Object> m_global_constants;
std::unordered_map<std::shared_ptr<goos::SymbolObject>, 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
+231 -2
View File
@@ -1,3 +1,232 @@
#include <stdexcept>
#include "Env.h"
#include "IR.h"
///////////////////
// Env
///////////////////
/*!
* Emit IR into the function currently being compiled.
*/
void Env::emit(std::unique_ptr<IR> 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<std::string, Label>& 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> 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<FileEnv>(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> 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<FunctionEnv> fe) {
m_functions.push_back(std::move(fe));
}
void FileEnv::add_top_level_function(std::unique_ptr<FunctionEnv> 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<NoEmitEnv>(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) {
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<RegVal>(ireg, ts);
m_iregs.push_back(std::move(rv));
return m_iregs.back().get();
}
std::unordered_map<std::string, Label>& FunctionEnv::get_label_map() {
return m_labels;
}
std::unordered_map<std::string, Label>& LabelEnv::get_label_map() {
return m_labels;
}
+219 -6
View File
@@ -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 <string>
#include <memory>
#include <vector>
#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> 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<std::string, Label>& 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> 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<std::unique_ptr<FileEnv>> 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> 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<FunctionEnv> fe);
void add_top_level_function(std::unique_ptr<FunctionEnv> fe);
NoEmitEnv* add_no_emit_env();
void debug_print_tl();
const std::vector<std::unique_ptr<FunctionEnv>>& functions() { return m_functions; }
bool is_empty();
~FileEnv() = default;
protected:
std::string m_name;
std::vector<std::unique_ptr<FunctionEnv>> m_functions;
std::unique_ptr<NoEmitEnv> 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<std::string, Label>& get_label_map() override;
void set_segment(int seg) { segment = seg; }
void emit(std::unique_ptr<IR> ir) override;
void finish();
RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override;
const std::vector<std::unique_ptr<IR>>& code() { return m_code; }
int max_vars() const { return m_iregs.size(); }
const std::vector<IRegConstraint>& 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 <typename T, class... Args>
T* alloc_val(Args&&... args) {
std::unique_ptr<T> new_obj = std::make_unique<T>(std::forward<Args>(args)...);
m_vals.push_back(std::move(new_obj));
return (T*)m_vals.back().get();
}
template <typename T, class... Args>
T* alloc_env(Args&&... args) {
std::unique_ptr<T> new_obj = std::make_unique<T>(std::forward<Args>(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<UnresolvedGoto> unresolved_gotos;
protected:
void resolve_gotos();
std::string m_name;
std::vector<std::unique_ptr<IR>> m_code;
std::vector<std::unique_ptr<RegVal>> m_iregs;
std::vector<std::unique_ptr<Val>> m_vals;
std::vector<std::unique_ptr<Env>> m_envs;
std::vector<IRegConstraint> m_constraints;
// todo, unresolved gotos
AllocationResult m_regalloc_result;
bool m_is_asm_func = false;
bool m_aligned_stack_required = false;
std::unordered_map<std::string, Env*> m_params;
std::unordered_map<std::string, Label> 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<TypeSpec> return_types;
};
class LexicalEnv : public Env {
public:
LexicalEnv(Env* parent);
std::string print() override;
};
class LabelEnv : public Env {
public:
std::unordered_map<std::string, Label>& get_label_map() override;
protected:
std::unordered_map<std::string, Label> m_labels;
};
class WithInlineEnv : public Env {};
class SymbolMacroEnv : public Env {};
template <typename T>
T* get_parent_env_of_type(Env* in) {
for (;;) {
auto attempt = dynamic_cast<T*>(in);
if (attempt)
return attempt;
if (dynamic_cast<GlobalEnv*>(in)) {
return nullptr;
}
in = in->parent();
}
}
// function
// block
// lexical
+241 -2
View File
@@ -1,3 +1,242 @@
#include "IR.h"
#include <utility>
#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<IRegConstraint>* constraints, int my_id) {
IRegConstraint c;
if (dynamic_cast<const None*>(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;
}
+113 -2
View File
@@ -4,18 +4,129 @@
#include <string>
#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<IRegConstraint>* 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<IRegConstraint>* 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
+13
View File
@@ -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
+10
View File
@@ -0,0 +1,10 @@
#ifndef JAK_LAMBDA_H
#define JAK_LAMBDA_H
struct Lambda {
std::string debug_name;
};
#endif // JAK_LAMBDA_H
+120
View File
@@ -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<util::MatchParam<goos::ObjectType>>& unnamed,
const std::unordered_map<std::string, std::pair<bool, util::MatchParam<goos::ObjectType>>>&
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<void(const goos::Object&)>& 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");
}
}
+49 -7
View File
@@ -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<IR_LoadConstant64>(rv, m_value));
return rv;
}
RegVal* SymbolVal::to_reg(Env* fe) {
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_LoadSymbolPointer>(re, m_name));
return re;
}
RegVal* SymbolValueVal::to_reg(Env* fe) {
auto re = fe->make_gpr(m_ts);
fe->emit(std::make_unique<IR_GetSymbolValue>(re, m_sym, m_sext));
return re;
}
+65 -9
View File
@@ -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
+233
View File
@@ -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, &macro_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<FunctionEnv>(env);
return fe->alloc_val<IntegerConstantVal>(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<FunctionEnv>(env);
return fe->alloc_val<SymbolVal>(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<FunctionEnv>(env);
auto sym = fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
auto re = fe->alloc_val<SymbolValueVal>(sym, ts, sext);
return re;
}
+144
View File
@@ -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<FunctionEnv>(env);
// create environment
auto block_env = fe->alloc_env<BlockEnv>(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<IR_RegSet>(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<FunctionEnv>(env);
// find block to return from
auto block = dynamic_cast<BlockEnv*>(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<IR_RegSet>(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<IR_GotoLabel>(&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<FunctionEnv>(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<IR_GotoLabel>();
// 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<FunctionEnv>(env)->unresolved_gotos.push_back({ir_goto.get(), label_name});
env->emit(std::move(ir_goto));
return get_none();
}
@@ -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<std::pair<std::string, float>> 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();
}
+41
View File
@@ -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<FunctionEnv>(env);
auto sym_val = fe->alloc_val<SymbolVal>(symbol_string(sym), m_ts.make_typespec("symbol"));
auto compiled_val = compile_error_guard(val, env);
auto as_lambda = dynamic_cast<LambdaVal*>(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<IR_SetSymbolValue>(sym_val, in_gpr));
return in_gpr;
}
+117
View File
@@ -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<FunctionEnv>(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();
}
+5
View File
@@ -14,6 +14,11 @@ std::vector<u8> 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;
}
+15 -1
View File
@@ -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<u8> ObjectGenerator::generate_header_v3() {
offset += push_data<u32>(N_SEG, result);
offset += sizeof(u32) * N_SEG * 4; // 4 u32's per segment
offset += 4;
struct SizeOffset {
uint32_t offset, size;
};
+2
View File
@@ -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);
+1 -3
View File
@@ -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.
+10 -10
View File
@@ -23,6 +23,16 @@ class Interpreter {
Object eval(Object obj, const std::shared_ptr<EnvironmentObject>& env);
Object intern(const std::string& name);
void disable_printfs();
Object eval_symbol(const Object& sym, const std::shared_ptr<EnvironmentObject>& 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<EnvironmentObject>& env);
Object eval_list_return_last(const Object& form,
Object rest,
const std::shared_ptr<EnvironmentObject>& env);
bool truthy(const Object& o);
Reader reader;
Object global_environment;
@@ -47,14 +57,9 @@ class Interpreter {
const std::unordered_map<std::string, std::pair<bool, util::MatchParam<ObjectType>>>& named);
Object eval_pair(const Object& o, const std::shared_ptr<EnvironmentObject>& env);
Object eval_symbol(const Object& sym, const std::shared_ptr<EnvironmentObject>& env);
Arguments get_args(const Object& form, const Object& rest, const ArgumentSpec& spec);
void eval_args(Arguments* args, const std::shared_ptr<EnvironmentObject>& env);
ArgumentSpec parse_arg_spec(const Object& form, Object& rest);
Object eval_list_return_last(const Object& form,
Object rest,
const std::shared_ptr<EnvironmentObject>& env);
Object quasiquote_helper(const Object& form, const std::shared_ptr<EnvironmentObject>& env);
IntType number_to_integer(const Object& obj);
@@ -206,11 +211,6 @@ class Interpreter {
const Object& rest,
const std::shared_ptr<EnvironmentObject>& env);
void set_args_in_env(const Object& form,
const Arguments& args,
const ArgumentSpec& arg_spec,
const std::shared_ptr<EnvironmentObject>& env);
bool want_exit = false;
bool disable_printing = false;
+7 -2
View File
@@ -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 ()
+186 -44
View File
@@ -1,20 +1,38 @@
/*!
* @file Listener.cpp
* The Listener can connect to a Deci2Server for debugging.
*
* TODO - msg ID?
*/
// TODO-Windows
#ifdef __linux__
#include <stdexcept>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#elif _WIN32
#define WIN32_LEAN_AND_MEAN
#include <WinSock2.h>
#include <WS2tcpip.h>
// 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 <stdexcept>
#include <cassert>
#include <cstring>
#include <chrono>
#include <thread>
#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<std::string> Listener::stop_recording_messages() {
filter = ListenerMessageKind::MSG_INVALID;
auto result = message_record;
message_record.clear();
return result;
}
void Listener::send_code(std::vector<uint8_t>& 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
+12 -3
View File
@@ -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<std::string> stop_recording_messages();
bool is_connected() const;
void send_reset(bool shutdown);
void send_poke();
void disconnect();
void send_code(std::vector<uint8_t>& 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;
+1
View File
@@ -4,6 +4,7 @@
void Logger::close() {
if (fp) {
fclose(fp);
fp = nullptr;
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
#include <cstdio>
#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;
}
+5 -2
View File
@@ -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());
}
+1 -1
View File
@@ -1 +1 @@
add_library(util SHARED text_util.cpp file_io.cpp)
add_library(util SHARED text_util.cpp file_io.cpp Timer.cpp)
+54
View File
@@ -0,0 +1,54 @@
#include "Timer.h"
#ifdef _WIN32
#include <Windows.h>
#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);
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef JAK_V2_TIMER_H
#define JAK_V2_TIMER_H
#include <cassert>
#include <cstdint>
#include <ctime>
/*!
* 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
+13
View File
@@ -29,4 +29,17 @@ std::string combine_path(std::vector<std::string> 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
+1
View File
@@ -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<std::string> path);
void write_binary_file(const std::string& name, void* data, size_t size);
} // namespace util
#endif // JAK1_FILE_IO_H
+11
View File
@@ -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
+1 -1
View File
@@ -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 "$@"
$DIR/build/test/goalc-test --gtest_color=yes "$@"
+18 -7
View File
@@ -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()
+125
View File
@@ -0,0 +1,125 @@
#include <thread>
#include <chrono>
#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<std::string> expected, actual;
std::string test_name;
};
std::vector<Test> tests;
void run_test(const std::string& test_file, const std::vector<std::string>& 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();
}
+29 -46
View File
@@ -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
+9
View File
@@ -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