mirror of
https://github.com/open-goal/jak-project
synced 2026-07-09 14:55:51 -04:00
Merge branch 'master' into master
This commit is contained in:
@@ -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
@@ -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,5 +1,6 @@
|
||||
# Jak Project
|
||||

|
||||
[](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?)
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,4 +73,6 @@ void KernelCheckAndDispatch();
|
||||
*/
|
||||
void KernelShutdown();
|
||||
|
||||
constexpr bool MasterUseKernel = false;
|
||||
|
||||
#endif // RUNTIME_KBOOT_H
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
'#f
|
||||
@@ -0,0 +1 @@
|
||||
'#t
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
(block my-block
|
||||
1
|
||||
(goto skip-early-return)
|
||||
(return-from my-block 2)
|
||||
(label skip-early-return)
|
||||
3
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
(block outer-block
|
||||
1
|
||||
2
|
||||
(block inner-block
|
||||
3
|
||||
4
|
||||
(return-from inner-block 7)
|
||||
5
|
||||
6
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
(block outer-block
|
||||
1
|
||||
2
|
||||
(block inner-block
|
||||
3
|
||||
4
|
||||
(return-from inner-block 7)
|
||||
5
|
||||
)
|
||||
8
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
(block outer-block
|
||||
1
|
||||
2
|
||||
(block inner-block
|
||||
3
|
||||
4
|
||||
(return-from outer-block 7)
|
||||
5
|
||||
)
|
||||
8
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
; simply return an integer
|
||||
#x123456789
|
||||
@@ -0,0 +1 @@
|
||||
#x17
|
||||
@@ -0,0 +1 @@
|
||||
-17
|
||||
@@ -0,0 +1 @@
|
||||
-2147483648
|
||||
@@ -0,0 +1 @@
|
||||
-2147483649
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
-123
|
||||
+14
-14
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
#ifndef JAK_LAMBDA_H
|
||||
#define JAK_LAMBDA_H
|
||||
|
||||
struct Lambda {
|
||||
std::string debug_name;
|
||||
};
|
||||
|
||||
#endif // JAK_LAMBDA_H
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "goalc/compiler/Compiler.h"
|
||||
#include "goalc/compiler/IR.h"
|
||||
|
||||
/*!
|
||||
* Main table for compiler forms
|
||||
*/
|
||||
static const std::unordered_map<
|
||||
std::string,
|
||||
Val* (Compiler::*)(const goos::Object& form, const goos::Object& rest, Env* env)>
|
||||
goal_forms = {
|
||||
// // inline asm
|
||||
// {".ret", &Compiler::compile_asm},
|
||||
// {".push", &Compiler::compile_asm},
|
||||
// {".pop", &Compiler::compile_asm},
|
||||
// {".jmp", &Compiler::compile_asm},
|
||||
// {".sub", &Compiler::compile_asm},
|
||||
// {".ret-reg", &Compiler::compile_asm},
|
||||
//
|
||||
// // BLOCK FORMS
|
||||
{"top-level", &Compiler::compile_top_level},
|
||||
{"begin", &Compiler::compile_begin},
|
||||
{"block", &Compiler::compile_block},
|
||||
{"return-from", &Compiler::compile_return_from},
|
||||
{"label", &Compiler::compile_label},
|
||||
{"goto", &Compiler::compile_goto},
|
||||
//
|
||||
// // COMPILER CONTROL
|
||||
// {"gs", &Compiler::compile_gs},
|
||||
{":exit", &Compiler::compile_exit},
|
||||
{"asm-file", &Compiler::compile_asm_file},
|
||||
{"listen-to-target", &Compiler::compile_listen_to_target},
|
||||
{"reset-target", &Compiler::compile_reset_target},
|
||||
{":status", &Compiler::compile_poke},
|
||||
// {"test", &Compiler::compile_test},
|
||||
// {"in-package", &Compiler::compile_in_package},
|
||||
//
|
||||
// // CONDITIONAL COMPILATION
|
||||
{"#cond", &Compiler::compile_gscond},
|
||||
{"defglobalconstant", &Compiler::compile_defglobalconstant},
|
||||
{"seval", &Compiler::compile_seval},
|
||||
//
|
||||
// // CONTROL FLOW
|
||||
// {"cond", &Compiler::compile_cond},
|
||||
// {"when-goto", &Compiler::compile_when_goto},
|
||||
//
|
||||
// // DEFINITION
|
||||
{"define", &Compiler::compile_define},
|
||||
// {"define-extern", &Compiler::compile_define_extern},
|
||||
// {"set!", &Compiler::compile_set},
|
||||
// {"defun-extern", &Compiler::compile_defun_extern},
|
||||
// {"declare-method", &Compiler::compile_declare_method},
|
||||
//
|
||||
// // DEFTYPE
|
||||
// {"deftype", &Compiler::compile_deftype},
|
||||
//
|
||||
// // ENUM
|
||||
// {"defenum", &Compiler::compile_defenum},
|
||||
//
|
||||
// // Field Access
|
||||
// {"->", &Compiler::compile_deref},
|
||||
// {"&", &Compiler::compile_addr_of},
|
||||
//
|
||||
//
|
||||
// // LAMBDA
|
||||
// {"lambda", &Compiler::compile_lambda},
|
||||
// {"inline", &Compiler::compile_inline},
|
||||
// {"with-inline", &Compiler::compile_with_inline},
|
||||
// {"rlet", &Compiler::compile_rlet},
|
||||
// {"mlet", &Compiler::compile_mlet},
|
||||
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
|
||||
//
|
||||
//
|
||||
//
|
||||
// // MACRO
|
||||
// {"print-type", &Compiler::compile_print_type},
|
||||
{"quote", &Compiler::compile_quote},
|
||||
// {"defconstant", &Compiler::compile_defconstant},
|
||||
//
|
||||
// {"declare", &Compiler::compile_declare},
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// // OBJECT
|
||||
//
|
||||
// {"the", &Compiler::compile_the},
|
||||
// {"the-as", &Compiler::compile_the_as},
|
||||
//
|
||||
// {"defmethod", &Compiler::compile_defmethod},
|
||||
//
|
||||
// {"current-method-type", &Compiler::compile_current_method_type},
|
||||
// {"new", &Compiler::compile_new},
|
||||
// {"method", &Compiler::compile_method},
|
||||
//
|
||||
// // PAIR
|
||||
// {"car", &Compiler::compile_car},
|
||||
// {"cdr", &Compiler::compile_cdr},
|
||||
//
|
||||
// // IT IS MATH
|
||||
// {"+", &Compiler::compile_add},
|
||||
// {"-", &Compiler::compile_sub},
|
||||
// {"*", &Compiler::compile_mult},
|
||||
// {"/", &Compiler::compile_divide},
|
||||
// {"shlv", &Compiler::compile_shlv},
|
||||
// {"shrv", &Compiler::compile_shrv},
|
||||
// {"sarv", &Compiler::compile_sarv},
|
||||
// {"shl", &Compiler::compile_shl},
|
||||
// {"shr", &Compiler::compile_shr},
|
||||
// {"sar", &Compiler::compile_sar},
|
||||
// {"mod", &Compiler::compile_mod},
|
||||
// {"logior", &Compiler::compile_logior},
|
||||
// {"logxor", &Compiler::compile_logxor},
|
||||
// {"logand", &Compiler::compile_logand},
|
||||
// {"lognot", &Compiler::compile_lognot},
|
||||
// {"=", &Compiler::compile_condition_as_bool},
|
||||
// {"!=", &Compiler::compile_condition_as_bool},
|
||||
// {"eq?", &Compiler::compile_condition_as_bool},
|
||||
// {"not", &Compiler::compile_condition_as_bool},
|
||||
// {"<=", &Compiler::compile_condition_as_bool},
|
||||
// {">=", &Compiler::compile_condition_as_bool},
|
||||
// {"<", &Compiler::compile_condition_as_bool},
|
||||
// {">", &Compiler::compile_condition_as_bool},
|
||||
//
|
||||
// // BUILDER
|
||||
// {"builder", &Compiler::compile_builder},
|
||||
//
|
||||
// // UTIL
|
||||
// {"set-config!", &Compiler::compile_set_config},
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
//
|
||||
// // temporary testing hacks...
|
||||
// {"send-test", &Compiler::compile_send_test_data},
|
||||
};
|
||||
|
||||
Val* Compiler::compile(const goos::Object& code, Env* env) {
|
||||
switch (code.type) {
|
||||
case goos::ObjectType::PAIR:
|
||||
return compile_pair(code, env);
|
||||
case goos::ObjectType::INTEGER:
|
||||
return compile_integer(code, env);
|
||||
case goos::ObjectType::SYMBOL:
|
||||
return compile_symbol(code, env);
|
||||
default:
|
||||
ice("Don't know how to compile " + code.print());
|
||||
}
|
||||
return get_none();
|
||||
}
|
||||
|
||||
Val* Compiler::compile_pair(const goos::Object& code, Env* env) {
|
||||
auto pair = code.as_pair();
|
||||
auto head = pair->car;
|
||||
auto rest = pair->cdr;
|
||||
|
||||
if (head.is_symbol()) {
|
||||
auto head_sym = head.as_symbol();
|
||||
// first try as a goal compiler form
|
||||
auto kv_gfs = goal_forms.find(head_sym->name);
|
||||
if (kv_gfs != goal_forms.end()) {
|
||||
return ((*this).*(kv_gfs->second))(code, rest, env);
|
||||
}
|
||||
|
||||
goos::Object macro_obj;
|
||||
if (try_getting_macro_from_goos(head, ¯o_obj)) {
|
||||
return compile_goos_macro(code, macro_obj, rest, env);
|
||||
}
|
||||
|
||||
// todo enum
|
||||
}
|
||||
|
||||
// todo function or method call
|
||||
ice("unhandled compile_pair on " + code.print());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Val* Compiler::compile_integer(const goos::Object& code, Env* env) {
|
||||
return compile_integer(code.integer_obj.value, env);
|
||||
}
|
||||
|
||||
Val* Compiler::compile_integer(s64 value, Env* env) {
|
||||
auto fe = get_parent_env_of_type<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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
void Logger::close() {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
fp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
add_library(util SHARED text_util.cpp file_io.cpp)
|
||||
add_library(util SHARED text_util.cpp file_io.cpp Timer.cpp)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
Executable
+9
@@ -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
|
||||
Reference in New Issue
Block a user