From 2b67b1078f238f4a874a7c6822b0d245c625bf94 Mon Sep 17 00:00:00 2001 From: water Date: Sat, 5 Sep 2020 18:55:07 -0400 Subject: [PATCH 01/34] add lambda skeleton --- goalc/compiler/Lambda.h | 10 +++++++++ goalc/compiler/Val.cpp | 31 +++++++++++++++++++++------ goalc/compiler/Val.h | 46 +++++++++++++++++++++++++++++++++-------- 3 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 goalc/compiler/Lambda.h diff --git a/goalc/compiler/Lambda.h b/goalc/compiler/Lambda.h new file mode 100644 index 0000000000..21a92ba820 --- /dev/null +++ b/goalc/compiler/Lambda.h @@ -0,0 +1,10 @@ + + +#ifndef JAK_LAMBDA_H +#define JAK_LAMBDA_H + +struct Lambda { + std::string debug_name; +}; + +#endif // JAK_LAMBDA_H diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index ef5ba521fd..78ae5c6d81 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -3,20 +3,39 @@ /*! * Fallback to_gpr if a more optimized one is not provided. */ -RegVal* Val::to_gpr(FunctionEnv* fe) const { +const RegVal* Val::to_gpr(FunctionEnv* fe) const { (void)fe; - throw std::runtime_error("Val::to_gpr NYI"); + 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 { +const RegVal* Val::to_xmm(FunctionEnv* fe) const { (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 { + +const RegVal* RegVal::to_reg(FunctionEnv* fe) const { (void)fe; - throw std::runtime_error("Cannot put None into a register."); + return this; } + +const RegVal * RegVal::to_gpr(FunctionEnv* fe) const { + (void)fe; + if(m_ireg.kind == emitter::RegKind::GPR) { + return this; + } else { + throw std::runtime_error("RegVal::to_gpr NYI"); // todo + } +} + +const RegVal * RegVal::to_xmm(FunctionEnv* fe) const { + (void)fe; + if(m_ireg.kind == emitter::RegKind::XMM) { + return this; + } else { + throw std::runtime_error("RegVal::to_xmm NYI"); // todo + } +} \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index a95f9f285f..f600526acf 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -12,6 +12,7 @@ #include "third-party/fmt/core.h" #include "common/type_system/TypeSystem.h" #include "goalc/regalloc/IRegister.h" +#include "Lambda.h" class RegVal; class FunctionEnv; @@ -30,9 +31,11 @@ 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 const RegVal* to_reg(FunctionEnv* fe) const { + throw std::runtime_error("to_reg called on invalid Val: " + print()); + } + virtual const RegVal* to_gpr(FunctionEnv* fe) const; + virtual const RegVal* to_xmm(FunctionEnv* fe) const; const TypeSpec& type() const { return m_ts; } @@ -47,7 +50,6 @@ class None : public Val { 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,16 +61,42 @@ 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; + const RegVal* to_reg(FunctionEnv* fe) const override; + const RegVal* to_gpr(FunctionEnv* fe) const override; + const RegVal* to_xmm(FunctionEnv* fe) const 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() { return m_name; } + std::string print() const override { return "<" + m_name + ">"; } + + protected: + std::string m_name; +}; + +/*! + * 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; } + + protected: + Lambda m_lam; + FunctionEnv* fe = nullptr; +}; + // Static // MemOffConstant // MemOffVar From 8bf0bd86d3dbd90cf07f219a77ecbdb4604f282c Mon Sep 17 00:00:00 2001 From: water Date: Sun, 6 Sep 2020 12:45:31 -0400 Subject: [PATCH 02/34] integer constant program working up to ir --- common/listener_common.h | 16 +- common/util/BinaryWriter.h | 78 +++++++ goalc/CMakeLists.txt | 23 ++- goalc/compiler/Compiler.cpp | 110 +++++++++- goalc/compiler/Compiler.h | 36 ++++ goalc/compiler/Env.cpp | 187 ++++++++++++++++- goalc/compiler/Env.h | 195 +++++++++++++++++- goalc/compiler/IR.cpp | 43 +++- goalc/compiler/IR.h | 35 +++- goalc/compiler/Label.h | 10 + goalc/compiler/Util.cpp | 96 +++++++++ goalc/compiler/Val.cpp | 15 +- goalc/compiler/Val.h | 13 ++ goalc/compiler/compilation/Atoms.cpp | 177 ++++++++++++++++ goalc/compiler/compilation/Block.cpp | 16 ++ .../compiler/compilation/CompilerControl.cpp | 12 ++ goalc/listener/CMakeLists.txt | 5 + goalc/listener/Listener.cpp | 94 +++++++++ goalc/listener/Listener.h | 9 + goalc/main.cpp | 6 +- 20 files changed, 1142 insertions(+), 34 deletions(-) create mode 100644 common/util/BinaryWriter.h create mode 100644 goalc/compiler/Label.h create mode 100644 goalc/compiler/Util.cpp create mode 100644 goalc/compiler/compilation/Atoms.cpp create mode 100644 goalc/compiler/compilation/Block.cpp create mode 100644 goalc/compiler/compilation/CompilerControl.cpp diff --git a/common/listener_common.h b/common/listener_common.h index f18c41b4a5..a589919ba6 100644 --- a/common/listener_common.h +++ b/common/listener_common.h @@ -33,7 +33,7 @@ enum class ListenerMessageKind : u16 { /*! * Type of message sent from compiler */ -enum ListenerToTargetMsgKind { +enum ListenerToTargetMsgKind : u16 { LTT_MSG_POKE = 1, //! "Poke" the game and have it flush buffers LTT_MSG_INSEPCT = 5, //! Inspect an object LTT_MSG_PRINT = 6, //! Print an object @@ -47,11 +47,15 @@ enum ListenerToTargetMsgKind { * TODO - there are other copies of this somewhere */ struct ListenerMessageHeader { - Deci2Header deci2_header; //! The header used for DECI2 communication - ListenerMessageKind msg_kind; //! GOAL Listener message kind - u16 u6; //! Unknown - u32 msg_size; //! Size of data after this header - u64 u8; //! Unknown + Deci2Header deci2_header; //! The header used for DECI2 communication + union { + ListenerMessageKind msg_kind; //! GOAL Listener message kind + ListenerToTargetMsgKind ltt_msg_kind; + }; + + u16 u6; //! Unknown + u32 msg_size; //! Size of data after this header + u64 u8; //! Unknown }; constexpr int DECI2_PORT = 8112; // TODO - is this a good choise? diff --git a/common/util/BinaryWriter.h b/common/util/BinaryWriter.h new file mode 100644 index 0000000000..fab9c5b19b --- /dev/null +++ b/common/util/BinaryWriter.h @@ -0,0 +1,78 @@ +#ifndef JAK_BINARYWRITER_H +#define JAK_BINARYWRITER_H + +#include +#include +#include +#include +#include + +struct BinaryWriterRef { + size_t offset; + size_t write_size; +}; + +class BinaryWriter { + public: + BinaryWriter() = default; + + template + BinaryWriterRef add(const T& obj) { + auto orig_size = data.size(); + data.resize(orig_size + sizeof(T)); + memcpy(data.data() + orig_size, &obj, sizeof(T)); + return {orig_size, sizeof(T)}; + } + + template + void add_at_ref(const T& obj, const BinaryWriterRef& ref) { + assert(ref.write_size == sizeof(T)); + assert(ref.offset + ref.write_size < get_size()); + memcpy(data.data() + ref.offset, &obj, sizeof(T)); + } + + void add_str_len(const std::string& str, size_t len) { + add_cstr_len(str.c_str(), len); + } + + void add_cstr_len(const char* str, size_t len) { + size_t i = 0; + while(*str) { + data.push_back(*str); + str++; + i++; + } + + while(i < len) { + data.push_back(0); + i++; + } + } + + BinaryWriterRef add_data(void* d, size_t len) { + auto orig_size = data.size(); + data.resize(orig_size + len); + memcpy(data.data() + orig_size, d, len); + return {orig_size, len}; + } + + size_t get_size() { + return data.size(); + } + + void* get_data() { + return data.data(); + } + + void write_to_file(const std::string& filename) { + auto fp = fopen(filename.c_str(), "wb"); + if(!fp) throw std::runtime_error("failed to open " + filename); + if(fwrite(get_data(), get_size(), 1, fp) != 1) throw std::runtime_error("failed to write " + filename); + fclose(fp); + } + + private: + std::vector data; +}; + +#endif // JAK_BINARYWRITER_H diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 21b3fcb92c..c63f22b516 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -2,11 +2,11 @@ add_subdirectory(util) add_subdirectory(goos) IF (WIN32) - # TODO - implement windows listener - message("Windows Listener Not Implemented!") -ELSE() - add_subdirectory(listener) -ENDIF() + # TODO - implement windows listener + message("Windows Listener Not Implemented!") +ELSE () + add_subdirectory(listener) +ENDIF () add_library(compiler SHARED @@ -19,6 +19,10 @@ 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/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp regalloc/Allocator.cpp @@ -30,8 +34,11 @@ add_executable(goalc main.cpp ) IF (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) - target_link_libraries(compiler util goos type_system mman) -ENDIF() + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + target_link_libraries(compiler util goos type_system mman) + +ELSE () + target_link_libraries(compiler util goos type_system listener) +ENDIF () target_link_libraries(goalc util goos compiler type_system) diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index e1353df059..240ded2502 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -1,19 +1,62 @@ #include "Compiler.h" #include "goalc/logger/Logger.h" +#include "common/link_types.h" + +using namespace goos; Compiler::Compiler() { init_logger(); m_ts.add_builtin_types(); + m_global_env = std::make_unique(); + m_none = std::make_unique(m_ts.make_typespec("none")); + + // todo - compile library } -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(); + + // // 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 +64,65 @@ 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; -} \ No newline at end of file +} + +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)); + + return file_env; +} + +std::unique_ptr Compiler::compile_top_level_function(const std::string& name, + const goos::Object& code, + Env* env) { + auto fe = std::make_unique(env, name); + fe->set_segment(TOP_LEVEL_SEGMENT); + + auto result = compile_error_guard(code, fe.get()); + + // only move to return register if we actually got a result + if (!dynamic_cast(result)) { + fe->emit(std::make_unique(fe->make_gpr(result->type()), result->to_gpr(fe.get()))); + } + + fe->finish(); + return fe; +} + +Val* Compiler::compile_error_guard(const goos::Object& code, Env* env) { + try { + return compile(code, env); + } catch (std::runtime_error& e) { + printf( + "------------------------------------------------------------------------------------------" + "-\n"); + auto obj_print = code.print(); + if (obj_print.length() > 80) { + obj_print = obj_print.substr(0, 80); + obj_print += "..."; + } + printf("object: %s\nfrom : %s\n", obj_print.c_str(), + m_goos.reader.db.get_info_for(code).c_str()); + throw e; + } +} + +void Compiler::throw_compile_error(const goos::Object& o, const std::string& err) { + gLogger.log(MSG_ERR, "[Error] Could not compile %s!\nReason: %s\n", o.print().c_str(), + err.c_str()); + throw std::runtime_error(err); +} + +void Compiler::ice(const std::string& error) { + gLogger.log(MSG_ICE, "[ICE] %s\n", error.c_str()); + throw std::runtime_error("ICE"); +} diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 12781a408d..a491416a35 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -2,17 +2,53 @@ #define JAK_COMPILER_H #include "common/type_system/TypeSystem.h" +#include "Env.h" +#include "goalc/listener/Listener.h" +#include "goalc/goos/Interpreter.h" class Compiler { public: Compiler(); ~Compiler(); void execute_repl(); + FileEnv* compile_object_file(const std::string& name, goos::Object code, bool allow_emit); + std::unique_ptr compile_top_level_function(const std::string& name, + const goos::Object& code, + Env* env); + Val* compile(const goos::Object& code, Env* env); + Val* compile_error_guard(const goos::Object& code, Env* env); + void throw_compile_error(const goos::Object& o, const std::string& err); + void ice(const std::string& err); + None* get_none() { return m_none.get(); } private: void init_logger(); + 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); + + void for_each_in_list(const goos::Object& list, const std::function& f); + + + goos::Arguments get_va(const goos::Object& form, const goos::Object& rest); + void va_check( + const goos::Object& form, + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& + named); TypeSystem m_ts; + std::unique_ptr m_global_env = nullptr; + std::unique_ptr m_none = nullptr; + bool m_want_exit = false; + listener::Listener m_listener; + goos::Interpreter m_goos; + + public: + Val* compile_exit(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_begin(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 785a75a876..dab3d75a76 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -1,3 +1,186 @@ - - +#include #include "Env.h" + +/////////////////// +// Env +/////////////////// + +/*! + * Emit IR into the function currently being compiled. + */ +void Env::emit(std::unique_ptr ir) { + // by default, we don't know how, so pass it up and hope for the best. + m_parent->emit(std::move(ir)); +} + +/*! + * Allocate an IRegister with the given type. + */ +RegVal* Env::make_ireg(TypeSpec ts, emitter::RegKind kind) { + return m_parent->make_ireg(std::move(ts), kind); +} + +/*! + * Apply a register constraint to the current function. + */ +void Env::constrain_reg(IRegConstraint constraint) { + m_parent->constrain_reg(std::move(constraint)); +} + +/*! + * Lookup the given symbol object as a lexical variable. + */ +Val* Env::lexical_lookup(goos::Object sym) { + return m_parent->lexical_lookup(std::move(sym)); +} + +BlockEnv* Env::find_block(const std::string& name) { + return m_parent->find_block(name); +} + +RegVal* Env::make_gpr(TypeSpec ts) { + return make_ireg(std::move(ts), emitter::RegKind::GPR); +} + +RegVal* Env::make_xmm(TypeSpec ts) { + return make_ireg(std::move(ts), emitter::RegKind::XMM); +} + +/////////////////// +// GlobalEnv +/////////////////// + +// Because this is the top of the environment chain, all these end the parent calls and provide +// errors, or return that the items were not found. + +GlobalEnv::GlobalEnv() : Env(nullptr) {} + +std::string GlobalEnv::print() { + return "global-env"; +} + +/*! + * Emit IR into the function currently being compiled. + */ +void GlobalEnv::emit(std::unique_ptr ir) { + // by default, we don't know how, so pass it up and hope for the best. + (void)ir; + throw std::runtime_error("cannot emit to GlobalEnv"); +} + +/*! + * Allocate an IRegister with the given type. + */ +RegVal* GlobalEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { + (void)ts; + (void)kind; + throw std::runtime_error("cannot alloc reg in GlobalEnv"); +} + +/*! + * Apply a register constraint to the current function. + */ +void GlobalEnv::constrain_reg(IRegConstraint constraint) { + (void)constraint; + throw std::runtime_error("cannot constrain reg in GlobalEnv"); +} + +/*! + * Lookup the given symbol object as a lexical variable. + */ +Val* GlobalEnv::lexical_lookup(goos::Object sym) { + (void)sym; + return nullptr; +} + +BlockEnv * GlobalEnv::find_block(const std::string& name) { + (void)name; + return nullptr; +} + +FileEnv * GlobalEnv::add_file(std::string name) { + m_files.push_back(std::make_unique(this, std::move(name))); + return m_files.back().get(); +} + +/////////////////// +// NoEmitEnv +/////////////////// + +/*! + * Get the name of a NoEmitEnv + */ +std::string NoEmitEnv::print() { + return "no-emit-env"; +} + +/*! + * Emit - which is invalid - into a NoEmitEnv and throw an exception. + */ +void NoEmitEnv::emit(std::unique_ptr ir) { + (void)ir; + throw std::runtime_error("emit into a no-emit env!"); +} + +/////////////////// +// FileEnv +/////////////////// + +FileEnv::FileEnv(Env* parent, std::string name) : Env(parent), m_name(std::move(name)) {} + +std::string FileEnv::print() { + return "file-" + m_name; +} + +void FileEnv::add_function(std::unique_ptr fe) { + m_functions.push_back(std::move(fe)); +} + +void FileEnv::add_top_level_function(std::unique_ptr fe) { + // todo, set FE as top level segment + m_functions.push_back(std::move(fe)); + m_top_level_func = m_functions.back().get(); +} + +NoEmitEnv * FileEnv::add_no_emit_env() { + assert(!m_no_emit_env); + m_no_emit_env = std::make_unique(this); + return m_no_emit_env.get(); +} + +void FileEnv::debug_print_tl() { + if(m_top_level_func) { + for(auto& code : m_top_level_func->code()) { + fmt::print("{}\n", code->print()); + } + } else { + printf("no top level function.\n"); + } +} + +/////////////////// +// FunctionEnv +/////////////////// + +FunctionEnv::FunctionEnv(Env* parent, std::string name) : DeclareEnv(parent), m_name(std::move(name)){} + +std::string FunctionEnv::print() { + return "function-" + m_name; +} + +void FunctionEnv::emit(std::unique_ptr ir) { + ir->add_constraints(&m_constraints, m_code.size()); + m_code.push_back(std::move(ir)); +} +void FunctionEnv::finish() { + // todo resolve gotos +} + +RegVal * FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { + IRegister ireg; + ireg.kind = kind; + ireg.id = m_iregs.size(); + auto rv = std::make_unique(ireg, ts); + m_iregs.push_back(std::move(rv)); + return m_iregs.back().get(); +} \ No newline at end of file diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index e7c85e61d9..bfef83ffc1 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -1,14 +1,197 @@ - +/*! + * @file Env.h + * The Env tree. The stores all of the nested scopes/contexts during compilation and also + * manages the memory for stuff generated during compiling. + */ #ifndef JAK_ENV_H #define JAK_ENV_H -class Env {}; +#include +#include +#include +#include "common/type_system/TypeSpec.h" +#include "goalc/goos/Object.h" +#include "IR.h" +#include "Label.h" +#include "Val.h" -// global -// noemit -// objectfile -// configuration +class FileEnv; +class BlockEnv; + +/*! + * Parent class for Env's + */ +class Env { + public: + explicit Env(Env* parent) : m_parent(parent) {} + virtual std::string print() = 0; + virtual void emit(std::unique_ptr ir); + virtual RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind); + virtual void constrain_reg(IRegConstraint constraint); + virtual Val* lexical_lookup(goos::Object sym); + virtual BlockEnv* find_block(const std::string& name); + RegVal* make_gpr(TypeSpec ts); + RegVal* make_xmm(TypeSpec ts); + virtual ~Env() = default; + + Env* parent() { return m_parent; } + + protected: + Env* m_parent = nullptr; +}; + +/*! + * The top-level Env. Holds FileEnvs for all files. + */ +class GlobalEnv : public Env { + public: + GlobalEnv(); + std::string print() override; + void emit(std::unique_ptr ir) override; + RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override; + void constrain_reg(IRegConstraint constraint) override; + Val* lexical_lookup(goos::Object sym) override; + BlockEnv* find_block(const std::string& name) override; + ~GlobalEnv() = default; + + FileEnv* add_file(std::string name); + + private: + std::vector> m_files; +}; + +/*! + * An Env that doesn't allow emitting to go past it. Used to make sure source code that shouldn't + * generate machine code actually does this. + */ +class NoEmitEnv : public Env { + public: + explicit NoEmitEnv(Env* parent) : Env(parent) {} + std::string print() override; + void emit(std::unique_ptr ir) override; + ~NoEmitEnv() = default; +}; + +/*! + * An Env for an entire file (or input to the REPL) + */ +class FileEnv : public Env { + public: + FileEnv(Env* parent, std::string name); + std::string print() override; + void add_function(std::unique_ptr fe); + void add_top_level_function(std::unique_ptr fe); + NoEmitEnv* add_no_emit_env(); + void debug_print_tl(); + + // todo - is_empty + ~FileEnv() = default; + + protected: + std::string m_name; + std::vector> m_functions; + std::unique_ptr m_no_emit_env = nullptr; + + // statics + FunctionEnv* m_top_level_func = nullptr; +}; + +/*! + * An Env which manages the scope for (declare ...) statements. + */ +class DeclareEnv : public Env { + public: + explicit DeclareEnv(Env* parent) : Env(parent) {} + virtual std::string print() = 0; + ~DeclareEnv() = default; + + struct Settings { + bool is_set = false; // has the user set these with a (declare)? + bool inline_by_default = false; // if a function, inline when possible? + bool save_code = true; // if a function, should we save the code? + bool allow_inline = false; // should we allow the user to use this an inline function + } m_settings; +}; + +class FunctionEnv : public DeclareEnv { + public: + FunctionEnv(Env* parent, std::string name); + std::string print() override; + void set_segment(int seg) { m_segment = seg; } + void emit(std::unique_ptr ir) override; + void finish(); + RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override; + const std::vector>& code() { return m_code; } + + template + T* alloc_val(Args&&... args) { + std::unique_ptr new_obj = std::make_unique(std::forward(args)...); + m_vals.push_back(std::move(new_obj)); + return (T*)m_vals.back().get(); + } + + + protected: + std::string m_name; + std::vector> m_code; + std::vector> m_iregs; + std::vector> m_vals; + std::vector m_constraints; + // todo, unresolved gotos + AllocationResult m_regalloc_result; + bool m_is_asm_func = false; + + std::string m_method_of_type_name = "#f"; + bool m_aligned_stack_required = false; + int m_segment = -1; + + std::unordered_map m_params; +}; + +class BlockEnv : public Env { + public: + BlockEnv(Env* parent, std::string name); + std::string print() override; + BlockEnv* find_block(const std::string& name) override; + protected: + std::string m_name; + Label* m_end_label = nullptr; + Val* m_return_value = nullptr; + std::vector m_return_types; +}; + +class LexicalEnv : public Env { + public: + LexicalEnv(Env* parent); + std::string print() override; +}; + +class LabelEnv : public Env { + public: + protected: + std::unordered_map m_labels; +}; + +class WithInlineEnv : public Env { + +}; + +class SymbolMacroEnv : public Env { + +}; + +template +T* get_parent_env_of_type(Env* in) { + for(;;) { + auto attempt = dynamic_cast(in); + if(attempt) return attempt; + if(dynamic_cast(in)) { + return nullptr; + } + in = in->parent(); + } +} // function // block // lexical diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 479292ef2e..287bf6348b 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -1,3 +1,42 @@ - - #include "IR.h" + +IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value) : m_return_reg(return_reg), m_value(value) {} +std::string IR_Return::print() { + return fmt::format("ret {} {}", m_return_reg->print(), m_value->print()); +} + +RegAllocInstr IR_Return::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_return_reg->ireg()); + rai.read.push_back(m_value->ireg()); + if(m_value->ireg().kind == m_return_reg->ireg().kind) { + rai.is_move = true; // only true if we aren't moving from register kind to register kind + } + return rai; +} + +void IR_Return::add_constraints(std::vector* constraints, int my_id) { + IRegConstraint c; + if(dynamic_cast(m_return_reg)){ + return; + } + + c.ireg = m_return_reg->ireg(); + c.instr_idx = my_id; + c.desired_register = emitter::RAX; + constraints->push_back(c); +} + +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; +} \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index f379466466..386e368b79 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -4,18 +4,49 @@ #include #include "CodeGenerator.h" #include "goalc/regalloc/allocate.h" +#include "Val.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(CodeGenerator* gen) = 0; + virtual void add_constraints(std::vector* constraints, int my_id) { + (void)constraints; + (void)my_id; + } }; -class IR_Set : public IR { +// class IR_Set : public IR { +// public: +// std::string print() override; +// RegAllocInstr to_rai() override; +//}; + +class IR_Return : public IR { public: + IR_Return(const RegVal* return_reg, const RegVal* value); std::string print() override; RegAllocInstr to_rai() override; + void add_constraints(std::vector* constraints, int my_id) override; +// void do_codegen(CodeGenerator* gen) 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(CodeGenerator* gen) override; + + protected: + const RegVal* m_dest = nullptr; + u64 m_value = 0; }; #endif // JAK_IR_H diff --git a/goalc/compiler/Label.h b/goalc/compiler/Label.h new file mode 100644 index 0000000000..025f4b22fa --- /dev/null +++ b/goalc/compiler/Label.h @@ -0,0 +1,10 @@ + + +#ifndef JAK_LABEL_H +#define JAK_LABEL_H + +struct Label { + +}; + +#endif // JAK_LABEL_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp new file mode 100644 index 0000000000..540c1be629 --- /dev/null +++ b/goalc/compiler/Util.cpp @@ -0,0 +1,96 @@ +#include "goalc/compiler/Compiler.h" + +goos::Arguments Compiler::get_va(const goos::Object& form, const goos::Object& rest) { + goos::Arguments args; + // loop over forms in list + goos::Object current = rest; + while (!current.is_empty_list()) { + auto arg = current.as_pair()->car; + + // did we get a ":keyword" + if (arg.is_symbol() && arg.as_symbol()->name.at(0) == ':') { + auto key_name = arg.as_symbol()->name.substr(1); + + // check for multiple definition of key + if (args.named.find(key_name) != args.named.end()) { + throw_compile_error(form, "Key argument " + key_name + " multiply defined"); + } + + // check for well-formed :key value expression + current = current.as_pair()->cdr; + if (current.is_empty_list()) { + throw_compile_error(form, "Key argument didn't have a value"); + } + + args.named[key_name] = current.as_pair()->car; + } else { + // not a keyword. Add to unnamed or rest, depending on what we expect + args.unnamed.push_back(arg); + } + current = current.as_pair()->cdr; + } + + return args; +} + +void Compiler::va_check( + const goos::Object& form, + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& + named) { + assert(args.rest.empty()); + if (unnamed.size() != args.unnamed.size()) { + throw_compile_error(form, "Got " + std::to_string(args.unnamed.size()) + + " arguments, but expected " + std::to_string(unnamed.size())); + } + + for (size_t i = 0; i < unnamed.size(); i++) { + if (unnamed[i] != args.unnamed[i].type) { + assert(!unnamed[i].is_wildcard); + throw_compile_error(form, "Argument " + std::to_string(i) + " has type " + + object_type_to_string(args.unnamed[i].type) + " but " + + object_type_to_string(unnamed[i].value) + " was expected"); + } + } + + for (const auto& kv : named) { + auto kv2 = args.named.find(kv.first); + if (kv2 == args.named.end()) { + // argument not given. + if (kv.second.first) { + // but was required + throw_compile_error(form, "Required named argument \"" + kv.first + "\" was not found"); + } + } else { + // argument given. + if (kv.second.second != kv2->second.type) { + // but is wrong type + assert(!kv.second.second.is_wildcard); + throw_compile_error(form, "Argument \"" + kv.first + "\" has type " + + object_type_to_string(kv2->second.type) + " but " + + object_type_to_string(kv.second.second.value) + + " was expected"); + } + } + } + + for (const auto& kv : args.named) { + if (named.find(kv.first) == named.end()) { + throw_compile_error(form, "Got unrecognized keyword argument \"" + kv.first + "\""); + } + } +} + +void Compiler::for_each_in_list(const goos::Object& list, const std::function& f) { + const goos::Object* iter = &list; + while(iter->is_pair()) { + auto lap = iter->as_pair(); + f(lap->car); + iter = &lap->cdr; + } + + if(!iter->is_empty_list()) { + throw_compile_error(list, "invalid list in for_each_in_list"); + } +} \ No newline at end of file diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index 78ae5c6d81..590d411392 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -1,11 +1,16 @@ #include "Val.h" +#include "Env.h" /*! * Fallback to_gpr if a more optimized one is not provided. */ const RegVal* Val::to_gpr(FunctionEnv* fe) const { - (void)fe; - throw std::runtime_error("Val::to_gpr NYI"); // todo + auto rv = to_reg(fe); + if(rv->ireg().kind == emitter::RegKind::GPR) { + return rv; + } else { + throw std::runtime_error("Val::to_gpr NYI"); // todo + } } /*! @@ -38,4 +43,10 @@ const RegVal * RegVal::to_xmm(FunctionEnv* fe) const { } else { throw std::runtime_error("RegVal::to_xmm NYI"); // todo } +} + +const RegVal * IntegerConstantVal::to_reg(FunctionEnv* fe) const { + auto rv = fe->make_gpr(m_ts); + fe->emit(std::make_unique(rv, m_value)); + return rv; } \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index f600526acf..9ee9b86f27 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -32,12 +32,14 @@ class Val { virtual std::string print() const = 0; virtual const RegVal* to_reg(FunctionEnv* fe) const { + (void)fe; throw std::runtime_error("to_reg called on invalid Val: " + print()); } virtual const RegVal* to_gpr(FunctionEnv* fe) const; virtual const RegVal* to_xmm(FunctionEnv* fe) const; const TypeSpec& type() const { return m_ts; } + void set_type(TypeSpec ts) { m_ts = std::move(ts); } protected: TypeSpec m_ts; @@ -47,6 +49,7 @@ 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"; } @@ -103,6 +106,16 @@ class LambdaVal : public Val { // 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); } + const RegVal* to_reg(FunctionEnv* fe) const override; + + protected: + s64 m_value = -1; +}; // IntegerConstant // FloatConstant // Bitfield diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp new file mode 100644 index 0000000000..1b967a39d9 --- /dev/null +++ b/goalc/compiler/compilation/Atoms.cpp @@ -0,0 +1,177 @@ +#include "goalc/compiler/Compiler.h" + +/*! + * Main table for compiler forms + */ +static const std::unordered_map 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}, +// {"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}, +// +// +// +// {"listen-to-target", &Compiler::compile_listen_to_target}, +// {"reset-target", &Compiler::compile_reset_target}, +// {":status", &Compiler::compile_poke}, +// +// // 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); + 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); + } + + // todo macro + // todo enum + } + + // todo function or method call + ice("unhandled compile_pair on " + code.print()); + return nullptr; +} + +Val * Compiler::compile_integer(const goos::Object& code, Env* env) { + return compile_integer(code.integer_obj.value, env); +} + +Val * Compiler::compile_integer(s64 value, Env* env) { + auto fe = get_parent_env_of_type(env); + return fe->alloc_val(m_ts.make_typespec("int"), value); +} + diff --git a/goalc/compiler/compilation/Block.cpp b/goalc/compiler/compilation/Block.cpp new file mode 100644 index 0000000000..e20da3e492 --- /dev/null +++ b/goalc/compiler/compilation/Block.cpp @@ -0,0 +1,16 @@ +#include "goalc/compiler/Compiler.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; +} diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp new file mode 100644 index 0000000000..b8c2cd2f07 --- /dev/null +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -0,0 +1,12 @@ +#include "goalc/compiler/Compiler.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(); + } + m_want_exit = true; + return get_none(); +} \ No newline at end of file diff --git a/goalc/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt index f978b4d633..1ec9728b8d 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,2 +1,7 @@ add_library(listener SHARED Listener.cpp) +IF (WIN32) + # +ELSE () + target_link_libraries(listener pthread) +ENDIF () \ No newline at end of file diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 2a53f26612..ee9ecea49a 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -1,6 +1,8 @@ /*! * @file Listener.cpp * The Listener can connect to a Deci2Server for debugging. + * + * TODO - msg ID? */ // TODO-Windows @@ -11,10 +13,12 @@ #include #include #include +#include #include "Listener.h" #include "common/versions.h" using namespace versions; +constexpr bool debug_listener = true; namespace listener { Listener::Listener() { @@ -241,5 +245,95 @@ void Listener::receive_func() { } } +void Listener::send_code(std::vector &code) { + got_ack = false; + int total_size = code.size() + sizeof(ListenerMessageHeader); + if(total_size > BUFFER_SIZE) { + printf("[ERROR] Listener send_code got too big of a message\n"); + return; + } + + auto* header = (ListenerMessageHeader*)m_buffer; + auto* buffer_data = (char*)(header + 1); + header->deci2_header.rsvd = 0; + header->deci2_header.len = total_size; + header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.src = 'H'; + header->deci2_header.dst = 'E'; + header->msg_size = code.size(); + header->ltt_msg_kind = LTT_MSG_CODE; + header->u6 = 0; + header->u8 = 0; + memcpy(buffer_data, code.data(), code.size()); + send_buffer(total_size); +} + +void Listener::send_reset() { + 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 = 0; + send_buffer(sizeof(ListenerMessageHeader)); + disconnect(); + close(socket_fd); + printf("closed connection to target\n"); +} + +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(socket_fd, 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; + usleep(1000); + } + + waiting_for_ack = false; + return false; +} + } // namespace listener #endif diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 5ae49e2f5f..01f68c576e 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -23,9 +23,18 @@ class Listener { void record_messages(ListenerMessageKind kind); void stop_recording_messages(); bool is_connected() const; + void send_reset(); void disconnect(); + void send_code(std::vector &code); + bool most_recent_send_was_acked() { + return got_ack; + } private: + void send_buffer(int sz); + bool wait_for_ack(); + + char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? bool receive_thread_running = false; //! is the receive thread unjoined? diff --git a/goalc/main.cpp b/goalc/main.cpp index 57d145c069..b86bda8981 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -1,13 +1,13 @@ #include -#include "goalc/goos/Interpreter.h" +#include "goalc/compiler/Compiler.h" int main(int argc, char** argv) { (void)argc; (void)argv; printf("goal compiler\n"); - goos::Interpreter interp; - interp.execute_repl(); + Compiler compiler; + compiler.execute_repl(); return 0; } From d49b01e310f31b176629d61afc7ef8726eb86a6d Mon Sep 17 00:00:00 2001 From: water Date: Sun, 6 Sep 2020 16:58:25 -0400 Subject: [PATCH 03/34] working return integer tests as part of gtest --- CMakeLists.txt | 27 +- common/util/BinaryWriter.h | 26 +- game/kernel/kdgo.cpp | 4 +- game/kernel/klisten.cpp | 3 + game/main.cpp | 4 +- game/runtime.cpp | 5 +- game/runtime.h | 2 +- goal_src/test/test-return-integer-1.gc | 2 + goal_src/test/test-return-integer-2.gc | 1 + goal_src/test/test-return-integer-3.gc | 1 + goal_src/test/test-return-integer-4.gc | 1 + goal_src/test/test-return-integer-5.gc | 1 + goal_src/test/test-return-integer-6.gc | 1 + goal_src/test/test-return-integer-7.gc | 1 + goalc/compiler/CodeGenerator.cpp | 118 +++++++- goalc/compiler/CodeGenerator.h | 14 +- goalc/compiler/Compiler.cpp | 86 +++++- goalc/compiler/Compiler.h | 10 +- goalc/compiler/Env.cpp | 16 +- goalc/compiler/Env.h | 35 ++- goalc/compiler/IR.cpp | 41 ++- goalc/compiler/IR.h | 13 +- goalc/compiler/Label.h | 4 +- goalc/compiler/Util.cpp | 8 +- goalc/compiler/Val.cpp | 18 +- goalc/compiler/compilation/Atoms.cpp | 271 +++++++++--------- goalc/compiler/compilation/Block.cpp | 9 +- .../compiler/compilation/CompilerControl.cpp | 7 +- goalc/emitter/ObjectGenerator.cpp | 7 +- goalc/emitter/ObjectGenerator.h | 1 + goalc/listener/Listener.cpp | 48 ++-- goalc/listener/Listener.h | 11 +- goalc/logger/Logger.cpp | 1 + goalc/regalloc/allocate.cpp | 7 +- test/CMakeLists.txt | 1 + test/test_compiler_and_runtime.cpp | 109 +++++++ 36 files changed, 648 insertions(+), 266 deletions(-) create mode 100644 goal_src/test/test-return-integer-1.gc create mode 100644 goal_src/test/test-return-integer-2.gc create mode 100644 goal_src/test/test-return-integer-3.gc create mode 100644 goal_src/test/test-return-integer-4.gc create mode 100644 goal_src/test/test-return-integer-5.gc create mode 100644 goal_src/test/test-return-integer-6.gc create mode 100644 goal_src/test/test-return-integer-7.gc create mode 100644 test/test_compiler_and_runtime.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b40377dc4a..952c0f5722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,13 @@ set(CMAKE_CXX_STANDARD 14) # Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. -if(CMAKE_COMPILER_IS_GNUCXX) +if (CMAKE_COMPILER_IS_GNUCXX) message(STATUS "GCC detected, adding compile flags") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} \ + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ -Wall \ -Winit-self \ + -ggdb \ -Wextra \ -Wcast-align \ -Wcast-qual \ @@ -22,16 +23,16 @@ if(CMAKE_COMPILER_IS_GNUCXX) -Wredundant-decls \ -Wshadow \ -Wsign-promo") -else() - set(CMAKE_CXX_FLAGS "/EHsc") -endif(CMAKE_COMPILER_IS_GNUCXX) +else () + set(CMAKE_CXX_FLAGS "/EHsc") +endif (CMAKE_COMPILER_IS_GNUCXX) IF (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -ENDIF() + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +ENDIF () # includes relative to top level jak-project folder include_directories(./) @@ -65,5 +66,5 @@ add_subdirectory(third-party/fmt) # windows memory management lib IF (WIN32) - add_subdirectory(third-party/mman) -ENDIF() + add_subdirectory(third-party/mman) +ENDIF () diff --git a/common/util/BinaryWriter.h b/common/util/BinaryWriter.h index fab9c5b19b..8140e1ac59 100644 --- a/common/util/BinaryWriter.h +++ b/common/util/BinaryWriter.h @@ -16,7 +16,7 @@ class BinaryWriter { public: BinaryWriter() = default; - template + template BinaryWriterRef add(const T& obj) { auto orig_size = data.size(); data.resize(orig_size + sizeof(T)); @@ -24,26 +24,24 @@ class BinaryWriter { return {orig_size, sizeof(T)}; } - template + template void add_at_ref(const T& obj, const BinaryWriterRef& ref) { assert(ref.write_size == sizeof(T)); assert(ref.offset + ref.write_size < get_size()); memcpy(data.data() + ref.offset, &obj, sizeof(T)); } - void add_str_len(const std::string& str, size_t len) { - add_cstr_len(str.c_str(), len); - } + void add_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) { + while (*str) { data.push_back(*str); str++; i++; } - while(i < len) { + while (i < len) { data.push_back(0); i++; } @@ -56,18 +54,16 @@ class BinaryWriter { return {orig_size, len}; } - size_t get_size() { - return data.size(); - } + size_t get_size() { return data.size(); } - void* get_data() { - return data.data(); - } + 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); + 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); } diff --git a/game/kernel/kdgo.cpp b/game/kernel/kdgo.cpp index 6181c18bb2..f57fbedf29 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/kdgo.cpp @@ -138,8 +138,8 @@ u32 InitRPC() { */ void StopIOP() { x[2] = 0x14; // todo - this type and message - RpcSync(PLAYER_RPC_CHANNEL); - RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); + // RpcSync(PLAYER_RPC_CHANNEL); + // RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); printf("IOP shut down\n"); // sceDmaSync(0x10009000, 0, 0); printf("DMA shut down\n"); diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index 7f990bf27e..1c2d85d34e 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -141,6 +141,9 @@ void ProcessListenerMessage(Ptr msg) { break; case LTT_MSG_RESET: MasterExit = 1; + if (protoBlock.msg_id == UINT64_MAX) { + MasterExit = 2; + } break; case LTT_MSG_CODE: { auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block"); diff --git a/game/main.cpp b/game/main.cpp index c0a1fc30fc..e6a1d3c257 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -10,7 +10,9 @@ int main(int argc, char** argv) { while (true) { // run the runtime in a loop so we can reset the game and have it restart cleanly printf("gk %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); - exec_runtime(argc, argv); + if (exec_runtime(argc, argv) == 2) { + return 0; + } } return 0; } \ No newline at end of file diff --git a/game/runtime.cpp b/game/runtime.cpp index 379898a9a1..4fa7a585e2 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -229,7 +229,7 @@ void iop_runner(SystemThreadInterface& iface) { * Main function to launch the runtime. * Arguments are currently ignored. */ -void exec_runtime(int argc, char** argv) { +u32 exec_runtime(int argc, char** argv) { (void)argc; (void)argv; @@ -262,5 +262,6 @@ void exec_runtime(int argc, char** argv) { // join and exit tm.join(); - printf("GOAL Runtime Shutdown\n"); + printf("GOAL Runtime Shutdown (code %d)\n", MasterExit); + return MasterExit; } diff --git a/game/runtime.h b/game/runtime.h index b252447ada..f2121d2c4b 100644 --- a/game/runtime.h +++ b/game/runtime.h @@ -9,6 +9,6 @@ #include "common/common_types.h" extern u8* g_ee_main_mem; -void exec_runtime(int argc, char** argv); +u32 exec_runtime(int argc, char** argv); #endif // JAK1_RUNTIME_H diff --git a/goal_src/test/test-return-integer-1.gc b/goal_src/test/test-return-integer-1.gc new file mode 100644 index 0000000000..6185b4d3b4 --- /dev/null +++ b/goal_src/test/test-return-integer-1.gc @@ -0,0 +1,2 @@ +; simply return an integer +#x123456789 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-2.gc b/goal_src/test/test-return-integer-2.gc new file mode 100644 index 0000000000..254b43b371 --- /dev/null +++ b/goal_src/test/test-return-integer-2.gc @@ -0,0 +1 @@ +#x17 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-3.gc b/goal_src/test/test-return-integer-3.gc new file mode 100644 index 0000000000..e85c88864c --- /dev/null +++ b/goal_src/test/test-return-integer-3.gc @@ -0,0 +1 @@ +-17 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-4.gc b/goal_src/test/test-return-integer-4.gc new file mode 100644 index 0000000000..f6712382ee --- /dev/null +++ b/goal_src/test/test-return-integer-4.gc @@ -0,0 +1 @@ +-2147483648 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-5.gc b/goal_src/test/test-return-integer-5.gc new file mode 100644 index 0000000000..0a0dce03c3 --- /dev/null +++ b/goal_src/test/test-return-integer-5.gc @@ -0,0 +1 @@ +-2147483649 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-6.gc b/goal_src/test/test-return-integer-6.gc new file mode 100644 index 0000000000..c227083464 --- /dev/null +++ b/goal_src/test/test-return-integer-6.gc @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/goal_src/test/test-return-integer-7.gc b/goal_src/test/test-return-integer-7.gc new file mode 100644 index 0000000000..bf79039cdd --- /dev/null +++ b/goal_src/test/test-return-integer-7.gc @@ -0,0 +1 @@ +-123 \ No newline at end of file diff --git a/goalc/compiler/CodeGenerator.cpp b/goalc/compiler/CodeGenerator.cpp index 2f78d6224d..f196de9d40 100644 --- a/goalc/compiler/CodeGenerator.cpp +++ b/goalc/compiler/CodeGenerator.cpp @@ -1,3 +1,117 @@ - - #include "CodeGenerator.h" +#include "goalc/emitter/IGen.h" +#include "IR.h" + +using namespace emitter; +constexpr int GPR_SIZE = 8; +constexpr int XMM_SIZE = 16; + +CodeGenerator::CodeGenerator(FileEnv* env) : m_fe(env) {} + +std::vector CodeGenerator::run() { + // todo, static objects + + for (auto& f : m_fe->functions()) { + do_function(f.get()); + } + + return m_gen.generate_data_v3().to_vector(); +} + +void CodeGenerator::do_function(FunctionEnv* env) { + auto f_rec = m_gen.add_function_to_seg(env->segment); // todo, extra alignment settings + auto& ri = emitter::gRegInfo; + const auto& allocs = env->alloc_result(); + + // compute how much stack we will use + int stack_offset = 0; + + // back up xmms + for (auto& saved_reg : allocs.used_saved_regs) { + if (saved_reg.is_xmm()) { + m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm8s(RSP, XMM_SIZE)); + m_gen.add_instr_no_ir(f_rec, IGen::store128_gpr64_xmm128(RSP, saved_reg)); + stack_offset += XMM_SIZE; + } + } + + // back up gprs + for (auto& saved_reg : allocs.used_saved_regs) { + if (saved_reg.is_gpr()) { + m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(saved_reg)); + stack_offset += GPR_SIZE; + } + } + + bool bonus_push = false; + int manually_added_stack_offset = GPR_SIZE * allocs.stack_slots; + stack_offset += manually_added_stack_offset; + + if (manually_added_stack_offset || allocs.needs_aligned_stack_for_spills || + env->needs_aligned_stack()) { + if (!(stack_offset & 15)) { + if (manually_added_stack_offset) { + manually_added_stack_offset += 8; + } else { + bonus_push = true; + m_gen.add_instr_no_ir(f_rec, IGen::push_gpr64(ri.get_saved_gpr(0))); + } + stack_offset += 8; + } + + assert(stack_offset & 15); + + if (manually_added_stack_offset) { + m_gen.add_instr_no_ir(f_rec, IGen::sub_gpr64_imm(RSP, manually_added_stack_offset)); + } + } + + // TODO EMIT FUNCTIONS + for (int ir_idx = 0; ir_idx < int(env->code().size()); ir_idx++) { + auto& ir = env->code().at(ir_idx); + auto i_rec = m_gen.add_ir(f_rec); + + auto& bonus = allocs.stack_ops.at(ir_idx); + for (auto& op : bonus.ops) { + if (op.load) { + assert(false); + } + } + ir->do_codegen(&m_gen, allocs, i_rec); + for (auto& op : bonus.ops) { + if (op.store) { + assert(false); + } + } + } + + // EPILOGUE + if (manually_added_stack_offset || allocs.needs_aligned_stack_for_spills || + env->needs_aligned_stack()) { + if (manually_added_stack_offset) { + m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm(RSP, manually_added_stack_offset)); + } + + if (bonus_push) { + assert(!manually_added_stack_offset); + m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(ri.get_saved_gpr(0))); + } + } + + for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) { + auto& saved_reg = allocs.used_saved_regs.at(i); + if (saved_reg.is_gpr()) { + m_gen.add_instr_no_ir(f_rec, IGen::pop_gpr64(saved_reg)); + } + } + + for (int i = int(allocs.used_saved_regs.size()); i-- > 0;) { + auto& saved_reg = allocs.used_saved_regs.at(i); + if (saved_reg.is_xmm()) { + m_gen.add_instr_no_ir(f_rec, IGen::load128_xmm128_gpr64(saved_reg, RSP)); + m_gen.add_instr_no_ir(f_rec, IGen::add_gpr64_imm8s(RSP, XMM_SIZE)); + } + } + + m_gen.add_instr_no_ir(f_rec, IGen::ret()); +} \ No newline at end of file diff --git a/goalc/compiler/CodeGenerator.h b/goalc/compiler/CodeGenerator.h index 6d1ca719f7..cbd1f4ee25 100644 --- a/goalc/compiler/CodeGenerator.h +++ b/goalc/compiler/CodeGenerator.h @@ -3,6 +3,18 @@ #ifndef JAK_CODEGENERATOR_H #define JAK_CODEGENERATOR_H -class CodeGenerator {}; +#include "Env.h" +#include "goalc/emitter/ObjectGenerator.h" + +class CodeGenerator { + public: + CodeGenerator(FileEnv* env); + std::vector run(); + + private: + void do_function(FunctionEnv* env); + emitter::ObjectGenerator m_gen; + FileEnv* m_fe; +}; #endif // JAK_CODEGENERATOR_H diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 240ded2502..fe2106f437 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -1,6 +1,9 @@ #include "Compiler.h" #include "goalc/logger/Logger.h" #include "common/link_types.h" +#include "IR.h" +#include "goalc/regalloc/allocate.h" +#include "unistd.h" using namespace goos; @@ -14,6 +17,7 @@ Compiler::Compiler() { } void Compiler::execute_repl() { + m_listener.connect_to_target(); while (!m_want_exit) { try { // 1). get a line from the user (READ) @@ -29,19 +33,19 @@ void Compiler::execute_repl() { auto obj_file = compile_object_file("repl", code, m_listener.is_connected()); obj_file->debug_print_tl(); - // // 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"); - // } - // } + // 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()); @@ -126,3 +130,59 @@ void Compiler::ice(const std::string& error) { gLogger.log(MSG_ICE, "[ICE] %s\n", error.c_str()); throw std::runtime_error("ICE"); } + +void Compiler::color_object_file(FileEnv* env) { + for (auto& f : env->functions()) { + AllocationInput input; + for (auto& i : f->code()) { + input.instructions.push_back(i->to_rai()); + input.debug_instruction_names.push_back(i->print()); + } + input.max_vars = f->max_vars(); + input.constraints = f->constraints(); + + // for now... + input.debug_settings.print_input = true; + input.debug_settings.print_result = true; + input.debug_settings.print_analysis = true; + + f->set_allocations(allocate_registers(input)); + } +} + +std::vector Compiler::codegen_object_file(FileEnv* env) { + CodeGenerator gen(env); + return gen.run(); +} + +std::vector Compiler::run_test(const std::string& source_code) { + if (!m_listener.is_connected()) { + for (int i = 0; i < 1000; i++) { + m_listener.connect_to_target(); + usleep(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(); +} + +void Compiler::shutdown_target() { + if (m_listener.is_connected()) { + m_listener.send_reset(true); + } +} \ No newline at end of file diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index a491416a35..dc5f67f24d 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -5,6 +5,7 @@ #include "Env.h" #include "goalc/listener/Listener.h" #include "goalc/goos/Interpreter.h" +#include "goalc/compiler/IR.h" class Compiler { public: @@ -21,14 +22,19 @@ class Compiler { void ice(const std::string& err); None* get_none() { return m_none.get(); } + std::vector run_test(const std::string& source_code); + void shutdown_target(); + private: void init_logger(); 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); + void color_object_file(FileEnv* env); + std::vector codegen_object_file(FileEnv* env); - void for_each_in_list(const goos::Object& list, const std::function& f); - + void for_each_in_list(const goos::Object& list, + const std::function& f); goos::Arguments get_va(const goos::Object& form, const goos::Object& rest); void va_check( diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index dab3d75a76..e6a7b500b6 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -1,5 +1,6 @@ #include #include "Env.h" +#include "IR.h" /////////////////// // Env @@ -93,12 +94,12 @@ Val* GlobalEnv::lexical_lookup(goos::Object sym) { return nullptr; } -BlockEnv * GlobalEnv::find_block(const std::string& name) { +BlockEnv* GlobalEnv::find_block(const std::string& name) { (void)name; return nullptr; } -FileEnv * GlobalEnv::add_file(std::string name) { +FileEnv* GlobalEnv::add_file(std::string name) { m_files.push_back(std::make_unique(this, std::move(name))); return m_files.back().get(); } @@ -142,15 +143,15 @@ void FileEnv::add_top_level_function(std::unique_ptr fe) { m_top_level_func = m_functions.back().get(); } -NoEmitEnv * FileEnv::add_no_emit_env() { +NoEmitEnv* FileEnv::add_no_emit_env() { assert(!m_no_emit_env); m_no_emit_env = std::make_unique(this); return m_no_emit_env.get(); } void FileEnv::debug_print_tl() { - if(m_top_level_func) { - for(auto& code : m_top_level_func->code()) { + if (m_top_level_func) { + for (auto& code : m_top_level_func->code()) { fmt::print("{}\n", code->print()); } } else { @@ -162,7 +163,8 @@ void FileEnv::debug_print_tl() { // FunctionEnv /////////////////// -FunctionEnv::FunctionEnv(Env* parent, std::string name) : DeclareEnv(parent), m_name(std::move(name)){} +FunctionEnv::FunctionEnv(Env* parent, std::string name) + : DeclareEnv(parent), m_name(std::move(name)) {} std::string FunctionEnv::print() { return "function-" + m_name; @@ -176,7 +178,7 @@ void FunctionEnv::finish() { // todo resolve gotos } -RegVal * FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { +RegVal* FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { IRegister ireg; ireg.kind = kind; ireg.id = m_iregs.size(); diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index bfef83ffc1..056c2aa3c1 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -11,13 +11,15 @@ #include #include #include "common/type_system/TypeSpec.h" +#include "goalc/regalloc/allocate.h" #include "goalc/goos/Object.h" -#include "IR.h" +//#include "IR.h" #include "Label.h" #include "Val.h" class FileEnv; class BlockEnv; +class IR; /*! * Parent class for Env's @@ -84,6 +86,7 @@ class FileEnv : public Env { void add_top_level_function(std::unique_ptr fe); NoEmitEnv* add_no_emit_env(); void debug_print_tl(); + const std::vector>& functions() { return m_functions; } // todo - is_empty ~FileEnv() = default; @@ -118,11 +121,18 @@ class FunctionEnv : public DeclareEnv { public: FunctionEnv(Env* parent, std::string name); std::string print() override; - void set_segment(int seg) { m_segment = seg; } + void set_segment(int seg) { segment = seg; } void emit(std::unique_ptr ir) override; void finish(); RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind) override; const std::vector>& code() { return m_code; } + int max_vars() const { return m_iregs.size(); } + const std::vector& constraints() { return m_constraints; } + void set_allocations(const AllocationResult& result) { m_regalloc_result = result; } + + const AllocationResult& alloc_result() { return m_regalloc_result; } + + bool needs_aligned_stack() const { return m_aligned_stack_required; } template T* alloc_val(Args&&... args) { @@ -130,7 +140,7 @@ class FunctionEnv : public DeclareEnv { m_vals.push_back(std::move(new_obj)); return (T*)m_vals.back().get(); } - + int segment = -1; protected: std::string m_name; @@ -144,7 +154,6 @@ class FunctionEnv : public DeclareEnv { std::string m_method_of_type_name = "#f"; bool m_aligned_stack_required = false; - int m_segment = -1; std::unordered_map m_params; }; @@ -154,6 +163,7 @@ class BlockEnv : public Env { BlockEnv(Env* parent, std::string name); std::string print() override; BlockEnv* find_block(const std::string& name) override; + protected: std::string m_name; Label* m_end_label = nullptr; @@ -173,20 +183,17 @@ class LabelEnv : public Env { std::unordered_map m_labels; }; -class WithInlineEnv : public Env { +class WithInlineEnv : public Env {}; -}; +class SymbolMacroEnv : public Env {}; -class SymbolMacroEnv : public Env { - -}; - -template +template T* get_parent_env_of_type(Env* in) { - for(;;) { + for (;;) { auto attempt = dynamic_cast(in); - if(attempt) return attempt; - if(dynamic_cast(in)) { + if (attempt) + return attempt; + if (dynamic_cast(in)) { return nullptr; } in = in->parent(); diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 287bf6348b..71e17bf7fc 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -1,6 +1,18 @@ #include "IR.h" +#include "goalc/emitter/IGen.h" -IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value) : m_return_reg(return_reg), m_value(value) {} +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 + +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()); } @@ -9,15 +21,15 @@ 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 + if (m_value->ireg().kind == m_return_reg->ireg().kind) { + rai.is_move = true; // only true if we aren't moving from register kind to register kind } return rai; } void IR_Return::add_constraints(std::vector* constraints, int my_id) { IRegConstraint c; - if(dynamic_cast(m_return_reg)){ + if (dynamic_cast(m_return_reg)) { return; } @@ -27,10 +39,22 @@ void IR_Return::add_constraints(std::vector* constraints, int my constraints->push_back(c); } -IR_LoadConstant64::IR_LoadConstant64(const RegVal* dest, u64 value) : m_dest(dest), m_value(value) { +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); + } } +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); } @@ -39,4 +63,11 @@ 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); } \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 386e368b79..be5e6bd523 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -5,12 +5,15 @@ #include "CodeGenerator.h" #include "goalc/regalloc/allocate.h" #include "Val.h" +#include "goalc/emitter/ObjectGenerator.h" class IR { public: virtual std::string print() = 0; virtual RegAllocInstr to_rai() = 0; -// virtual void do_codegen(CodeGenerator* gen) = 0; + virtual void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) = 0; virtual void add_constraints(std::vector* constraints, int my_id) { (void)constraints; (void)my_id; @@ -29,7 +32,9 @@ class IR_Return : public IR { std::string print() override; RegAllocInstr to_rai() override; void add_constraints(std::vector* constraints, int my_id) override; -// void do_codegen(CodeGenerator* gen) override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; const RegVal* value() { return m_value; } protected: @@ -42,7 +47,9 @@ class IR_LoadConstant64 : public IR { IR_LoadConstant64(const RegVal* dest, u64 value); std::string print() override; RegAllocInstr to_rai() override; -// void do_codegen(CodeGenerator* gen) override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; protected: const RegVal* m_dest = nullptr; diff --git a/goalc/compiler/Label.h b/goalc/compiler/Label.h index 025f4b22fa..0307a723f1 100644 --- a/goalc/compiler/Label.h +++ b/goalc/compiler/Label.h @@ -3,8 +3,6 @@ #ifndef JAK_LABEL_H #define JAK_LABEL_H -struct Label { - -}; +struct Label {}; #endif // JAK_LABEL_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 540c1be629..ed40906c93 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -1,4 +1,5 @@ #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; @@ -82,15 +83,16 @@ void Compiler::va_check( } } -void Compiler::for_each_in_list(const goos::Object& list, const std::function& f) { +void Compiler::for_each_in_list(const goos::Object& list, + const std::function& f) { const goos::Object* iter = &list; - while(iter->is_pair()) { + while (iter->is_pair()) { auto lap = iter->as_pair(); f(lap->car); iter = &lap->cdr; } - if(!iter->is_empty_list()) { + if (!iter->is_empty_list()) { throw_compile_error(list, "invalid list in for_each_in_list"); } } \ No newline at end of file diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index 590d411392..c0c4f2e72e 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -1,12 +1,13 @@ #include "Val.h" #include "Env.h" +#include "IR.h" /*! * Fallback to_gpr if a more optimized one is not provided. */ const RegVal* Val::to_gpr(FunctionEnv* fe) const { auto rv = to_reg(fe); - if(rv->ireg().kind == emitter::RegKind::GPR) { + if (rv->ireg().kind == emitter::RegKind::GPR) { return rv; } else { throw std::runtime_error("Val::to_gpr NYI"); // todo @@ -21,31 +22,30 @@ const RegVal* Val::to_xmm(FunctionEnv* fe) const { throw std::runtime_error("Val::to_xmm NYI"); // todo } - const RegVal* RegVal::to_reg(FunctionEnv* fe) const { (void)fe; return this; } -const RegVal * RegVal::to_gpr(FunctionEnv* fe) const { +const RegVal* RegVal::to_gpr(FunctionEnv* fe) const { (void)fe; - if(m_ireg.kind == emitter::RegKind::GPR) { + if (m_ireg.kind == emitter::RegKind::GPR) { return this; } else { - throw std::runtime_error("RegVal::to_gpr NYI"); // todo + throw std::runtime_error("RegVal::to_gpr NYI"); // todo } } -const RegVal * RegVal::to_xmm(FunctionEnv* fe) const { +const RegVal* RegVal::to_xmm(FunctionEnv* fe) const { (void)fe; - if(m_ireg.kind == emitter::RegKind::XMM) { + if (m_ireg.kind == emitter::RegKind::XMM) { return this; } else { - throw std::runtime_error("RegVal::to_xmm NYI"); // todo + throw std::runtime_error("RegVal::to_xmm NYI"); // todo } } -const RegVal * IntegerConstantVal::to_reg(FunctionEnv* fe) const { +const RegVal* IntegerConstantVal::to_reg(FunctionEnv* fe) const { auto rv = fe->make_gpr(m_ts); fe->emit(std::make_unique(rv, m_value)); return rv; diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 1b967a39d9..00ff0aa74c 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -1,139 +1,141 @@ #include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" /*! * Main table for compiler forms */ -static const std::unordered_map 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 +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}, + // {"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}, -// {"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}, -// -// -// -// {"listen-to-target", &Compiler::compile_listen_to_target}, -// {"reset-target", &Compiler::compile_reset_target}, -// {":status", &Compiler::compile_poke}, -// -// // temporary testing hacks... -// {"send-test", &Compiler::compile_send_test_data}, - }; + // {"asm-file", &Compiler::compile_asm_file}, + // {"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}, + // + // + // + // {"listen-to-target", &Compiler::compile_listen_to_target}, + // {"reset-target", &Compiler::compile_reset_target}, + // {":status", &Compiler::compile_poke}, + // + // // temporary testing hacks... + // {"send-test", &Compiler::compile_send_test_data}, +}; -Val * Compiler::compile(const goos::Object& code, Env* env) { - switch(code.type) { +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: @@ -144,34 +146,33 @@ Val * Compiler::compile(const goos::Object& code, Env* env) { return get_none(); } -Val * Compiler::compile_pair(const goos::Object& code, Env* env) { +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()) { + + 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()) { + if (kv_gfs != goal_forms.end()) { return ((*this).*(kv_gfs->second))(code, rest, env); } // todo macro // 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) { +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) { +Val* Compiler::compile_integer(s64 value, Env* env) { auto fe = get_parent_env_of_type(env); return fe->alloc_val(m_ts.make_typespec("int"), value); } - diff --git a/goalc/compiler/compilation/Block.cpp b/goalc/compiler/compilation/Block.cpp index e20da3e492..404d022f71 100644 --- a/goalc/compiler/compilation/Block.cpp +++ b/goalc/compiler/compilation/Block.cpp @@ -1,16 +1,15 @@ #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) { +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) { +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); - }); + for_each_in_list(rest, [&](const Object& o) { result = compile_error_guard(o, env); }); return result; } diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index b8c2cd2f07..c666ac8823 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -1,11 +1,12 @@ #include "goalc/compiler/Compiler.h" +#include "goalc/compiler/IR.h" -Val * Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) { +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(); + if (m_listener.is_connected()) { + m_listener.send_reset(false); } m_want_exit = true; return get_none(); diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 988d61bd78..1f9da29214 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -156,6 +156,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 +466,7 @@ std::vector ObjectGenerator::generate_header_v3() { offset += push_data(N_SEG, result); offset += sizeof(u32) * N_SEG * 4; // 4 u32's per segment - + offset += 4; struct SizeOffset { uint32_t offset, size; }; diff --git a/goalc/emitter/ObjectGenerator.h b/goalc/emitter/ObjectGenerator.h index c7ee449d25..968577cbc3 100644 --- a/goalc/emitter/ObjectGenerator.h +++ b/goalc/emitter/ObjectGenerator.h @@ -44,6 +44,7 @@ class ObjectGenerator { IR_Record add_ir(const FunctionRecord& func); IR_Record get_future_ir_record(const FunctionRecord& func, int ir_id); InstructionRecord add_instr(Instruction inst, IR_Record ir); + void add_instr_no_ir(FunctionRecord func, Instruction inst); StaticRecord add_static_to_seg(int seg, int min_align = 16); void link_instruction_jump(InstructionRecord jump_instr, IR_Record destination); void link_static_type_ptr(StaticRecord rec, int offset, const std::string& type_name); diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index ee9ecea49a..f971dc35b8 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -18,7 +18,7 @@ #include "common/versions.h" using namespace versions; -constexpr bool debug_listener = true; +constexpr bool debug_listener = false; namespace listener { Listener::Listener() { @@ -245,10 +245,24 @@ void Listener::receive_func() { } } -void Listener::send_code(std::vector &code) { +void Listener::record_messages(ListenerMessageKind kind) { + if (filter != ListenerMessageKind::MSG_INVALID) { + printf("[Listener] Already recording!\n"); + } + filter = kind; +} + +std::vector Listener::stop_recording_messages() { + filter = ListenerMessageKind::MSG_INVALID; + auto result = message_record; + message_record.clear(); + return result; +} + +void Listener::send_code(std::vector& code) { got_ack = false; int total_size = code.size() + sizeof(ListenerMessageHeader); - if(total_size > BUFFER_SIZE) { + if (total_size > BUFFER_SIZE) { printf("[ERROR] Listener send_code got too big of a message\n"); return; } @@ -257,7 +271,7 @@ void Listener::send_code(std::vector &code) { 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.proto = 0xe042; // todo don't hardcode header->deci2_header.src = 'H'; header->deci2_header.dst = 'E'; header->msg_size = code.size(); @@ -268,21 +282,21 @@ void Listener::send_code(std::vector &code) { send_buffer(total_size); } -void Listener::send_reset() { - if(!m_connected) { +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.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 = 0; + header->u8 = shutdown ? UINT64_MAX : 0; send_buffer(sizeof(ListenerMessageHeader)); disconnect(); close(socket_fd); @@ -292,25 +306,24 @@ void Listener::send_reset() { void Listener::send_buffer(int sz) { int wrote = 0; - if(debug_listener) { + if (debug_listener) { printf("[L -> T] sending %d bytes...\n", sz); } got_ack = false; waiting_for_ack = true; - while(wrote < sz) { + while (wrote < sz) { auto to_send = std::min(512, sz - wrote); auto x = write(socket_fd, m_buffer + wrote, to_send); wrote += x; } - if(debug_listener) { + if (debug_listener) { printf(" waiting for ack...\n"); } - - if(wait_for_ack()) { - if(debug_listener) { + if (wait_for_ack()) { + if (debug_listener) { printf("ack buff:\n"); printf("%s\n", ack_recv_buff); printf(" OK\n"); @@ -321,13 +334,14 @@ void Listener::send_buffer(int sz) { } bool Listener::wait_for_ack() { - if(!m_connected) { + 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; + for (int i = 0; i < 2000; i++) { + if (got_ack) + return true; usleep(1000); } diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 01f68c576e..38c453c6d4 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -21,20 +21,17 @@ class Listener { ~Listener(); bool connect_to_target(const std::string& ip = "127.0.0.1", int port = DECI2_PORT); void record_messages(ListenerMessageKind kind); - void stop_recording_messages(); + std::vector stop_recording_messages(); bool is_connected() const; - void send_reset(); + void send_reset(bool shutdown); void disconnect(); - void send_code(std::vector &code); - bool most_recent_send_was_acked() { - return got_ack; - } + void send_code(std::vector& code); + bool most_recent_send_was_acked() { return got_ack; } private: void send_buffer(int sz); bool wait_for_ack(); - char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? bool receive_thread_running = false; //! is the receive thread unjoined? diff --git a/goalc/logger/Logger.cpp b/goalc/logger/Logger.cpp index 684d406a6a..63e3596d22 100644 --- a/goalc/logger/Logger.cpp +++ b/goalc/logger/Logger.cpp @@ -4,6 +4,7 @@ void Logger::close() { if (fp) { fclose(fp); + fp = nullptr; } } diff --git a/goalc/regalloc/allocate.cpp b/goalc/regalloc/allocate.cpp index 8e6bf798ea..8b4f5a26f3 100644 --- a/goalc/regalloc/allocate.cpp +++ b/goalc/regalloc/allocate.cpp @@ -12,10 +12,12 @@ namespace { * Print out the input data for debugging. */ void print_allocate_input(const AllocationInput& in) { - fmt::print("[RegAlloc] Debug Input:\n"); + fmt::print("[RegAlloc] Debug Input Program:\n"); if (in.instructions.size() == in.debug_instruction_names.size()) { for (size_t i = 0; i < in.instructions.size(); i++) { - fmt::print(" [{:3d}] {:30} -> {:30}\n", in.debug_instruction_names.at(i), + // fmt::print(" [{}] {} -> {}\n", in.debug_instruction_names.at(i), + // in.instructions.at(i).print()); + fmt::print(" [{:3d}] {:30} -> {:30}\n", i, in.debug_instruction_names.at(i), in.instructions.at(i).print()); } } else { @@ -23,6 +25,7 @@ void print_allocate_input(const AllocationInput& in) { fmt::print(" [{:3d}] {}\n", instruction.print()); } } + fmt::print("[RegAlloc] Debug Input Constraints:\n"); for (const auto& c : in.constraints) { fmt::print(" {}\n", c.to_string()); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8173c9820c..6987685866 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(goalc-test test_emitter_loads_and_store.cpp test_emitter_xmm32.cpp test_emitter_integer_math.cpp + test_compiler_and_runtime.cpp ) IF (WIN32) diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp new file mode 100644 index 0000000000..a03b01177d --- /dev/null +++ b/test/test_compiler_and_runtime.cpp @@ -0,0 +1,109 @@ +#include + +#include "gtest/gtest.h" +#include "game/runtime.h" +#include "goalc/listener/Listener.h" +#include "goalc/compiler/Compiler.h" + +TEST(CompilerAndRuntime, ConstructCompiler) { + Compiler compiler; +} + +TEST(CompilerAndRuntime, StartRuntime) { + std::thread runtime_thread([]() { exec_runtime(0, nullptr); }); + + listener::Listener listener; + while (!listener.is_connected()) { + listener.connect_to_target(); + usleep(1000); + } + + listener.send_reset(true); + runtime_thread.join(); +} + +TEST(CompilerAndRuntime, SendProgram) { + std::thread runtime_thread([]() { exec_runtime(0, nullptr); }); + Compiler compiler; + compiler.run_test("goal_src/test/test-return-integer-1.gc"); + compiler.shutdown_target(); + runtime_thread.join(); +} + +namespace { +std::string escaped_string(const std::string& in) { + std::string result; + for (auto x : in) { + switch (x) { + case '\n': + result.append("\\n"); + break; + case '\t': + result.append("\\t"); + break; + default: + result.push_back(x); + } + } + return result; +} + +struct CompilerTestRunner { + Compiler* c = nullptr; + + struct Test { + std::vector expected, actual; + std::string test_name; + }; + + std::vector tests; + + void run_test(const std::string& test_file, const std::vector& expected) { + auto result = c->run_test("goal_src/test/" + test_file); + EXPECT_EQ(result, expected); + tests.push_back({expected, result, test_file}); + } + + void print_summary() { + fmt::print("~~ Compiler Test Summary for {} tests... ~~\n", tests.size()); + int passed = 0; + for (auto& test : tests) { + if (test.expected == test.actual) { + fmt::print("[{:30}] PASS!\n", test.test_name); + passed++; + } else { + fmt::print("[{:30}] 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"}); + + compiler.shutdown_target(); + runtime_thread.join(); + runner.print_summary(); +} \ No newline at end of file From 1de0cbb6f60bc4bdb6022a205c5597ac3783ba28 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 6 Sep 2020 17:42:20 -0400 Subject: [PATCH 04/34] enable macros --- goal_src/goal-lib.gc | 9 +++++ goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.cpp | 24 +++++++----- goalc/compiler/Compiler.h | 6 +++ goalc/compiler/Env.cpp | 4 ++ goalc/compiler/Env.h | 3 +- goalc/compiler/compilation/Atoms.cpp | 10 +++-- .../compiler/compilation/CompilerControl.cpp | 12 ++++++ goalc/compiler/compilation/Macro.cpp | 37 +++++++++++++++++++ goalc/goos/Interpreter.h | 19 +++++----- 10 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 goal_src/goal-lib.gc create mode 100644 goalc/compiler/compilation/Macro.cpp diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc new file mode 100644 index 0000000000..af0e1fad03 --- /dev/null +++ b/goal_src/goal-lib.gc @@ -0,0 +1,9 @@ +;; 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) + ) \ No newline at end of file diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index c63f22b516..63cdb110eb 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(compiler compiler/compilation/Atoms.cpp compiler/compilation/CompilerControl.cpp compiler/compilation/Block.cpp + compiler/compilation/Macro.cpp compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index fe2106f437..d9d062e9b1 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -14,10 +14,12 @@ Compiler::Compiler() { m_none = std::make_unique(m_ts.make_typespec("none")); // todo - compile library + Object library_code = m_goos.reader.read_from_file("goal_src/goal-lib.gc"); + compile_object_file("goal-lib", library_code, false); } void Compiler::execute_repl() { - m_listener.connect_to_target(); + m_listener.connect_to_target(); // todo, remove while (!m_want_exit) { try { // 1). get a line from the user (READ) @@ -33,17 +35,19 @@ void Compiler::execute_repl() { auto obj_file = compile_object_file("repl", code, m_listener.is_connected()); obj_file->debug_print_tl(); - // 3). color - color_object_file(obj_file); + if (!obj_file->is_empty()) { + // 3). color + color_object_file(obj_file); - // 4). codegen - auto data = codegen_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"); + // 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"); + } } } diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index dc5f67f24d..16722ea814 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -27,6 +27,11 @@ class Compiler { 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); @@ -55,6 +60,7 @@ class Compiler { Val* compile_exit(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_begin(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_seval(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index e6a7b500b6..50bc153280 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -159,6 +159,10 @@ void FileEnv::debug_print_tl() { } } +bool FileEnv::is_empty() { + return m_functions.size() == 1 && m_functions.front().get() == m_top_level_func && + m_top_level_func->code().empty(); +} /////////////////// // FunctionEnv /////////////////// diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index 056c2aa3c1..8a3c7baad6 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -89,6 +89,7 @@ class FileEnv : public Env { const std::vector>& functions() { return m_functions; } // todo - is_empty + bool is_empty(); ~FileEnv() = default; protected: @@ -141,6 +142,7 @@ class FunctionEnv : public DeclareEnv { return (T*)m_vals.back().get(); } int segment = -1; + std::string method_of_type_name = "#f"; protected: std::string m_name; @@ -152,7 +154,6 @@ class FunctionEnv : public DeclareEnv { AllocationResult m_regalloc_result; bool m_is_asm_func = false; - std::string m_method_of_type_name = "#f"; bool m_aligned_stack_required = false; std::unordered_map m_params; diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 00ff0aa74c..7fd4039d52 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -26,7 +26,7 @@ static const std::unordered_map< // // // COMPILER CONTROL // {"gs", &Compiler::compile_gs}, - {":exit", &Compiler::compile_exit} + {":exit", &Compiler::compile_exit}, // {"asm-file", &Compiler::compile_asm_file}, // {"test", &Compiler::compile_test}, // {"in-package", &Compiler::compile_in_package}, @@ -34,7 +34,7 @@ static const std::unordered_map< // // CONDITIONAL COMPILATION // {"#cond", &Compiler::compile_gscond}, // {"defglobalconstant", &Compiler::compile_defglobalconstant}, - // {"seval", &Compiler::compile_seval}, + {"seval", &Compiler::compile_seval}, // // // CONTROL FLOW // {"cond", &Compiler::compile_cond}, @@ -159,7 +159,11 @@ Val* Compiler::compile_pair(const goos::Object& code, Env* env) { return ((*this).*(kv_gfs->second))(code, rest, env); } - // todo macro + goos::Object macro_obj; + if (try_getting_macro_from_goos(head, ¯o_obj)) { + return compile_goos_macro(code, macro_obj, rest, env); + } + // todo enum } diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index c666ac8823..2d83f48ffd 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -10,4 +10,16 @@ Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, } 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(); } \ No newline at end of file diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp new file mode 100644 index 0000000000..275207c197 --- /dev/null +++ b/goalc/compiler/compilation/Macro.cpp @@ -0,0 +1,37 @@ +#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(); + m_goos.set_args_in_env(o, args, macro->args, mac_env); + m_goos.goal_to_goos.enclosing_method_type = + get_parent_env_of_type(env)->method_of_type_name; + auto goos_result = m_goos.eval_list_return_last(macro->body, macro->body, mac_env); + m_goos.goal_to_goos.reset(); + return compile_error_guard(goos_result, env); +} \ No newline at end of file diff --git a/goalc/goos/Interpreter.h b/goalc/goos/Interpreter.h index b19903e68e..d58f46670e 100644 --- a/goalc/goos/Interpreter.h +++ b/goalc/goos/Interpreter.h @@ -23,6 +23,15 @@ class Interpreter { Object eval(Object obj, const std::shared_ptr& env); Object intern(const std::string& name); void disable_printfs(); + Object eval_symbol(const Object& sym, const std::shared_ptr& env); + Arguments get_args(const Object& form, const Object& rest, const ArgumentSpec& spec); + void set_args_in_env(const Object& form, + const Arguments& args, + const ArgumentSpec& arg_spec, + const std::shared_ptr& env); + Object eval_list_return_last(const Object& form, + Object rest, + const std::shared_ptr& env); Reader reader; Object global_environment; @@ -47,14 +56,9 @@ class Interpreter { const std::unordered_map>>& named); Object eval_pair(const Object& o, const std::shared_ptr& env); - Object eval_symbol(const Object& sym, const std::shared_ptr& env); - Arguments get_args(const Object& form, const Object& rest, const ArgumentSpec& spec); void eval_args(Arguments* args, const std::shared_ptr& env); ArgumentSpec parse_arg_spec(const Object& form, Object& rest); - Object eval_list_return_last(const Object& form, - Object rest, - const std::shared_ptr& env); Object quasiquote_helper(const Object& form, const std::shared_ptr& env); IntType number_to_integer(const Object& obj); @@ -206,11 +210,6 @@ class Interpreter { const Object& rest, const std::shared_ptr& env); - void set_args_in_env(const Object& form, - const Arguments& args, - const ArgumentSpec& arg_spec, - const std::shared_ptr& env); - bool want_exit = false; bool disable_printing = false; From ee4eb9f12823b0b4c8ad4a653dbdff1f13492dad Mon Sep 17 00:00:00 2001 From: water Date: Mon, 7 Sep 2020 13:28:16 -0400 Subject: [PATCH 05/34] add some basic symbol stuff --- doc/goal_todo.md | 19 +++ game/kernel/asm_funcs.asm | 8 +- game/kernel/kboot.cpp | 20 ++- game/kernel/kboot.h | 2 + game/kernel/kmachine.cpp | 3 +- game/kernel/kscheme.cpp | 44 +++--- goal_src/goal-lib.gc | 44 ++++++ .../test/test-conditional-compilation-1.gc | 12 ++ goal_src/test/test-define-1.gc | 10 ++ goal_src/test/test-get-symbol-1.gc | 1 + goal_src/test/test-get-symbol-2.gc | 1 + goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.cpp | 55 ++++--- goalc/compiler/Compiler.h | 33 ++++- goalc/compiler/Env.h | 3 +- goalc/compiler/IR.cpp | 98 +++++++++++++ goalc/compiler/IR.h | 43 ++++++ goalc/compiler/Util.cpp | 8 + goalc/compiler/Val.cpp | 24 ++- goalc/compiler/Val.h | 33 +++-- goalc/compiler/compilation/Atoms.cpp | 50 ++++++- .../compiler/compilation/CompilerControl.cpp | 138 ++++++++++++++++++ goalc/compiler/compilation/Define.cpp | 41 ++++++ goalc/compiler/compilation/Macro.cpp | 52 +++++++ goalc/emitter/ObjectFileData.cpp | 5 + goalc/goos/Interpreter.cpp | 4 +- goalc/goos/Interpreter.h | 1 + goalc/listener/Listener.cpp | 34 ++++- goalc/listener/Listener.h | 5 +- goalc/util/CMakeLists.txt | 2 +- goalc/util/Timer.cpp | 54 +++++++ goalc/util/Timer.h | 47 ++++++ goalc/util/file_io.cpp | 13 ++ goalc/util/file_io.h | 1 + test/test_compiler_and_runtime.cpp | 13 +- 35 files changed, 841 insertions(+), 81 deletions(-) create mode 100644 doc/goal_todo.md create mode 100644 goal_src/test/test-conditional-compilation-1.gc create mode 100644 goal_src/test/test-define-1.gc create mode 100644 goal_src/test/test-get-symbol-1.gc create mode 100644 goal_src/test/test-get-symbol-2.gc create mode 100644 goalc/compiler/compilation/Define.cpp create mode 100644 goalc/util/Timer.cpp create mode 100644 goalc/util/Timer.h diff --git a/doc/goal_todo.md b/doc/goal_todo.md new file mode 100644 index 0000000000..148bdb50b2 --- /dev/null +++ b/doc/goal_todo.md @@ -0,0 +1,19 @@ +Done (has documentation) +- `e` +- `:exit` +- `asm-file`, `m`, `ml` +- `listen-to-target`, `reset-target`, `:status`, `lt`, `r` + +Done (needs documentation) +- `top-level` +- `begin` +- `seval` +- `#cond`, `#when`, `#unless` + +- Macro System + + + + +Todo +- Type System diff --git a/game/kernel/asm_funcs.asm b/game/kernel/asm_funcs.asm index 2b41efe59d..c43173753a 100644 --- a/game/kernel/asm_funcs.asm +++ b/game/kernel/asm_funcs.asm @@ -121,9 +121,9 @@ _call_goal_asm_linux: ;; set GOAL function pointer mov r13, rcx ;; offset - mov r15, r8 + mov r14, r8 ;; symbol table - mov r14, r9 + mov r15, r9 ;; call GOAL by function pointer call r13 @@ -165,8 +165,8 @@ _call_goal_asm_win32: mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument - mov r15, [rsp + 144] ;; symbol table - mov r14, [rsp + 152] ;; offset + mov r14, [rsp + 144] ;; symbol table + mov r15, [rsp + 152] ;; offset call r13 diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 9f6f6de06d..b8605b36f2 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -12,6 +12,7 @@ #include "kscheme.h" #include "ksocket.h" #include "klisten.h" +#include "kprint.h" #ifdef _WIN32 #include "Windows.h" @@ -133,7 +134,24 @@ void KernelCheckAndDispatch() { auto old_listener = ListenerFunction->value; // dispatch the kernel //(**kernel_dispatcher)(); - call_goal(Ptr(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem); + + // todo remove. this is added while KERNEL.CGO is broken. + if (MasterUseKernel) { + call_goal(Ptr(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem); + } else { + if (ListenerFunction->value != s7.offset) { + auto cptr = Ptr(ListenerFunction->value).c(); + for (int i = 0; i < 40; i++) { + printf("%x ", cptr[i]); + } + printf("\n"); + auto result = + call_goal(Ptr(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem); + cprintf("%ld\n", result); + ListenerFunction->value = s7.offset; + } + } + // TODO-WINDOWS #ifdef __linux__ ClearPending(); diff --git a/game/kernel/kboot.h b/game/kernel/kboot.h index fd53697f1b..3ac75533e8 100644 --- a/game/kernel/kboot.h +++ b/game/kernel/kboot.h @@ -73,4 +73,6 @@ void KernelCheckAndDispatch(); */ void KernelShutdown(); +constexpr bool MasterUseKernel = false; + #endif // RUNTIME_KBOOT_H diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 4752b90b93..6a030104f8 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -601,7 +601,8 @@ void InitMachineScheme() { intern_from_c("*kernel-boot-level*")->value = intern_from_c(DebugBootLevel).offset; } - if (DiskBoot) { + // todo remove MasterUseKernel + if (DiskBoot && MasterUseKernel) { *EnableMethodSet = (*EnableMethodSet) + 1; load_and_link_dgo_from_c("game", kglobalheap, LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index d2c41a96cb..bec0f1be12 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -958,7 +958,10 @@ uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, * Wrapper around _call_goal_asm for calling a GOAL function from C. */ u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { - auto st_ptr = (void*)((uint8_t*)(offset) + st); + // auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler! + void* st_ptr = (void*)st; + printf("st is 0x%x\n", st); + void* fptr = f.c(); #ifdef __linux__ return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset); @@ -1828,25 +1831,28 @@ s32 InitHeapAndSymbol() { intern_from_c("*boot-video-mode*")->value = 0; // load the kernel! - method_set_symbol->value++; - load_and_link_dgo_from_c("kernel", kglobalheap, - LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, - 0x400000); - method_set_symbol->value--; + // todo, remove MasterUseKernel + if (MasterUseKernel) { + method_set_symbol->value++; + load_and_link_dgo_from_c("kernel", kglobalheap, + LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, + 0x400000); + method_set_symbol->value--; - // check the kernel version! - auto kernel_version = intern_from_c("*kernel-version*")->value; - if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { - MsgErr("\n"); - MsgErr( - "dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the " - "goal> prompt (:mch) then mkee your kernel in linux.\n", - KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13, - (kernel_version >> 3) & 0xffff); - return -1; - } else { - printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13, - (kernel_version >> 3) & 0xffff); + // check the kernel version! + auto kernel_version = intern_from_c("*kernel-version*")->value; + if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { + MsgErr("\n"); + MsgErr( + "dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the " + "goal> prompt (:mch) then mkee your kernel in linux.\n", + KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + return -1; + } else { + printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + } } // setup deci2count for message counter. diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index af0e1fad03..09f71dab94 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -6,4 +6,48 @@ ;; compile, color, load and save a file (defmacro ml (file) `(asm-file ,file :color :load :write) + ) + +(defmacro e () + `(:exit) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CONDITIONAL COMPILATION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro #when (clause &rest body) + `(#cond (,clause ,@body)) + ) + +(defmacro #unless (clause &rest body) + `(#cond ((not ,clause) ,@body)) + ) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TARGET CONTROL +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro lt (&rest args) + ;; shortcut for listen-to-target. also sends a :status command to make sure + ;; all buffers on the target are flushed. + `(begin + (listen-to-target ,@args) + (:status) + ) + ) + +(defmacro r (&rest args) + ;; shortcut to completely reset the target and connect, regardless of current state + `(begin + ;; connect, so we can send reset. if we're already connected, does nothing + (listen-to-target ,@args) + ;; send a reset message, disconnecting us + (reset-target) + ;; establish connection again + (listen-to-target ,@args) + ;; flush buffers + (:status) + ) ) \ No newline at end of file diff --git a/goal_src/test/test-conditional-compilation-1.gc b/goal_src/test/test-conditional-compilation-1.gc new file mode 100644 index 0000000000..27e30228fb --- /dev/null +++ b/goal_src/test/test-conditional-compilation-1.gc @@ -0,0 +1,12 @@ +;; test the use of #cond to evaluate goos expressions at compile time + +(#cond + ((> 2 (+ 2 1)) + 1 + (invalid-code) + ) + + ((< 2 (+ 1 2)) + 3 + ) + ) \ No newline at end of file diff --git a/goal_src/test/test-define-1.gc b/goal_src/test/test-define-1.gc new file mode 100644 index 0000000000..2dfc9036fe --- /dev/null +++ b/goal_src/test/test-define-1.gc @@ -0,0 +1,10 @@ +(define first-var 1) +(define second-var 2) +(define first-var 12) + +(begin + (define first-var 13) + (define second-var 12) + (define first-var 17) + second-var + first-var) diff --git a/goal_src/test/test-get-symbol-1.gc b/goal_src/test/test-get-symbol-1.gc new file mode 100644 index 0000000000..3ac5f6a806 --- /dev/null +++ b/goal_src/test/test-get-symbol-1.gc @@ -0,0 +1 @@ +'#f \ No newline at end of file diff --git a/goal_src/test/test-get-symbol-2.gc b/goal_src/test/test-get-symbol-2.gc new file mode 100644 index 0000000000..8a7c3981c4 --- /dev/null +++ b/goal_src/test/test-get-symbol-2.gc @@ -0,0 +1 @@ +'#t \ No newline at end of file diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 63cdb110eb..1a26b2ec14 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(compiler 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 diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index d9d062e9b1..a728753176 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -19,7 +19,6 @@ Compiler::Compiler() { } void Compiler::execute_repl() { - m_listener.connect_to_target(); // todo, remove while (!m_want_exit) { try { // 1). get a line from the user (READ) @@ -86,6 +85,10 @@ FileEnv* Compiler::compile_object_file(const std::string& name, 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; } @@ -160,33 +163,45 @@ std::vector Compiler::codegen_object_file(FileEnv* env) { } std::vector Compiler::run_test(const std::string& source_code) { - if (!m_listener.is_connected()) { - for (int i = 0; i < 1000; i++) { - m_listener.connect_to_target(); - usleep(10000); - if (m_listener.is_connected()) { - break; + try { + if (!m_listener.is_connected()) { + for (int i = 0; i < 1000; i++) { + m_listener.connect_to_target(); + usleep(10000); + if (m_listener.is_connected()) { + break; + } + } + if (!m_listener.is_connected()) { + throw std::runtime_error("Compiler::run_test couldn't connect!"); } } - 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"); + 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 {}; } - return m_listener.stop_recording_messages(); } 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) { + m_ts.typecheck(expected, actual, error_message, true, true); } \ No newline at end of file diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 16722ea814..9f49720f34 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -35,6 +35,9 @@ class Compiler { Val* compile_pair(const goos::Object& code, Env* env); Val* compile_integer(const goos::Object& code, Env* env); Val* compile_integer(s64 value, Env* env); + Val* compile_symbol(const goos::Object& form, Env* env); + Val* compile_get_symbol_value(const std::string& name, Env* env); + SymbolVal* compile_get_sym_obj(const std::string& name, Env* env); void color_object_file(FileEnv* env); std::vector codegen_object_file(FileEnv* env); @@ -48,6 +51,8 @@ class Compiler { const std::vector>& unnamed, const std::unordered_map>>& named); + std::string as_string(const goos::Object& o); + std::string symbol_string(const goos::Object& o); TypeSystem m_ts; std::unique_ptr m_global_env = nullptr; @@ -55,12 +60,36 @@ class Compiler { bool m_want_exit = false; listener::Listener m_listener; goos::Interpreter m_goos; + std::unordered_map m_symbol_types; + std::unordered_map, goos::Object> m_global_constants; + std::unordered_map, LambdaVal*> m_inlineable_functions; + + void typecheck(const goos::Object& form, + const TypeSpec& expected, + const TypeSpec& actual, + const std::string& error_message = ""); public: - Val* compile_exit(const goos::Object& form, const goos::Object& rest, Env* env); - Val* compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env); + // 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); + + // 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); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index 8a3c7baad6..c81671c6ea 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -88,7 +88,6 @@ class FileEnv : public Env { void debug_print_tl(); const std::vector>& functions() { return m_functions; } - // todo - is_empty bool is_empty(); ~FileEnv() = default; @@ -115,7 +114,7 @@ class DeclareEnv : public Env { 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 - } m_settings; + } settings; }; class FunctionEnv : public DeclareEnv { diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 71e17bf7fc..33afba90b6 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -1,4 +1,6 @@ #include "IR.h" + +#include #include "goalc/emitter/IGen.h" using namespace emitter; @@ -11,6 +13,9 @@ Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_R } } // 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() { @@ -52,6 +57,9 @@ void IR_Return::do_codegen(emitter::ObjectGenerator* gen, } } +///////////////////// +// LoadConstant64 +///////////////////// IR_LoadConstant64::IR_LoadConstant64(const RegVal* dest, u64 value) : m_dest(dest), m_value(value) {} @@ -70,4 +78,94 @@ void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen, 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()); + } } \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index be5e6bd523..b06845e0b3 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -56,4 +56,47 @@ class IR_LoadConstant64 : public IR { 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; +}; + #endif // JAK_IR_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index ed40906c93..435055de1a 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -95,4 +95,12 @@ void Compiler::for_each_in_list(const goos::Object& list, 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; } \ No newline at end of file diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index c0c4f2e72e..06c3e20302 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -5,7 +5,7 @@ /*! * Fallback to_gpr if a more optimized one is not provided. */ -const RegVal* Val::to_gpr(FunctionEnv* fe) const { +RegVal* Val::to_gpr(Env* fe) { auto rv = to_reg(fe); if (rv->ireg().kind == emitter::RegKind::GPR) { return rv; @@ -17,17 +17,17 @@ const RegVal* Val::to_gpr(FunctionEnv* fe) const { /*! * Fallback to_xmm if a more optimized one is not provided. */ -const RegVal* Val::to_xmm(FunctionEnv* fe) const { +RegVal* Val::to_xmm(Env* fe) { (void)fe; throw std::runtime_error("Val::to_xmm NYI"); // todo } -const RegVal* RegVal::to_reg(FunctionEnv* fe) const { +RegVal* RegVal::to_reg(Env* fe) { (void)fe; return this; } -const RegVal* RegVal::to_gpr(FunctionEnv* fe) const { +RegVal* RegVal::to_gpr(Env* fe) { (void)fe; if (m_ireg.kind == emitter::RegKind::GPR) { return this; @@ -36,7 +36,7 @@ const RegVal* RegVal::to_gpr(FunctionEnv* fe) const { } } -const RegVal* RegVal::to_xmm(FunctionEnv* fe) const { +RegVal* RegVal::to_xmm(Env* fe) { (void)fe; if (m_ireg.kind == emitter::RegKind::XMM) { return this; @@ -45,8 +45,20 @@ const RegVal* RegVal::to_xmm(FunctionEnv* fe) const { } } -const RegVal* IntegerConstantVal::to_reg(FunctionEnv* fe) const { +RegVal* IntegerConstantVal::to_reg(Env* fe) { auto rv = fe->make_gpr(m_ts); fe->emit(std::make_unique(rv, m_value)); return rv; +} + +RegVal* SymbolVal::to_reg(Env* fe) { + auto re = fe->make_gpr(m_ts); + fe->emit(std::make_unique(re, m_name)); + return re; +} + +RegVal* SymbolValueVal::to_reg(Env* fe) { + auto re = fe->make_gpr(m_ts); + fe->emit(std::make_unique(re, m_sym, m_sext)); + return re; } \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 9ee9b86f27..1313aa6427 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -15,6 +15,7 @@ #include "Lambda.h" class RegVal; +class Env; class FunctionEnv; /*! @@ -31,12 +32,12 @@ class Val { } virtual std::string print() const = 0; - virtual const RegVal* to_reg(FunctionEnv* fe) const { + virtual RegVal* to_reg(Env* fe) { (void)fe; throw std::runtime_error("to_reg called on invalid Val: " + print()); } - virtual const RegVal* to_gpr(FunctionEnv* fe) const; - virtual const RegVal* to_xmm(FunctionEnv* fe) const; + 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); } @@ -64,9 +65,9 @@ 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(); }; - const RegVal* to_reg(FunctionEnv* fe) const override; - const RegVal* to_gpr(FunctionEnv* fe) const override; - const 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; @@ -79,13 +80,27 @@ class RegVal : public Val { class SymbolVal : public Val { public: SymbolVal(std::string name, TypeSpec ts) : Val(std::move(ts)), m_name(std::move(name)) {} - const std::string& name() { return m_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. @@ -94,10 +109,10 @@ 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; - FunctionEnv* fe = nullptr; }; // Static @@ -111,7 +126,7 @@ 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); } - const RegVal* to_reg(FunctionEnv* fe) const override; + RegVal* to_reg(Env* fe) override; protected: s64 m_value = -1; diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 7fd4039d52..aa66b84e2d 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -27,12 +27,15 @@ static const std::unordered_map< // // COMPILER CONTROL // {"gs", &Compiler::compile_gs}, {":exit", &Compiler::compile_exit}, - // {"asm-file", &Compiler::compile_asm_file}, + {"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}, + {"#cond", &Compiler::compile_gscond}, // {"defglobalconstant", &Compiler::compile_defglobalconstant}, {"seval", &Compiler::compile_seval}, // @@ -41,7 +44,7 @@ static const std::unordered_map< // {"when-goto", &Compiler::compile_when_goto}, // // // DEFINITION - // {"define", &Compiler::compile_define}, + {"define", &Compiler::compile_define}, // {"define-extern", &Compiler::compile_define_extern}, // {"set!", &Compiler::compile_set}, // {"defun-extern", &Compiler::compile_defun_extern}, @@ -70,7 +73,7 @@ static const std::unordered_map< // // // MACRO // {"print-type", &Compiler::compile_print_type}, - // {"quote", &Compiler::compile_quote}, + {"quote", &Compiler::compile_quote}, // {"defconstant", &Compiler::compile_defconstant}, // // {"declare", &Compiler::compile_declare}, @@ -126,9 +129,7 @@ static const std::unordered_map< // // // - // {"listen-to-target", &Compiler::compile_listen_to_target}, - // {"reset-target", &Compiler::compile_reset_target}, - // {":status", &Compiler::compile_poke}, + // // // temporary testing hacks... // {"send-test", &Compiler::compile_send_test_data}, @@ -140,6 +141,8 @@ Val* Compiler::compile(const goos::Object& code, Env* env) { 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()); } @@ -180,3 +183,36 @@ Val* Compiler::compile_integer(s64 value, Env* env) { auto fe = get_parent_env_of_type(env); return fe->alloc_val(m_ts.make_typespec("int"), value); } + +SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) { + auto fe = get_parent_env_of_type(env); + return fe->alloc_val(name, m_ts.make_typespec("symbol")); +} + +Val* Compiler::compile_symbol(const goos::Object& form, Env* env) { + auto name = symbol_string(form); + + if (name == "none") { + return get_none(); + } + + // todo mlet + // todo lexical + // todo global constant + + return compile_get_symbol_value(name, env); +} + +Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) { + auto existing_symbol = m_symbol_types.find(name); + if (existing_symbol == m_symbol_types.end()) { + throw std::runtime_error("The symbol " + name + " was not defined"); + } + + auto ts = existing_symbol->second; + auto sext = m_ts.lookup_type(ts)->get_load_signed(); + auto fe = get_parent_env_of_type(env); + auto sym = fe->alloc_val(name, m_ts.make_typespec("symbol")); + auto re = fe->alloc_val(sym, ts, sext); + return re; +} \ No newline at end of file diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index 2d83f48ffd..ae85bc07e1 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -1,5 +1,7 @@ #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; @@ -22,4 +24,140 @@ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest, throw_compile_error(form, std::string("seval error: ") + e.what()); } return get_none(); +} + +Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + int i = 0; + std::string filename; + bool load = false; + bool color = false; + bool write = false; + bool no_code = false; + + std::vector> timing; + Timer total_timer; + + for_each_in_list(rest, [&](const goos::Object& o) { + if (i == 0) { + filename = as_string(o); + } else { + auto setting = symbol_string(o); + if (setting == ":load") { + load = true; + } else if (setting == ":color") { + color = true; + } else if (setting == ":write") { + write = true; + } else if (setting == ":no-code") { + no_code = true; + } else { + throw_compile_error(form, "invalid option " + setting + " in asm-file form"); + } + } + i++; + }); + + Timer reader_timer; + auto code = m_goos.reader.read_from_file(filename); + timing.emplace_back("read", reader_timer.getMs()); + + Timer compile_timer; + std::string obj_file_name = basename(filename.c_str()); + obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.')); + auto obj_file = compile_object_file(obj_file_name, code, !no_code); + timing.emplace_back("compile", compile_timer.getMs()); + + if (color) { + Timer color_timer; + color_object_file(obj_file); + timing.emplace_back("color", color_timer.getMs()); + + Timer codegen_timer; + auto data = codegen_object_file(obj_file); + timing.emplace_back("codegen", codegen_timer.getMs()); + + if (load) { + if (m_listener.is_connected()) { + m_listener.send_code(data); + } else { + printf("WARNING - couldn't load because listener isn't connected\n"); + } + } + + if (write) { + // auto output_dir = as_string(get_constant_or_error(form, "*compiler-output-path*")); + // todo, change extension based on v3/v4 + auto output_name = m_goos.reader.get_source_dir() + "/out/" + obj_file_name + ".o"; + util::write_binary_file(output_name, (void*)data.data(), data.size()); + } + } else { + if (load) { + printf("WARNING - couldn't load because coloring is not enabled\n"); + } + + if (write) { + printf("WARNING - couldn't write because coloring is not enabled\n"); + } + } + + // if(truthy(get_config("print-asm-file-time"))) { + for (auto& e : timing) { + printf(" %12s %4.2f\n", e.first.c_str(), e.second); + } + // } + + return get_none(); +} + +Val* Compiler::compile_listen_to_target(const goos::Object& form, + const goos::Object& rest, + Env* env) { + (void)env; + std::string ip = "127.0.0.1"; + int port = 8112; + bool got_port = false, got_ip = false; + + for_each_in_list(rest, [&](const goos::Object& o) { + if (o.is_string()) { + if (got_ip) { + throw_compile_error(form, "got multiple strings!"); + } + got_ip = true; + ip = o.as_string()->data; + } else if (o.is_int()) { + if (got_port) { + throw_compile_error(form, "got multiple ports!"); + } + got_port = true; + port = o.integer_obj.value; + } else { + throw_compile_error(form, "invalid argument to listen-to-target"); + } + }); + + m_listener.connect_to_target(30, ip, port); + return get_none(); +} + +Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + bool shutdown = false; + for_each_in_list(rest, [&](const goos::Object& o) { + if (o.is_symbol() && symbol_string(o) == ":shutdown") { + shutdown = true; + } else { + throw_compile_error(form, "invalid argument to reset-target"); + } + }); + m_listener.send_reset(shutdown); + return get_none(); +} + +Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + auto args = get_va(form, rest); + va_check(form, args, {}, {}); + m_listener.send_poke(); + return get_none(); } \ No newline at end of file diff --git a/goalc/compiler/compilation/Define.cpp b/goalc/compiler/compilation/Define.cpp new file mode 100644 index 0000000000..336d711a7b --- /dev/null +++ b/goalc/compiler/compilation/Define.cpp @@ -0,0 +1,41 @@ +#include "goalc/compiler/Compiler.h" + +Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {}); + auto& sym = args.unnamed.at(0); + auto& val = args.unnamed.at(1); + + // check we aren't duplicated a name as both a symbol and global constant + auto global_constant = m_global_constants.find(sym.as_symbol()); + if (global_constant != m_global_constants.end()) { + throw_compile_error( + form, "it is illegal to define a GOAL symbol with the same name as a GOAL global constant"); + } + + auto fe = get_parent_env_of_type(env); + auto sym_val = fe->alloc_val(symbol_string(sym), m_ts.make_typespec("symbol")); + auto compiled_val = compile_error_guard(val, env); + auto as_lambda = dynamic_cast(compiled_val); + if (as_lambda) { + // there are two cases in which we save a function body that is passed to a define: + // 1. It generated code [so went through the compiler] and the allow_inline flag is set. + // 2. It didn't generate code [so explicitly with :inline-only lambdas] + // The third case - immediate lambdas - don't get passed to a define, + // so this won't cause those to live for longer than they should + if ((as_lambda->func && as_lambda->func->settings.allow_inline) || !as_lambda->func) { + m_inlineable_functions[sym.as_symbol()] = as_lambda; + } + } + + auto in_gpr = compiled_val->to_gpr(fe); + auto existing_type = m_symbol_types.find(sym.as_symbol()->name); + if (existing_type == m_symbol_types.end()) { + m_symbol_types[sym.as_symbol()->name] = in_gpr->type(); + } else { + typecheck(form, existing_type->second, in_gpr->type(), "define on existing symbol"); + } + + fe->emit(std::make_unique(sym_val, in_gpr)); + return in_gpr; +} diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp index 275207c197..a5d6b6aa27 100644 --- a/goalc/compiler/compilation/Macro.cpp +++ b/goalc/compiler/compilation/Macro.cpp @@ -34,4 +34,56 @@ Val* Compiler::compile_goos_macro(const goos::Object& o, 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(); } \ No newline at end of file diff --git a/goalc/emitter/ObjectFileData.cpp b/goalc/emitter/ObjectFileData.cpp index b18e1468cc..cf338f4b8b 100644 --- a/goalc/emitter/ObjectFileData.cpp +++ b/goalc/emitter/ObjectFileData.cpp @@ -14,6 +14,11 @@ std::vector ObjectFileData::to_vector() const { // data (code + static objects, by segment) for (int seg = N_SEG; seg-- > 0;) { result.insert(result.end(), segment_data[seg].begin(), segment_data[seg].end()); + // printf("seg %d data\n", seg); + // for (auto x : segment_data[seg]) { + // printf("%02x ", x); + // } + // printf("\n"); } return result; } diff --git a/goalc/goos/Interpreter.cpp b/goalc/goos/Interpreter.cpp index 285f607fbe..7e1c411315 100644 --- a/goalc/goos/Interpreter.cpp +++ b/goalc/goos/Interpreter.cpp @@ -805,11 +805,9 @@ Object Interpreter::eval_quasiquote(const Object& form, return quasiquote_helper(rest.as_pair()->car, env); } -namespace { -bool truthy(const Object& o) { +bool Interpreter::truthy(const Object& o) { return !(o.is_symbol() && o.as_symbol()->name == "#f"); } -} // namespace /*! * Scheme "cond" statement - tested by integrated tests only. diff --git a/goalc/goos/Interpreter.h b/goalc/goos/Interpreter.h index d58f46670e..3d58a5c369 100644 --- a/goalc/goos/Interpreter.h +++ b/goalc/goos/Interpreter.h @@ -32,6 +32,7 @@ class Interpreter { Object eval_list_return_last(const Object& form, Object rest, const std::shared_ptr& env); + bool truthy(const Object& o); Reader reader; Object global_environment; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index f971dc35b8..961a3cb60d 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -51,9 +51,10 @@ bool Listener::is_connected() const { * Attempt to connect to the target. If the target isn't running, this should fail quickly. * Returns true if successfully connected. */ -bool Listener::connect_to_target(const std::string& ip, int port) { +bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) { if (m_connected) { - throw std::runtime_error("attempted a Listener::connect_to_target when already connected!"); + printf("already connected!\n"); + return true; } if (socket_fd >= 0) { @@ -100,12 +101,21 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } // connect! - int rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address)); + int rv, i; + for (i = 0; i < n_tries; i++) { + rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address)); + if (rv >= 0) { + break; + } + usleep(100000); + } if (rv < 0) { printf("[Listener] Failed to connect\n"); close(socket_fd); socket_fd = -1; return false; + } else { + printf("[Listener] Socket connected established! (took %d tries)\n", i); } // get the GOAL version number, to make sure we connected to the right thing @@ -303,6 +313,24 @@ void Listener::send_reset(bool shutdown) { 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; diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 38c453c6d4..b26b619944 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -19,11 +19,14 @@ 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); std::vector stop_recording_messages(); bool is_connected() const; void send_reset(bool shutdown); + void send_poke(); void disconnect(); void send_code(std::vector& code); bool most_recent_send_was_acked() { return got_ack; } diff --git a/goalc/util/CMakeLists.txt b/goalc/util/CMakeLists.txt index 609a1fbb68..fdca90d0c6 100644 --- a/goalc/util/CMakeLists.txt +++ b/goalc/util/CMakeLists.txt @@ -1 +1 @@ -add_library(util SHARED text_util.cpp file_io.cpp) \ No newline at end of file +add_library(util SHARED text_util.cpp file_io.cpp Timer.cpp) \ No newline at end of file diff --git a/goalc/util/Timer.cpp b/goalc/util/Timer.cpp new file mode 100644 index 0000000000..4ac44ab25c --- /dev/null +++ b/goalc/util/Timer.cpp @@ -0,0 +1,54 @@ +#include "Timer.h" + +#ifdef _WIN32 +#include +#define MS_PER_SEC 1000ULL // MS = milliseconds +#define US_PER_MS 1000ULL // US = microseconds +#define HNS_PER_US 10ULL // HNS = hundred-nanoseconds (e.g., 1 hns = 100 ns) +#define NS_PER_US 1000ULL + +#define HNS_PER_SEC (MS_PER_SEC * US_PER_MS * HNS_PER_US) +#define NS_PER_HNS (100ULL) // NS = nanoseconds +#define NS_PER_SEC (MS_PER_SEC * US_PER_MS * NS_PER_US) + +int Timer::clock_gettime_monotonic(struct timespec* tv) { + static LARGE_INTEGER ticksPerSec; + LARGE_INTEGER ticks; + double seconds; + + if (!ticksPerSec.QuadPart) { + QueryPerformanceFrequency(&ticksPerSec); + if (!ticksPerSec.QuadPart) { + errno = ENOTSUP; + return -1; + } + } + + QueryPerformanceCounter(&ticks); + + seconds = (double)ticks.QuadPart / (double)ticksPerSec.QuadPart; + tv->tv_sec = (time_t)seconds; + tv->tv_nsec = (long)((ULONGLONG)(seconds * NS_PER_SEC) % NS_PER_SEC); + + return 0; +} +#endif + +void Timer::start() { +#ifdef __linux__ + clock_gettime(CLOCK_MONOTONIC, &_startTime); +#elif _WIN32 + clock_gettime_monotonic(&_startTime); +#endif +} + +int64_t Timer::getNs() { + struct timespec now = {}; +#ifdef __linux__ + clock_gettime(CLOCK_MONOTONIC, &now); +#elif _WIN32 + clock_gettime_monotonic(&now); +#endif + return (int64_t)(now.tv_nsec - _startTime.tv_nsec) + + 1000000000 * (now.tv_sec - _startTime.tv_sec); +} diff --git a/goalc/util/Timer.h b/goalc/util/Timer.h new file mode 100644 index 0000000000..5972020339 --- /dev/null +++ b/goalc/util/Timer.h @@ -0,0 +1,47 @@ +#ifndef JAK_V2_TIMER_H +#define JAK_V2_TIMER_H + +#include +#include +#include + +/*! + * Timer for measuring time elapsed with clock_monotonic + */ +class Timer { + public: + /*! + * Construct and start timer + */ + explicit Timer() { start(); } + +#ifdef _WIN32 + int clock_gettime_monotonic(struct timespec* tv); +#endif + + /*! + * Start the timer + */ + void start(); + + /*! + * Get milliseconds elapsed + */ + double getMs() { return (double)getNs() / 1.e6; } + + double getUs() { return (double)getNs() / 1.e3; } + + /*! + * Get nanoseconds elapsed + */ + int64_t getNs(); + + /*! + * Get seconds elapsed + */ + double getSeconds() { return (double)getNs() / 1.e9; } + + struct timespec _startTime = {}; +}; + +#endif // JAK_V2_TIMER_H diff --git a/goalc/util/file_io.cpp b/goalc/util/file_io.cpp index d6b35e1735..095ad933a8 100644 --- a/goalc/util/file_io.cpp +++ b/goalc/util/file_io.cpp @@ -29,4 +29,17 @@ std::string combine_path(std::vector path) { return result; } +void write_binary_file(const std::string& name, void* data, size_t size) { + FILE* fp = fopen(name.c_str(), "wb"); + if (!fp) { + throw std::runtime_error("couldn't open file " + name); + } + + if (fwrite(data, size, 1, fp) != 1) { + throw std::runtime_error("couldn't write file " + name); + } + + fclose(fp); +} + } // namespace util diff --git a/goalc/util/file_io.h b/goalc/util/file_io.h index aae7340ee7..9fa7802040 100644 --- a/goalc/util/file_io.h +++ b/goalc/util/file_io.h @@ -8,6 +8,7 @@ namespace util { std::string read_text_file(const std::string& path); std::string combine_path(const std::string& parent, const std::string& child); std::string combine_path(std::vector path); +void write_binary_file(const std::string& name, void* data, size_t size); } // namespace util #endif // JAK1_FILE_IO_H diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp index a03b01177d..fffb74b451 100644 --- a/test/test_compiler_and_runtime.cpp +++ b/test/test_compiler_and_runtime.cpp @@ -69,10 +69,10 @@ struct CompilerTestRunner { int passed = 0; for (auto& test : tests) { if (test.expected == test.actual) { - fmt::print("[{:30}] PASS!\n", test.test_name); + fmt::print("[{:40}] PASS!\n", test.test_name); passed++; } else { - fmt::print("[{:30}] FAIL!\n", test.test_name); + fmt::print("[{:40}] FAIL!\n", test.test_name); fmt::print("expected:\n"); for (auto& x : test.expected) { fmt::print(" \"{}\"\n", escaped_string(x)); @@ -102,6 +102,15 @@ TEST(CompilerAndRuntime, CompilerTests) { 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"}); compiler.shutdown_target(); runtime_thread.join(); From 1394cf13cd6528773e02a54f855d101fe49f991e Mon Sep 17 00:00:00 2001 From: water Date: Mon, 7 Sep 2020 19:17:48 -0400 Subject: [PATCH 06/34] 17 of 124 compiler tests passing --- game/kernel/kscheme.cpp | 1 - goal_src/test/test-defglobalconstant-1.gc | 5 + goal_src/test/test-defglobalconstant-2.gc | 8 ++ goal_src/test/test-goto-1.gc | 9 ++ goal_src/test/test-nested-blocks-1.gc | 11 ++ goal_src/test/test-nested-blocks-2.gc | 11 ++ goal_src/test/test-nested-blocks-3.gc | 11 ++ goalc/compiler/Compiler.cpp | 1 + goalc/compiler/Compiler.h | 8 ++ goalc/compiler/Env.cpp | 42 ++++++- goalc/compiler/Env.h | 33 +++++- goalc/compiler/IR.cpp | 71 ++++++++++++ goalc/compiler/IR.h | 30 +++++ goalc/compiler/Label.h | 11 +- goalc/compiler/Util.cpp | 14 +++ goalc/compiler/compilation/Atoms.cpp | 25 ++++- goalc/compiler/compilation/Block.cpp | 129 ++++++++++++++++++++++ goalc/compiler/compilation/Macro.cpp | 28 +++++ goalc/emitter/ObjectGenerator.cpp | 9 ++ goalc/emitter/ObjectGenerator.h | 1 + test/test_compiler_and_runtime.cpp | 6 + 21 files changed, 449 insertions(+), 15 deletions(-) create mode 100644 goal_src/test/test-defglobalconstant-1.gc create mode 100644 goal_src/test/test-defglobalconstant-2.gc create mode 100644 goal_src/test/test-goto-1.gc create mode 100644 goal_src/test/test-nested-blocks-1.gc create mode 100644 goal_src/test/test-nested-blocks-2.gc create mode 100644 goal_src/test/test-nested-blocks-3.gc diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index bec0f1be12..816918ff0f 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -960,7 +960,6 @@ uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { // auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler! void* st_ptr = (void*)st; - printf("st is 0x%x\n", st); void* fptr = f.c(); #ifdef __linux__ diff --git a/goal_src/test/test-defglobalconstant-1.gc b/goal_src/test/test-defglobalconstant-1.gc new file mode 100644 index 0000000000..ce99baba6b --- /dev/null +++ b/goal_src/test/test-defglobalconstant-1.gc @@ -0,0 +1,5 @@ +(defglobalconstant my-constant 12) +(defglobalconstant my-constant 17) +(defglobalconstant not-my-consant 13) + +my-constant \ No newline at end of file diff --git a/goal_src/test/test-defglobalconstant-2.gc b/goal_src/test/test-defglobalconstant-2.gc new file mode 100644 index 0000000000..781eae4074 --- /dev/null +++ b/goal_src/test/test-defglobalconstant-2.gc @@ -0,0 +1,8 @@ +(defmacro get-goos-by-name (name) + ;; do the lookup in the goos global environment + (eval name) + ) + +(defglobalconstant my-constant 18) + +(get-goos-by-name my-constant) \ No newline at end of file diff --git a/goal_src/test/test-goto-1.gc b/goal_src/test/test-goto-1.gc new file mode 100644 index 0000000000..b310f67891 --- /dev/null +++ b/goal_src/test/test-goto-1.gc @@ -0,0 +1,9 @@ + + +(block my-block + 1 + (goto skip-early-return) + (return-from my-block 2) + (label skip-early-return) + 3 + ) diff --git a/goal_src/test/test-nested-blocks-1.gc b/goal_src/test/test-nested-blocks-1.gc new file mode 100644 index 0000000000..00bc13bca7 --- /dev/null +++ b/goal_src/test/test-nested-blocks-1.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from inner-block 7) + 5 + 6 + ) + ) \ No newline at end of file diff --git a/goal_src/test/test-nested-blocks-2.gc b/goal_src/test/test-nested-blocks-2.gc new file mode 100644 index 0000000000..f421a20275 --- /dev/null +++ b/goal_src/test/test-nested-blocks-2.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from inner-block 7) + 5 + ) + 8 + ) \ No newline at end of file diff --git a/goal_src/test/test-nested-blocks-3.gc b/goal_src/test/test-nested-blocks-3.gc new file mode 100644 index 0000000000..cd1e3b1889 --- /dev/null +++ b/goal_src/test/test-nested-blocks-3.gc @@ -0,0 +1,11 @@ +(block outer-block + 1 + 2 + (block inner-block + 3 + 4 + (return-from outer-block 7) + 5 + ) + 8 + ) \ No newline at end of file diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index a728753176..29c5c4d95f 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -203,5 +203,6 @@ void Compiler::typecheck(const goos::Object& form, const TypeSpec& expected, const TypeSpec& actual, const std::string& error_message) { + (void)form; m_ts.typecheck(expected, actual, error_message, true, true); } \ No newline at end of file diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 9f49720f34..215ee61f0b 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -53,6 +53,9 @@ class Compiler { named); std::string as_string(const goos::Object& o); std::string symbol_string(const goos::Object& o); + const goos::Object& pair_car(const goos::Object& o); + const goos::Object& pair_cdr(const goos::Object& o); + void expect_empty_list(const goos::Object& o); TypeSystem m_ts; std::unique_ptr m_global_env = nullptr; @@ -75,6 +78,10 @@ class Compiler { // 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); @@ -90,6 +97,7 @@ class Compiler { // Macro Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_defglobalconstant(const goos::Object& form, const goos::Object& rest, Env* env); }; #endif // JAK_COMPILER_H diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 50bc153280..0114dca609 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -47,6 +47,10 @@ RegVal* Env::make_xmm(TypeSpec ts) { return make_ireg(std::move(ts), emitter::RegKind::XMM); } +std::unordered_map& Env::get_label_map() { + return parent()->get_label_map(); +} + /////////////////// // GlobalEnv /////////////////// @@ -123,6 +127,24 @@ void NoEmitEnv::emit(std::unique_ptr 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 /////////////////// @@ -179,7 +201,17 @@ void FunctionEnv::emit(std::unique_ptr ir) { m_code.push_back(std::move(ir)); } void FunctionEnv::finish() { - // todo resolve gotos + 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) { @@ -189,4 +221,12 @@ RegVal* FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { auto rv = std::make_unique(ireg, ts); m_iregs.push_back(std::move(rv)); return m_iregs.back().get(); +} + +std::unordered_map& FunctionEnv::get_label_map() { + return m_labels; +} + +std::unordered_map& LabelEnv::get_label_map() { + return m_labels; } \ No newline at end of file diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index c81671c6ea..9837ac15f6 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -33,6 +33,7 @@ class Env { virtual void constrain_reg(IRegConstraint constraint); virtual Val* lexical_lookup(goos::Object sym); virtual BlockEnv* find_block(const std::string& name); + virtual std::unordered_map& get_label_map(); RegVal* make_gpr(TypeSpec ts); RegVal* make_xmm(TypeSpec ts); virtual ~Env() = default; @@ -117,10 +118,18 @@ class DeclareEnv : public Env { } settings; }; +class IR_GotoLabel; + +struct UnresolvedGoto { + IR_GotoLabel* ir; + std::string label; +}; + class FunctionEnv : public DeclareEnv { public: FunctionEnv(Env* parent, std::string name); std::string print() override; + std::unordered_map& get_label_map() override; void set_segment(int seg) { segment = seg; } void emit(std::unique_ptr ir) override; void finish(); @@ -140,14 +149,26 @@ class FunctionEnv : public DeclareEnv { m_vals.push_back(std::move(new_obj)); return (T*)m_vals.back().get(); } + + template + T* alloc_env(Args&&... args) { + std::unique_ptr new_obj = std::make_unique(std::forward(args)...); + m_envs.push_back(std::move(new_obj)); + return (T*)m_envs.back().get(); + } + int segment = -1; std::string method_of_type_name = "#f"; + std::vector unresolved_gotos; + protected: + void resolve_gotos(); std::string m_name; std::vector> m_code; std::vector> m_iregs; std::vector> m_vals; + std::vector> m_envs; std::vector m_constraints; // todo, unresolved gotos AllocationResult m_regalloc_result; @@ -156,6 +177,7 @@ class FunctionEnv : public DeclareEnv { bool m_aligned_stack_required = false; std::unordered_map m_params; + std::unordered_map m_labels; }; class BlockEnv : public Env { @@ -164,11 +186,10 @@ class BlockEnv : public Env { std::string print() override; BlockEnv* find_block(const std::string& name) override; - protected: - std::string m_name; - Label* m_end_label = nullptr; - Val* m_return_value = nullptr; - std::vector m_return_types; + std::string name; + Label end_label = nullptr; + RegVal* return_value = nullptr; + std::vector return_types; }; class LexicalEnv : public Env { @@ -179,6 +200,8 @@ class LexicalEnv : public Env { class LabelEnv : public Env { public: + std::unordered_map& get_label_map() override; + protected: std::unordered_map m_labels; }; diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 33afba90b6..236662ba1e 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -168,4 +168,75 @@ void IR_GetSymbolValue::do_codegen(emitter::ObjectGenerator* gen, irec); gen->link_instruction_symbol_mem(instr, m_src->name()); } +} + +///////////////////// +// RegSet +///////////////////// + +IR_RegSet::IR_RegSet(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} + +RegAllocInstr IR_RegSet::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + rai.read.push_back(m_src->ireg()); + if (m_dest->ireg().kind == m_src->ireg().kind) { + rai.is_move = true; // only true if we aren't moving from register kind to register kind + } + return rai; +} + +void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + auto val_reg = get_reg(m_src, allocs, irec); + auto dest_reg = get_reg(m_dest, allocs, irec); + + if (val_reg == dest_reg) { + gen->add_instr(IGen::null(), irec); + } else { + gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); + } +} + +std::string IR_RegSet::print() { + return fmt::format("mov {}, {}", m_dest->print(), m_src->print()); +} + +///////////////////// +// GotoLabel +///////////////////// + +IR_GotoLabel::IR_GotoLabel(const Label* dest) : m_dest(dest) { + m_resolved = true; +} + +IR_GotoLabel::IR_GotoLabel() { + m_resolved = false; +} + +std::string IR_GotoLabel::print() { + return fmt::format("goto {}", m_dest->print()); +} + +RegAllocInstr IR_GotoLabel::to_rai() { + assert(m_resolved); + RegAllocInstr rai; + rai.jumps.push_back(m_dest->idx); + rai.fallthrough = false; + return rai; +} + +void IR_GotoLabel::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + (void)allocs; + auto instr = gen->add_instr(IGen::jmp_32(), irec); + gen->link_instruction_jump(instr, gen->get_future_ir_record_in_same_func(irec, m_dest->idx)); +} + +void IR_GotoLabel::resolve(const Label* dest) { + assert(!m_resolved); + m_dest = dest; + m_resolved = true; } \ No newline at end of file diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index b06845e0b3..65e3747426 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -99,4 +99,34 @@ class IR_GetSymbolValue : public IR { bool m_sext = false; }; +class IR_RegSet : public IR { + public: + IR_RegSet(const RegVal* dest, const RegVal* src); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + const RegVal* m_src = nullptr; +}; + +class IR_GotoLabel : public IR { + public: + IR_GotoLabel(); + void resolve(const Label* dest); + explicit IR_GotoLabel(const Label* dest); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const Label* m_dest = nullptr; + bool m_resolved = false; +}; + #endif // JAK_IR_H diff --git a/goalc/compiler/Label.h b/goalc/compiler/Label.h index 0307a723f1..61336994ab 100644 --- a/goalc/compiler/Label.h +++ b/goalc/compiler/Label.h @@ -1,8 +1,13 @@ - - #ifndef JAK_LABEL_H #define JAK_LABEL_H -struct Label {}; +class FunctionEnv; +struct Label { + Label() = default; + Label(FunctionEnv* f, int _idx = -1) : func(f), idx(_idx) {} + FunctionEnv* func = nullptr; + int idx = -1; + std::string print() const { return "label-" + std::to_string(idx); } +}; #endif // JAK_LABEL_H diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 435055de1a..c891c5be29 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -103,4 +103,18 @@ std::string Compiler::as_string(const goos::Object& o) { std::string Compiler::symbol_string(const goos::Object& o) { return o.as_symbol()->name; +} + +const goos::Object& Compiler::pair_car(const goos::Object& o) { + return o.as_pair()->car; +} + +const goos::Object& Compiler::pair_cdr(const goos::Object& o) { + return o.as_pair()->cdr; +} + +void Compiler::expect_empty_list(const goos::Object& o) { + if (!o.is_empty_list()) { + throw_compile_error(o, "expected to be an empty list"); + } } \ No newline at end of file diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index aa66b84e2d..9f3be74ce7 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -19,10 +19,10 @@ static const std::unordered_map< // // 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}, + {"block", &Compiler::compile_block}, + {"return-from", &Compiler::compile_return_from}, + {"label", &Compiler::compile_label}, + {"goto", &Compiler::compile_goto}, // // // COMPILER CONTROL // {"gs", &Compiler::compile_gs}, @@ -36,7 +36,7 @@ static const std::unordered_map< // // // CONDITIONAL COMPILATION {"#cond", &Compiler::compile_gscond}, - // {"defglobalconstant", &Compiler::compile_defglobalconstant}, + {"defglobalconstant", &Compiler::compile_defglobalconstant}, {"seval", &Compiler::compile_seval}, // // // CONTROL FLOW @@ -200,6 +200,21 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) { // 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); } diff --git a/goalc/compiler/compilation/Block.cpp b/goalc/compiler/compilation/Block.cpp index 404d022f71..1db6980419 100644 --- a/goalc/compiler/compilation/Block.cpp +++ b/goalc/compiler/compilation/Block.cpp @@ -13,3 +13,132 @@ Val* Compiler::compile_begin(const goos::Object& form, const goos::Object& rest, for_each_in_list(rest, [&](const Object& o) { result = compile_error_guard(o, env); }); return result; } + +Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest, Env* env) { + auto rest = &_rest; + auto name = pair_car(*rest); + rest = &pair_cdr(*rest); + + if (!rest->is_pair()) { + throw_compile_error(form, "Block form has an empty or invliad body"); + } + + auto fe = get_parent_env_of_type(env); + + // create environment + auto block_env = fe->alloc_env(env, symbol_string(name)); + + // we need to create a return value register, as a "return-from" statement inside the block may + // set it. for now it has a type of none, but we will set it more accurate after compiling the + // block. + // block_env->return_value = env->alloc_reg(get_base_typespec("none")); + block_env->return_value = env->make_gpr(m_ts.make_typespec("none")); + + // create label to the end of the block (we don't yet know where it is...) + block_env->end_label = Label(fe); + + // compile everything in the body + Val* result = get_none(); + for_each_in_list(*rest, [&](const Object& o) { result = compile_error_guard(o, block_env); }); + + // if no return-from's were used, we can ignore the return_value register, and basically turn this + // into a begin. this allows a block which returns a floating point value to return the value in + // an xmm register, which is likely to eliminate a gpr->xmm move. + if (block_env->return_types.empty()) { + return result; + } + + // determine return type as the lowest common ancestor of the block's last form and any + // return-from's + auto& return_types = block_env->return_types; + return_types.push_back(result->type()); + auto return_type = m_ts.lowest_common_ancestor(return_types); + block_env->return_value->set_type(return_type); + + // an IR to move the result of the block into the block's return register (if no return-from's are + // taken) + auto ir_move_rv = std::make_unique(block_env->return_value, result->to_gpr(fe)); + + // note - one drawback of doing this single pass is that a block always evaluates to a gpr. + // so we may have an unneeded xmm -> gpr move that could have been an xmm -> xmm that could have + // been eliminated. + env->emit(std::move(ir_move_rv)); + + // now we know the end of the block, so we set the label index to be on whatever comes after the + // return move. functions always end with a "null" IR and "null" instruction, so this is safe. + block_env->end_label.idx = block_env->end_label.func->code().size(); + + return block_env->return_value; +} + +Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object& _rest, Env* env) { + const Object* rest = &_rest; + auto block_name = symbol_string(pair_car(*rest)); + rest = &pair_cdr(*rest); + auto value_expression = pair_car(*rest); + expect_empty_list(pair_cdr(*rest)); + + // evaluate expression to return + auto result = compile_error_guard(value_expression, env); + auto fe = get_parent_env_of_type(env); + + // find block to return from + auto block = dynamic_cast(env->find_block(block_name)); + if (!block) { + throw_compile_error(form, + "The return-from form was unable to find a block named " + block_name); + } + + // move result into return register + auto ir_move_rv = std::make_unique(block->return_value, result->to_gpr(fe)); + + // inform block of our possible return type + block->return_types.push_back(result->type()); + + env->emit(std::move(ir_move_rv)); + + // jump to end of block + auto ir_jump = std::make_unique(&block->end_label); + // we know this label is a real label. even though end_label doesn't know where it is, there is an + // actual label object. this means we won't try to resolve this label _by name_ later on when the + // block is done. + // ir_jump->resolved = true; + env->emit(std::move(ir_jump)); + + // In the real GOAL, there is likely a bug here where a non-none value is returned. + return get_none(); +} + +Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest, Env* env) { + auto label_name = symbol_string(pair_car(rest)); + expect_empty_list(pair_cdr(rest)); + + // make sure we don't have a label with this name already + auto& labels = env->get_label_map(); + auto kv = labels.find(label_name); + if (kv != labels.end()) { + throw_compile_error( + form, "There are two labels named " + label_name + " in the same label environment"); + } + + // make a label pointing to the end of the current function env. + auto func_env = get_parent_env_of_type(env); + labels[label_name] = Label(func_env, func_env->code().size()); + return get_none(); +} + +Val* Compiler::compile_goto(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)form; + auto label_name = symbol_string(pair_car(rest)); + expect_empty_list(pair_cdr(rest)); + + auto ir_goto = std::make_unique(); + // this requires looking up the label by name after, as it may be a goto to a label which has not + // yet been defined. + + // add this goto to the list of gotos to resolve after the function is done. + // it's safe to have this reference, as the FunctionEnv also owns the goto. + get_parent_env_of_type(env)->unresolved_gotos.push_back({ir_goto.get(), label_name}); + env->emit(std::move(ir_goto)); + return get_none(); +} \ No newline at end of file diff --git a/goalc/compiler/compilation/Macro.cpp b/goalc/compiler/compilation/Macro.cpp index a5d6b6aa27..89b5afb265 100644 --- a/goalc/compiler/compilation/Macro.cpp +++ b/goalc/compiler/compilation/Macro.cpp @@ -28,6 +28,7 @@ Val* Compiler::compile_goos_macro(const goos::Object& o, Arguments args = m_goos.get_args(o, rest, macro->args); auto mac_env_obj = EnvironmentObject::make_new(); auto mac_env = mac_env_obj.as_env(); + mac_env->parent_env = m_goos.global_environment.as_env(); m_goos.set_args_in_env(o, args, macro->args, mac_env); m_goos.goal_to_goos.enclosing_method_type = get_parent_env_of_type(env)->method_of_type_name; @@ -85,5 +86,32 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, default: throw_compile_error(form, "Can't quote this"); } + return get_none(); +} + +Val* Compiler::compile_defglobalconstant(const goos::Object& form, + const goos::Object& _rest, + Env* env) { + auto rest = &_rest; + (void)env; + if (!rest->is_pair()) { + throw_compile_error(form, "invalid defglobalconstant"); + } + + auto sym = pair_car(*rest).as_symbol(); + rest = &pair_cdr(*rest); + auto value = pair_car(*rest); + + rest = &rest->as_pair()->cdr; + if (!rest->is_empty_list()) { + throw_compile_error(form, "invalid defglobalconstant"); + } + + // GOAL constant + m_global_constants[sym] = value; + + // GOOS constant + m_goos.global_environment.as_env()->vars[sym] = value; + return get_none(); } \ No newline at end of file diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 1f9da29214..0c14eb1e37 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -136,6 +136,15 @@ IR_Record ObjectGenerator::get_future_ir_record(const FunctionRecord& func, int return rec; } +IR_Record ObjectGenerator::get_future_ir_record_in_same_func(const IR_Record& irec, int ir_id) { + assert(irec.func_id == int(m_function_data_by_seg.at(irec.seg).size()) - 1); + IR_Record rec; + rec.seg = irec.seg; + rec.func_id = irec.func_id; + rec.ir_id = ir_id; + return rec; +} + /*! * Add a new Instruction for the given IR instruction. */ diff --git a/goalc/emitter/ObjectGenerator.h b/goalc/emitter/ObjectGenerator.h index 968577cbc3..4d3c436c16 100644 --- a/goalc/emitter/ObjectGenerator.h +++ b/goalc/emitter/ObjectGenerator.h @@ -43,6 +43,7 @@ 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); diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp index fffb74b451..8192b3a464 100644 --- a/test/test_compiler_and_runtime.cpp +++ b/test/test_compiler_and_runtime.cpp @@ -111,6 +111,12 @@ TEST(CompilerAndRuntime, CompilerTests) { 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(); From 1ab8329e3c52155fea6f6d558618597f77e44da8 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Mon, 7 Sep 2020 19:58:54 -0400 Subject: [PATCH 07/34] Created a cross-platform socket shim --- common/cross_sockets/CMakeLists.txt | 12 ++++++++ common/cross_sockets/xsocket.cpp | 45 +++++++++++++++++++++++++++++ common/cross_sockets/xsocket.h | 18 ++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 common/cross_sockets/CMakeLists.txt create mode 100644 common/cross_sockets/xsocket.cpp create mode 100644 common/cross_sockets/xsocket.h diff --git a/common/cross_sockets/CMakeLists.txt b/common/cross_sockets/CMakeLists.txt new file mode 100644 index 0000000000..f97ddd0f83 --- /dev/null +++ b/common/cross_sockets/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(cross_sockets + SHARED + "xsocket.h" + "xsocket.cpp") + +IF (WIN32) + # set stuff for windows + target_link_libraries(cross_sockets wsock32 ws2_32) +ELSE() + # set stuff for other systems + target_link_libraries(cross_sockets) +ENDIF() diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp new file mode 100644 index 0000000000..010550d2c5 --- /dev/null +++ b/common/cross_sockets/xsocket.cpp @@ -0,0 +1,45 @@ +#ifdef __linux +#include +#include +#include +#elif _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +void close_socket(int sock) { + if (sock < 0) { + return; + } +#ifdef __linux + close(sock); +#elif _WIN32 + closesocket(sock); +#endif +} + +int set_socket_option(int socket, int level, int optname, int optval, int optlen) { +#ifdef __linux + return setsockopt(socket, level, optname, &optval, optlen); +#elif _WIN32 + const char optVal = optval; + return setsockopt(socket, level, optname, &optVal, optlen); +#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 +} \ No newline at end of file diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h new file mode 100644 index 0000000000..fa1e5b4e70 --- /dev/null +++ b/common/cross_sockets/xsocket.h @@ -0,0 +1,18 @@ +#ifdef __linux +#include +#include +#include +#elif _WIN32 +#include +#endif + +#ifdef __linux +const int TCP_SOCKET_LEVEL = SOL_TCP; +#elif _WIN32 +const int TCP_SOCKET_LEVEL = IPPROTO_IP; +#endif + +void close_socket(int sock); +int set_socket_option(int socket, int level, int optname, int optval, int optlen); +int write_to_socket(int socket, const char* buf, int len); +int read_from_socket(int socket, char* buf, int len); From 84e0bee6f4bf14971fb916a0f48be58ab3ec2bf7 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Mon, 7 Sep 2020 19:59:44 -0400 Subject: [PATCH 08/34] Use shim in Listener/Deci2Server --- CMakeLists.txt | 3 ++ game/CMakeLists.txt | 5 +-- game/system/Deci2Server.cpp | 79 ++++++++++++++++++++---------------- game/system/Deci2Server.h | 11 +++-- goalc/CMakeLists.txt | 18 +++----- goalc/listener/Listener.cpp | 42 +++++++++++-------- test/CMakeLists.txt | 7 ++-- test/test_listener_deci2.cpp | 4 -- 8 files changed, 88 insertions(+), 81 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b40377dc4a..32fea0bb71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,9 @@ add_subdirectory(asset_tool) # build type_system library for compiler/decompiler add_subdirectory(common/type_system) +# build cross platform socket library +add_subdirectory(common/cross_sockets) + # build decompiler add_subdirectory(decompiler) diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 59cacdf7af..3a18ddf0cc 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -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) ENDIF() diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 600f34462e..2618d2404d 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -4,15 +4,22 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ -// TODO-WINDOWS -#ifdef __linux__ #include +#include +#include + +// TODO - i think im not including the dependency right..? +#include "common/cross_sockets/xsocket.h" + +#ifdef __linux #include #include #include -#include -#include +#elif _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif #include "common/listener_common.h" #include "common/versions.h" @@ -33,38 +40,39 @@ 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 = 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; + int server_socket_tcp_level = SOL_TCP; +#elif _WIN32 + int server_socket_opt = SO_REUSEADDR | SO_BROADCAST; + int server_socket_tcp_level = IPPROTO_IP; +#endif + int opt = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { + if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, opt, sizeof(opt))) { printf("[Deci2Server] Failed to setsockopt 1\n"); - close(server_fd); - server_fd = -1; + close_server_socket(); return false; } int one = 1; - if (setsockopt(server_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) { + if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, one, sizeof(one))) { printf("[Deci2Server] Failed to setsockopt 2\n"); - close(server_fd); - server_fd = -1; + close_server_socket(); return false; } @@ -72,10 +80,9 @@ bool Deci2Server::init() { timeout.tv_sec = 0; timeout.tv_usec = 100000; - if (setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { + if (setsockopt(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { printf("[Deci2Server] Failed to setsockopt 3\n"); - close(server_fd); - server_fd = -1; + close_server_socket(); return false; } @@ -83,17 +90,15 @@ bool Deci2Server::init() { 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 +109,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 +139,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 +196,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 +247,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 +266,12 @@ 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); + new_sock = accept(server_socket, (sockaddr*)&addr, &l); if (new_sock >= 0) { u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; - send(new_sock, &versions, 8, 0); // todo, check result? + write_to_socket(new_sock, (char*)&versions, 8); // todo, check result? server_connected = true; return; } } } -#endif \ No newline at end of file diff --git a/game/system/Deci2Server.h b/game/system/Deci2Server.h index 8695ff020d..c8ddbaa1da 100644 --- a/game/system/Deci2Server.h +++ b/game/system/Deci2Server.h @@ -4,11 +4,14 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ -#ifdef __linux__ #ifndef JAK1_DECI2SERVER_H #define JAK1_DECI2SERVER_H +#ifdef __linux #include +#elif _WIN32 +#include +#endif #include #include #include @@ -32,11 +35,12 @@ class Deci2Server { void run(); private: + void close_server_socket(); void accept_thread_func(); bool kill_accept_thread = false; char* buffer = nullptr; - int server_fd = -1; - sockaddr_in addr; + int server_socket = -1; + struct sockaddr_in addr = {}; int new_sock = -1; bool server_initialized = false; bool accept_thread_running = false; @@ -52,4 +56,3 @@ class Deci2Server { }; #endif // JAK1_DECI2SERVER_H -#endif diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 21b3fcb92c..ee0a59f2fd 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,12 +1,6 @@ add_subdirectory(util) add_subdirectory(goos) - -IF (WIN32) - # TODO - implement windows listener - message("Windows Listener Not Implemented!") -ELSE() - add_subdirectory(listener) -ENDIF() +add_subdirectory(listener) add_library(compiler SHARED @@ -26,12 +20,10 @@ add_library(compiler 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) + target_link_libraries(compiler cross_sockets util goos type_system mman) +ELSE() + target_link_libraries(compiler cross_sockets util goos goalc type_system) ENDIF() - -target_link_libraries(goalc util goos compiler type_system) diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 2a53f26612..709d5ab5b1 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -3,13 +3,20 @@ * The Listener can connect to a Deci2Server for debugging. */ -// TODO-Windows -#ifdef __linux__ - -#include +#ifdef __linux #include #include #include +#elif _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +// TODO - i think im not including the dependency right..? +#include "common/cross_sockets/xsocket.h" + +#include #include #include "Listener.h" #include "common/versions.h" @@ -27,7 +34,7 @@ Listener::~Listener() { delete[] m_buffer; if (socket_fd >= 0) { - close(socket_fd); + close_socket(socket_fd); } } @@ -53,7 +60,7 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } if (socket_fd >= 0) { - close(socket_fd); + close_socket(socket_fd); } // construct socket @@ -70,16 +77,16 @@ bool Listener::connect_to_target(const std::string& ip, int port) { 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); + close_socket(socket_fd); socket_fd = -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))) { + if (set_socket_option(socket_fd, TCP_SOCKET_LEVEL, TCP_NODELAY, one, sizeof(one))) { printf("[Listener] failed to TCP_NODELAY\n"); - close(socket_fd); + close_socket(socket_fd); socket_fd = -1; return false; } @@ -90,7 +97,7 @@ 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); + close_socket(socket_fd); socket_fd = -1; return false; } @@ -99,7 +106,7 @@ bool Listener::connect_to_target(const std::string& ip, int port) { int rv = connect(socket_fd, (sockaddr*)&server_address, sizeof(server_address)); if (rv < 0) { printf("[Listener] Failed to connect\n"); - close(socket_fd); + close_socket(socket_fd); socket_fd = -1; return false; } @@ -110,7 +117,7 @@ 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); + auto r = read_from_socket(socket_fd, (char*)version_buffer + prog, 8 - prog); if (r < 0) { ok = false; break; @@ -124,7 +131,7 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } if (!ok) { printf("[Listener] Failed to get version number\n"); - close(socket_fd); + close_socket(socket_fd); socket_fd = -1; return false; } @@ -138,7 +145,7 @@ 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); + close_socket(socket_fd); socket_fd = -1; return false; } @@ -155,7 +162,7 @@ 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(socket_fd, buff + rcvd, rcvd_desired - rcvd); rcvd += got > 0 ? got : 0; // kick us out if we got a bogus read result @@ -188,7 +195,7 @@ 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(socket_fd, ack_recv_buff + ack_recv_prog, hdr->deci2_header.len - rcvd); got = got > 0 ? got : 0; rcvd += got; ack_recv_prog += got; @@ -210,7 +217,7 @@ void Listener::receive_func() { return; } - int got = read(socket_fd, str_buff + msg_prog, hdr->deci2_header.len - rcvd); + int got = read_from_socket(socket_fd, str_buff + msg_prog, hdr->deci2_header.len - rcvd); got = got > 0 ? got : 0; rcvd += got; msg_prog += got; @@ -242,4 +249,3 @@ void Listener::receive_func() { } } // namespace listener -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8173c9820c..05bcf9570f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,9 +18,8 @@ add_executable(goalc-test 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 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 runtime compiler type_system gtest) ELSE() - target_link_libraries(goalc-test goos util listener runtime compiler type_system gtest) + target_link_libraries(goalc-test cross_sockets goos util listener runtime compiler type_system gtest) ENDIF() diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index d7b65cba2a..99874e3d09 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -1,5 +1,3 @@ -#ifdef __linux__ - #include "gtest/gtest.h" #include "goalc/listener/Listener.h" #include "game/system/Deci2Server.h" @@ -126,5 +124,3 @@ TEST(Listener, ListenerMultipleDecis) { l.disconnect(); } } - -#endif \ No newline at end of file From aea7b692e43f9ce94dbb631c95482cfac8a98edc Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Mon, 7 Sep 2020 20:00:02 -0400 Subject: [PATCH 09/34] Got rid of a lot of windows ifdefs --- game/kernel/kboot.cpp | 12 ++++-------- game/kernel/kdsnetm.cpp | 3 --- game/kernel/kdsnetm.h | 7 ------- game/kernel/klisten.cpp | 10 +--------- game/kernel/kmachine.cpp | 6 ------ game/kernel/ksocket.cpp | 3 --- game/runtime.cpp | 15 +++------------ game/sce/deci2.cpp | 33 --------------------------------- 8 files changed, 8 insertions(+), 81 deletions(-) diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 9f6f6de06d..1cd2cc1a13 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -5,6 +5,9 @@ */ #include +#include +#include + #include "common/common_types.h" #include "game/sce/libscf.h" #include "kboot.h" @@ -134,21 +137,14 @@ void KernelCheckAndDispatch() { // dispatch the kernel //(**kernel_dispatcher)(); call_goal(Ptr(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem); - // TODO-WINDOWS -#ifdef __linux__ ClearPending(); -#endif // if the listener function changed, it means the kernel ran it, so we should notify compiler. if (MasterDebug && ListenerFunction->value != old_listener) { SendAck(); } -#ifdef _WIN32 - Sleep(1000); // todo - remove this -#elif __linux__ - usleep(1000); -#endif + std::this_thread::sleep_for(std::chrono::microseconds(1000)); } } diff --git a/game/kernel/kdsnetm.cpp b/game/kernel/kdsnetm.cpp index 2d06a93160..da07a7a8bb 100644 --- a/game/kernel/kdsnetm.cpp +++ b/game/kernel/kdsnetm.cpp @@ -27,8 +27,6 @@ void kdsnetm_init_globals() { protoBlock.reset(); } -// TODO-WINDOWS -#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -222,4 +220,3 @@ void GoalProtoStatus() { Msg(6, "gproto: got %d %d\n", protoBlock.most_recent_event, protoBlock.most_recent_param); Msg(6, "gproto: %d %d\n", protoBlock.last_receive_size, protoBlock.send_remaining); } -#endif \ No newline at end of file diff --git a/game/kernel/kdsnetm.h b/game/kernel/kdsnetm.h index 51125e0c1f..bdbef66096 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/kdsnetm.h @@ -51,8 +51,6 @@ extern GoalProtoBlock protoBlock; */ void kdsnetm_init_globals(); -// TODO-WINDOWS -#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -65,8 +63,6 @@ void InitGoalProto(); */ void ShutdownGoalProto(); -#endif - /*! * Handle a DECI2 Protocol Event for the GOAL Proto. * Called by the DECI2 Protocol driver @@ -74,8 +70,6 @@ void ShutdownGoalProto(); */ void GoalProtoHandler(int event, int param, void* data); -// TODO-WINDOWS -#ifdef __linux__ /*! * Low level DECI2 send * Will block until send is complete. @@ -83,7 +77,6 @@ void GoalProtoHandler(int event, int param, void* data); * removed */ s32 SendFromBufferD(s32 p1, u64 p2, char* data, s32 size); -#endif /*! * Print GOAL Protocol status diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index 7f990bf27e..4f1d733bbe 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -71,10 +71,7 @@ void ClearPending() { Ptr msg = OutputBufArea.cast() + sizeof(GoalMessageHeader); auto size = strlen(msg.c()); // note - if size is ever greater than 2^16 this will cause an issue. - // TODO-WINDOWS -#ifdef __linux__ SendFromBuffer(msg.c(), size); -#endif clear_output(); } @@ -87,10 +84,7 @@ void ClearPending() { if (send_size > 64000) { send_size = 64000; } -// TODO-WINDOWS -#ifdef __linux__ SendFromBufferD(2, 0, msg, send_size); -#endif size -= send_size; msg += send_size; } @@ -109,11 +103,9 @@ void ClearPending() { */ void SendAck() { if (MasterDebug) { -#ifdef __linux__ SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, AckBufArea + sizeof(GoalMessageHeader), strlen(AckBufArea + sizeof(GoalMessageHeader))); -#endif } } @@ -161,4 +153,4 @@ void ProcessListenerMessage(Ptr msg) { break; } SendAck(); -} \ No newline at end of file +} diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 4752b90b93..168619844a 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -329,10 +329,7 @@ int InitMachine() { // } if (MasterDebug) { // connect to GOAL compiler -// TODO-WINDOWS -#ifdef __linux__ InitGoalProto(); -#endif } printf("InitSound\n"); @@ -362,10 +359,7 @@ int ShutdownMachine() { StopIOP(); CloseListener(); ShutdownSound(); -// TODO-WINDOWS -#ifdef __linux__ ShutdownGoalProto(); -#endif Msg(6, "kernel: machine shutdown"); return 0; } diff --git a/game/kernel/ksocket.cpp b/game/kernel/ksocket.cpp index b66fc1dd7e..4eb0194017 100644 --- a/game/kernel/ksocket.cpp +++ b/game/kernel/ksocket.cpp @@ -52,8 +52,6 @@ u32 ReceiveToBuffer(char* buff) { return msg_size; } -// TODO-WINDOWS -#ifdef __linux__ /*! * Do a DECI2 send and block until it is complete. * The message type is OUTPUT @@ -62,7 +60,6 @@ u32 ReceiveToBuffer(char* buff) { s32 SendFromBuffer(char* buff, s32 size) { return SendFromBufferD(u16(ListenerMessageKind::MSG_OUTPUT), 0, buff, size); } -#endif /*! * Just prepare the Ack buffer, doesn't actually connect. diff --git a/game/runtime.cpp b/game/runtime.cpp index 379898a9a1..a35fd9f717 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -12,7 +12,9 @@ #include #endif +#include #include +#include #include "runtime.h" #include "system/SystemThread.h" @@ -45,11 +47,6 @@ u8* g_ee_main_mem = nullptr; -/*! - * TODO-WINDOWS - * runtime.cpp - Deci2Listener has been disabled for now, pending rewriting for Windows. - */ - namespace { /*! @@ -57,8 +54,6 @@ namespace { */ void deci2_runner(SystemThreadInterface& iface) { -// TODO-WINDOWS -#ifdef __linux__ // callback function so the server knows when to give up and shutdown std::function shutdown_callback = [&]() { return iface.get_want_exit(); }; @@ -89,10 +84,9 @@ void deci2_runner(SystemThreadInterface& iface) { server.run(); } else { // no connection yet. Do a sleep so we don't spam checking the listener. - usleep(50000); + std::this_thread::sleep_for(std::chrono::microseconds(50000)); } } -#endif } // EE System @@ -236,10 +230,7 @@ void exec_runtime(int argc, char** 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 diff --git a/game/sce/deci2.cpp b/game/sce/deci2.cpp index 6cb5a84058..99b12cbcb3 100644 --- a/game/sce/deci2.cpp +++ b/game/sce/deci2.cpp @@ -12,14 +12,11 @@ namespace ee { namespace { -// TODO-WINDOWS -#ifdef __linux__ constexpr int MAX_DECI2_PROTOCOLS = 4; Deci2Driver protocols[MAX_DECI2_PROTOCOLS]; // info for each deci2 protocol registered int protocol_count; // number of registered protocols Deci2Driver* sending_driver; // currently sending protocol driver ::Deci2Server* server; // the server to send data to -#endif } // namespace @@ -27,8 +24,6 @@ Deci2Driver* sending_driver; // currently sending protocol drive * Initialize the library. */ void LIBRARY_INIT_sceDeci2() { -// TODO-WINDOWS -#ifdef __linux__ // reset protocols for (auto& p : protocols) { p = Deci2Driver(); @@ -36,15 +31,12 @@ void LIBRARY_INIT_sceDeci2() { protocol_count = 0; server = nullptr; sending_driver = nullptr; -#endif } /*! * Run any pending requested sends. */ void LIBRARY_sceDeci2_run_sends() { -// TODO-WINDOWS -#ifdef __linux__ for (auto& prot : protocols) { if (prot.active && prot.pending_send == 'H') { sending_driver = &prot; @@ -54,17 +46,13 @@ void LIBRARY_sceDeci2_run_sends() { (prot.handler)(DECI2_WRITEDONE, 0, prot.opt); } } -#endif } /*! * Register a Deci2Server with this library. */ void LIBRARY_sceDeci2_register(::Deci2Server* s) { -// TODO-WINDOWS -#ifdef __linux__ server = s; -#endif } /*! @@ -73,8 +61,6 @@ void LIBRARY_sceDeci2_register(::Deci2Server* s) { * I don't know why it's like this. */ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, void* opt)) { -// TODO-WINDOWS -#ifdef __linux__ server->lock(); Deci2Driver drv; drv.protocol = protocol; @@ -93,20 +79,14 @@ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, } return drv.id; -#elif _WIN32 - return 0; -#endif } /*! * Deactivate a DECI2 protocol by socket descriptor. */ s32 sceDeci2Close(s32 s) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].active = false; -#endif return 1; } @@ -114,12 +94,9 @@ s32 sceDeci2Close(s32 s) { * Start a send. */ s32 sceDeci2ReqSend(s32 s, char dest) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); auto& proto = protocols[s - 1]; proto.pending_send = dest; -#endif return 0; } @@ -128,8 +105,6 @@ s32 sceDeci2ReqSend(s32 s, char dest) { * Returns after data is copied. */ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].recv_size = len; auto avail = protocols[s - 1].available_to_receive; @@ -140,17 +115,12 @@ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { printf("[DECI2] Error: ExRecv %d, only %d available!\n", len, avail); return -1; } -#elif _WIN32 - return 0; -#endif } /*! * Do a send. */ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { -// TODO-WINDOWS -#ifdef __linux__ assert(s - 1 < protocol_count); if (!sending_driver) { printf("sceDeci2ExSend called at illegal time!\n"); @@ -162,8 +132,5 @@ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { server->send_data(buf, len); return len; -#elif _WIN32 - return 0; -#endif } } // namespace ee From 3d8002acaf6fd223d99a23ac2bd7407fd9475cf2 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Mon, 7 Sep 2020 20:44:31 -0400 Subject: [PATCH 10/34] oh no, im actually starting to understand cmake --- goalc/CMakeLists.txt | 11 ++++++----- goalc/listener/CMakeLists.txt | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index ee0a59f2fd..5365a1f48c 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -17,13 +17,14 @@ add_library(compiler regalloc/IRegister.cpp regalloc/Allocator.cpp regalloc/allocate.cpp - compiler/Compiler.cpp - ) + compiler/Compiler.cpp) add_executable(goalc main.cpp) IF (WIN32) - target_link_libraries(compiler cross_sockets util goos type_system mman) -ELSE() - target_link_libraries(compiler cross_sockets util goos goalc type_system) + target_link_libraries(compiler fmt mman type_system) +ELSE () + target_link_libraries(compiler fmt type_system) ENDIF() + +target_link_libraries(goalc util goos compiler) \ No newline at end of file diff --git a/goalc/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt index f978b4d633..adbaeb88e3 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,2 +1,3 @@ -add_library(listener SHARED - Listener.cpp) +add_library(listener SHARED Listener.cpp) + +target_link_libraries(listener cross_sockets) \ No newline at end of file From f8fccbf7a60cc7debf890c00da447f23b177b41a Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 00:01:46 -0400 Subject: [PATCH 11/34] Additional progress, listener tests are starting to pass --- common/cross_sockets/xsocket.cpp | 33 +++++++++++++++++++++++---- common/cross_sockets/xsocket.h | 5 ++-- game/system/Deci2Server.cpp | 39 ++++++++++++++++++++------------ goalc/listener/Listener.cpp | 27 +++++++++++++++------- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index 010550d2c5..de15d2ec90 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -8,6 +8,24 @@ #include #endif +#include + +int open_socket(int af, int type, int protocol) { +#ifdef __linux + return socket(af, type, protocol); +#elif _WIN32 + WSADATA wsaData = {0}; + int iResult = 0; + // Initialize Winsock + iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + printf("WSAStartup failed: %d\n", iResult); + return 1; + } + return socket(af, type, protocol); +#endif +} + void close_socket(int sock) { if (sock < 0) { return; @@ -16,15 +34,20 @@ void close_socket(int sock) { close(sock); #elif _WIN32 closesocket(sock); + WSACleanup(); #endif } -int set_socket_option(int socket, int level, int optname, int optval, int optlen) { +int set_socket_option(int socket, int level, int optname, const char* optval, int optlen) { #ifdef __linux - return setsockopt(socket, level, optname, &optval, optlen); + return setsockopt(socket, level, optname, optval, optlen); #elif _WIN32 - const char optVal = optval; - return setsockopt(socket, level, optname, &optVal, optlen); + int ret = setsockopt(socket, level, optname, optval, optlen); + if (ret < 0) { + int err = WSAGetLastError(); + printf("Failed to setsockopt - Err: %d\n", err); + } + return ret; #endif } @@ -42,4 +65,4 @@ int read_from_socket(int socket, char* buf, int len) { #elif _WIN32 return recv(socket, buf, len, 0); #endif -} \ No newline at end of file +} diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h index fa1e5b4e70..8f7bb359ae 100644 --- a/common/cross_sockets/xsocket.h +++ b/common/cross_sockets/xsocket.h @@ -9,10 +9,11 @@ #ifdef __linux const int TCP_SOCKET_LEVEL = SOL_TCP; #elif _WIN32 -const int TCP_SOCKET_LEVEL = IPPROTO_IP; +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, int optval, int optlen); +int set_socket_option(int socket, int level, int optname, const char* optval, int optlen); int write_to_socket(int socket, const char* buf, int len); int read_from_socket(int socket, char* buf, int len); diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 2618d2404d..5f2308f575 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -4,7 +4,6 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ - #include #include #include @@ -17,7 +16,8 @@ #include #include #elif _WIN32 -#define WIN32_LEAN_AND_MEAN +#include +#include #include #endif @@ -48,7 +48,7 @@ Deci2Server::~Deci2Server() { * Start waiting for the Listener to connect */ bool Deci2Server::init() { - server_socket = socket(AF_INET, SOCK_STREAM, 0); + server_socket = open_socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { server_socket = -1; return false; @@ -58,33 +58,41 @@ bool Deci2Server::init() { int server_socket_opt = SO_REUSEADDR | SO_REUSEPORT; int server_socket_tcp_level = SOL_TCP; #elif _WIN32 - int server_socket_opt = SO_REUSEADDR | SO_BROADCAST; - int server_socket_tcp_level = IPPROTO_IP; + int server_socket_opt = SO_EXCLUSIVEADDRUSE; + int server_socket_tcp_level = IPPROTO_TCP; #endif - int opt = 1; - if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, opt, sizeof(opt))) { - printf("[Deci2Server] Failed to setsockopt 1\n"); + char opt = 1; + if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, &opt, sizeof(opt)) < + 0) { close_server_socket(); return false; } - int one = 1; - if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, one, sizeof(one))) { - printf("[Deci2Server] Failed to setsockopt 2\n"); + if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, &opt, + sizeof(opt)) < 0) { close_server_socket(); return false; } - timeval timeout = {}; +// TODO - put in library +#ifdef __linux + timeval timeout = {}; timeout.tv_sec = 0; timeout.tv_usec = 100000; - - if (setsockopt(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { - printf("[Deci2Server] Failed to setsockopt 3\n"); + if (set_socket_option(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < + 0) { close_server_socket(); return false; } +#elif _WIN32 + unsigned long timeout = 100; // ms + if (set_socket_option(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < + 0) { + close_server_socket(); + return false; + } +#endif addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; @@ -266,6 +274,7 @@ void Deci2Server::run() { void Deci2Server::accept_thread_func() { socklen_t l = sizeof(addr); while (!kill_accept_thread) { + // 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) { u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 709d5ab5b1..774f0415a0 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -64,27 +64,38 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } // construct socket - socket_fd = socket(AF_INET, SOCK_STREAM, 0); + socket_fd = open_socket(AF_INET, SOCK_STREAM, 0); if (socket_fd < 0) { printf("[Listener] Failed to create socket.\n"); socket_fd = -1; return false; } - // set timeout for receive - timeval timeout = {}; + // TODO - put in library +#ifdef __linux + 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"); + if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < + 0) { close_socket(socket_fd); - socket_fd = -1; + socket_fd = -1; return false; } +#elif _WIN32 + unsigned long timeout = 100; // ms + if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < + 0) { + printf("[Listener] setsockopt failed\n"); + close_socket(socket_fd); + socket_fd = -1; + return false; + } +#endif // set nodelay, which makes small rapid messages faster, but large messages slower - int one = 1; - if (set_socket_option(socket_fd, TCP_SOCKET_LEVEL, TCP_NODELAY, one, sizeof(one))) { + char one = 1; + if (set_socket_option(socket_fd, TCP_SOCKET_LEVEL, TCP_NODELAY, &one, sizeof(one))) { printf("[Listener] failed to TCP_NODELAY\n"); close_socket(socket_fd); socket_fd = -1; From 871f1b43b053f37ca281c255b9907d7b0090ff27 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 14:12:04 -0400 Subject: [PATCH 12/34] Remove the problematic Multiple Deci tests --- README.md | 4 ---- common/cross_sockets/xsocket.cpp | 10 ++++----- game/CMakeLists.txt | 2 +- game/runtime.cpp | 2 +- game/system/Deci2Server.cpp | 12 +++++------ goalc/listener/Listener.cpp | 19 ++++++++-------- test/test_listener_deci2.cpp | 37 ++------------------------------ 7 files changed, 24 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 6b33344a30..32627d23dd 100644 --- a/README.md +++ b/README.md @@ -154,13 +154,9 @@ where the `~~ HACK ~~` message is from code in `KERNEL.CGO`. ## TODOs - Build on Windows! - - Networking - File paths - Timer - - CMake? - - Assembly - Windows calling convention for assembly stuff - - pthreads (can probably replace with `std::thread`, I don't remember why I used `pthread`s) - performance stats for `SystemThread` (probably just get rid of these performance stats completely) - `mmap`ing executable memory - line input library (appears windows compatible?) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index de15d2ec90..0fa79e6864 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -22,7 +22,7 @@ int open_socket(int af, int type, int protocol) { printf("WSAStartup failed: %d\n", iResult); return 1; } - return socket(af, type, protocol); + return socket(af, type, protocol); #endif } @@ -42,11 +42,11 @@ int set_socket_option(int socket, int level, int optname, const char* optval, in #ifdef __linux return setsockopt(socket, level, optname, optval, optlen); #elif _WIN32 - int ret = setsockopt(socket, level, optname, optval, optlen); + int ret = setsockopt(socket, level, optname, optval, optlen); if (ret < 0) { - int err = WSAGetLastError(); - printf("Failed to setsockopt - Err: %d\n", err); - } + int err = WSAGetLastError(); + printf("Failed to setsockopt - Err: %d\n", err); + } return ret; #endif } diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 3a18ddf0cc..e51dde9e25 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -81,5 +81,5 @@ IF (WIN32) target_link_libraries(gk cross_sockets mman) ELSE() # set stuff for other systems - target_link_libraries(gk cross_sockets) + target_link_libraries(gk cross_sockets pthread) ENDIF() diff --git a/game/runtime.cpp b/game/runtime.cpp index a35fd9f717..f851e11a82 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -84,7 +84,7 @@ void deci2_runner(SystemThreadInterface& iface) { server.run(); } else { // no connection yet. Do a sleep so we don't spam checking the listener. - std::this_thread::sleep_for(std::chrono::microseconds(50000)); + std::this_thread::sleep_for(std::chrono::microseconds(50000)); } } } diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 5f2308f575..7579b60f65 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -63,21 +63,20 @@ bool Deci2Server::init() { #endif char opt = 1; - if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, &opt, sizeof(opt)) < - 0) { + if (set_socket_option(server_socket, SOL_SOCKET, server_socket_opt, &opt, sizeof(opt)) < 0) { close_server_socket(); return false; } - if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, &opt, - sizeof(opt)) < 0) { + if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, &opt, sizeof(opt)) < + 0) { close_server_socket(); return false; } // TODO - put in library #ifdef __linux - timeval timeout = {}; + timeval timeout = {}; timeout.tv_sec = 0; timeout.tv_usec = 100000; if (set_socket_option(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < @@ -274,7 +273,8 @@ void Deci2Server::run() { void Deci2Server::accept_thread_func() { socklen_t l = sizeof(addr); while (!kill_accept_thread) { - // TODO - might want to do a WSAStartUp call here as well, else it won't be balanced on the close + // 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) { u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 774f0415a0..11f62f0260 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -71,24 +71,22 @@ bool Listener::connect_to_target(const std::string& ip, int port) { return false; } - // TODO - put in library + // TODO - put in library #ifdef __linux - timeval timeout = {}; + timeval timeout = {}; timeout.tv_sec = 0; timeout.tv_usec = 100000; - if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < - 0) { + if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { close_socket(socket_fd); - socket_fd = -1; + socket_fd = -1; return false; } #elif _WIN32 unsigned long timeout = 100; // ms - if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < - 0) { - printf("[Listener] setsockopt failed\n"); + if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { + printf("[Listener] setsockopt failed\n"); close_socket(socket_fd); - socket_fd = -1; + socket_fd = -1; return false; } #endif @@ -206,7 +204,8 @@ void Listener::receive_func() { while (rcvd < hdr->deci2_header.len) { if (!m_connected) return; - int got = read_from_socket(socket_fd, ack_recv_buff + ack_recv_prog, hdr->deci2_header.len - rcvd); + int got = read_from_socket(socket_fd, ack_recv_buff + ack_recv_prog, + hdr->deci2_header.len - rcvd); got = got > 0 ? got : 0; rcvd += got; ack_recv_prog += got; diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 99874e3d09..a2ffd96e8c 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -23,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 */ @@ -62,9 +56,8 @@ TEST(Listener, DeciThenListener) { EXPECT_FALSE(s.check_for_listener()); EXPECT_FALSE(s.check_for_listener()); EXPECT_TRUE(l.connect_to_target()); - // kind of a hack. + // TODO - some sort of backoff and retry would be better while (!s.check_for_listener()) { - // printf("...\n"); } EXPECT_TRUE(s.check_for_listener()); @@ -93,34 +86,8 @@ TEST(Listener, ListenerThenDeci) { EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); EXPECT_TRUE(l.connect_to_target()); + // TODO - some sort of backoff and retry would be better while (!s.check_for_listener()) { - // printf("...\n"); } } } - -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(); - } -} From 4b6223a6d0e59c7dd32819aea127b39bee5dd75a Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 14:17:15 -0400 Subject: [PATCH 13/34] Extract adding timeout to socket --- common/cross_sockets/xsocket.cpp | 11 +++++++++++ common/cross_sockets/xsocket.h | 1 + game/system/Deci2Server.cpp | 16 +--------------- goalc/listener/Listener.cpp | 15 +-------------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index 0fa79e6864..96a575e8f0 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -51,6 +51,17 @@ int set_socket_option(int socket, int level, int optname, const char* optval, in #endif } +int set_socket_timeout(int socket, int microSeconds) { +#ifdef __linux + timeval timeout = {}; + timeout.tv_sec = 0; + timeout.tv_usec = microSeconds; +#elif _WIN32 + unsigned long timeout = microSeconds / 1000; // milliseconds +#endif + return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); +} + int write_to_socket(int socket, const char* buf, int len) { #ifdef __linux return write(socket, buf, len); diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h index 8f7bb359ae..06f1711ae6 100644 --- a/common/cross_sockets/xsocket.h +++ b/common/cross_sockets/xsocket.h @@ -15,5 +15,6 @@ const int TCP_SOCKET_LEVEL = IPPROTO_TCP; 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 char* optval, int optlen); +int set_socket_timeout(int socket, int microSeconds); int write_to_socket(int socket, const char* buf, int len); int read_from_socket(int socket, char* buf, int len); diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 7579b60f65..cbcd924d37 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -74,24 +74,10 @@ bool Deci2Server::init() { return false; } -// TODO - put in library -#ifdef __linux - timeval timeout = {}; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (set_socket_option(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < - 0) { + if (set_socket_timeout(server_socket, 100000) < 0) { close_server_socket(); return false; } -#elif _WIN32 - unsigned long timeout = 100; // ms - if (set_socket_option(server_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < - 0) { - close_server_socket(); - return false; - } -#endif addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 11f62f0260..bdbcf47820 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -71,25 +71,12 @@ bool Listener::connect_to_target(const std::string& ip, int port) { return false; } - // TODO - put in library -#ifdef __linux - timeval timeout = {}; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { - close_socket(socket_fd); - socket_fd = -1; - return false; - } -#elif _WIN32 - unsigned long timeout = 100; // ms - if (set_socket_option(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) { + if (set_socket_timeout(socket_fd, 100000) < 0) { printf("[Listener] setsockopt failed\n"); close_socket(socket_fd); socket_fd = -1; return false; } -#endif // set nodelay, which makes small rapid messages faster, but large messages slower char one = 1; From 8f1ddca8256e33882b24541fc75365d3e9aa06ff Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 14:39:16 -0400 Subject: [PATCH 14/34] Add Coveralls.io --- .github/workflows/workflow.yaml | 7 +- CMakeLists.txt | 2 + cmake/modules/CodeCoverage.cmake | 436 +++++++++++++++++++++++++++++++ test-cov.sh | 11 + test.sh | 5 +- test/CMakeLists.txt | 13 +- 6 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 cmake/modules/CodeCoverage.cmake create mode 100644 test-cov.sh diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index cd8278cf4c..d510b598dd 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -30,7 +30,7 @@ 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 @@ -42,6 +42,11 @@ jobs: - name: Run Tests timeout-minutes: 5 run: ./test.sh + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./build/goalc-test_coverage.info build-windows: # Very good resource - https://cristianadam.eu/20191222/using-github-actions-with-c-plus-plus-and-cmake/ name: (Windows) Build & Test diff --git a/CMakeLists.txt b/CMakeLists.txt index b40377dc4a..b1cf012d2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,8 @@ IF (WIN32) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) ENDIF() +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) + # includes relative to top level jak-project folder include_directories(./) diff --git a/cmake/modules/CodeCoverage.cmake b/cmake/modules/CodeCoverage.cmake new file mode 100644 index 0000000000..27e7d3d404 --- /dev/null +++ b/cmake/modules/CodeCoverage.cmake @@ -0,0 +1,436 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags: +# append_coverage_compiler_flags() +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() +elseif(NOT CMAKE_COMPILER_IS_GNUCXX) + if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") + # Do nothing; exit conditional without error if true + elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + # Do nothing; exit conditional without error if true + else() + message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + endif() +endif() + +set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" + CACHE INTERNAL "") + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup lcov + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . -b ${BASEDIR} --zerocounters + # Create baseline to make sure untouched files show up in the report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b ${BASEDIR} -o ${Coverage_NAME}.base + + # Run tests + COMMAND ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + # add baseline counters + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + # filter collected data to final coverage report + COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + + # Generate HTML output + COMMAND ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${Coverage_NAME}.info + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${Coverage_NAME} # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Running gcovr + COMMAND ${GCOVR_PATH} --xml + -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}.xml + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(${Coverage_BASE_DIRECTORY}) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + + # Create folder + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + + # Running gcovr + COMMAND ${GCOVR_PATH} --html --html-details + -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}/index.html + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME} # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags diff --git a/test-cov.sh b/test-cov.sh new file mode 100644 index 0000000000..f5b98c515a --- /dev/null +++ b/test-cov.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Directory of this script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt +cd $DIR/build/test +make init +make gcov +make lcov \ No newline at end of file diff --git a/test.sh b/test.sh index 6370a8a2db..3541eb59f3 100755 --- a/test.sh +++ b/test.sh @@ -5,4 +5,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR export FAKE_ISO_PATH=/game/fake_iso.txt -$DIR/build/test/goalc-test --gtest_color=yes "$@" \ No newline at end of file +cd $DIR/build/ +make goalc-test_coverage +ls +ls .. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8173c9820c..c9ef3d5bde 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,3 @@ -enable_testing() - add_executable(goalc-test test_main.cpp test_test.cpp @@ -16,6 +14,8 @@ add_executable(goalc-test test_emitter_integer_math.cpp ) +enable_testing() + IF (WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # TODO - implement windows listener @@ -24,3 +24,12 @@ IF (WIN32) ELSE() target_link_libraries(goalc-test goos util listener runtime compiler type_system gtest) ENDIF() + +if(CMAKE_COMPILER_IS_GNUCXX) + 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 "test/*" "third-party/*" "/usr/include/*") +endif() From 570d64c6a37eca104f188f7709c318aa365904f1 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 15:52:31 -0400 Subject: [PATCH 15/34] Add coverage badge --- README.md | 1 + test.sh | 2 -- test/CMakeLists.txt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6b33344a30..e22cdce586 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Jak Project ![Build](https://github.com/water111/jak-project/workflows/Build/badge.svg) +[![Coverage Status](https://coveralls.io/repos/github/water111/jak-project/badge.svg?branch=master)](https://coveralls.io/github/water111/jak-project?branch=master) ## Table of Contents diff --git a/test.sh b/test.sh index 3541eb59f3..482651ed02 100755 --- a/test.sh +++ b/test.sh @@ -7,5 +7,3 @@ export NEXT_DIR=$DIR export FAKE_ISO_PATH=/game/fake_iso.txt cd $DIR/build/ make goalc-test_coverage -ls -ls .. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c9ef3d5bde..989eb211a4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,5 +31,5 @@ if(CMAKE_COMPILER_IS_GNUCXX) setup_target_for_coverage_lcov(NAME goalc-test_coverage EXECUTABLE goalc-test --gtest_color=yes DEPENDENCIES goalc-test - EXCLUDE "test/*" "third-party/*" "/usr/include/*") + EXCLUDE "third-party/*" "/usr/include/*") endif() From 699270c00cde594b6bf98c888ccc6dfae00ed95b Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 16:19:05 -0400 Subject: [PATCH 16/34] Add coverage flags to everything we compile --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1cf012d2b..2836a1fc54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,11 @@ ENDIF() set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) +if(CMAKE_COMPILER_IS_GNUCXX) + include(CodeCoverage) + append_coverage_compiler_flags() +endif() + # includes relative to top level jak-project folder include_directories(./) From fff501d78601d2410e649b473f1991bfbf7ebaf7 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Tue, 8 Sep 2020 19:15:14 -0400 Subject: [PATCH 17/34] Corrections to fix linux, and further cleanup --- common/cross_sockets/xsocket.cpp | 31 +++++++++++------ common/cross_sockets/xsocket.h | 4 +-- game/system/Deci2Server.cpp | 10 +++--- goalc/listener/Listener.cpp | 57 ++++++++++++++++---------------- goalc/listener/Listener.h | 2 +- test/test_listener_deci2.cpp | 10 +++--- 6 files changed, 62 insertions(+), 52 deletions(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index 96a575e8f0..a7f2827070 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -7,8 +7,9 @@ #include #include #endif - +#include #include +#include int open_socket(int af, int type, int protocol) { #ifdef __linux @@ -38,28 +39,36 @@ void close_socket(int sock) { #endif } -int set_socket_option(int socket, int level, int optname, const char* optval, int optlen) { -#ifdef __linux - return setsockopt(socket, level, optname, optval, optlen); -#elif _WIN32 - int ret = setsockopt(socket, level, optname, optval, optlen); +int set_socket_option(int socket, int level, int optname, const void* optval, int optlen) { + int ret = setsockopt(socket, level, optname, (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("Failed to setsockopt - Err: %d\n", err); + printf("WSAGetLastError: %d\n", err); } - return ret; #endif + return ret; } -int set_socket_timeout(int socket, int microSeconds) { +int set_socket_timeout(int socket, long microSeconds) { #ifdef __linux - timeval timeout = {}; + 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 unsigned long timeout = microSeconds / 1000; // milliseconds + return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #endif - return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); } int write_to_socket(int socket, const char* buf, int len) { diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h index 06f1711ae6..eb8e09b38c 100644 --- a/common/cross_sockets/xsocket.h +++ b/common/cross_sockets/xsocket.h @@ -14,7 +14,7 @@ const int TCP_SOCKET_LEVEL = IPPROTO_TCP; 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 char* optval, int optlen); -int set_socket_timeout(int socket, int microSeconds); +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); diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index cbcd924d37..4d7f466aab 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -56,28 +56,28 @@ bool Deci2Server::init() { #ifdef __linux int server_socket_opt = SO_REUSEADDR | SO_REUSEPORT; - int server_socket_tcp_level = SOL_TCP; #elif _WIN32 int server_socket_opt = SO_EXCLUSIVEADDRUSE; - int server_socket_tcp_level = IPPROTO_TCP; #endif - char opt = 1; + int opt = 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"); - if (set_socket_option(server_socket, server_socket_tcp_level, TCP_NODELAY, &opt, sizeof(opt)) < - 0) { + if (set_socket_option(server_socket, TCP_SOCKET_LEVEL, TCP_NODELAY, &opt, sizeof(opt)) < 0) { close_server_socket(); return false; } + printf("[Deci2Server] Created TCP Socket Options\n"); if (set_socket_timeout(server_socket, 100000) < 0) { close_server_socket(); return false; } + printf("[Deci2Server] Created Socket Timeout\n"); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index bdbcf47820..c47a0610d3 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -33,8 +33,8 @@ Listener::~Listener() { disconnect(); delete[] m_buffer; - if (socket_fd >= 0) { - close_socket(socket_fd); + if (listen_socket >= 0) { + close_socket(listen_socket); } } @@ -59,31 +59,29 @@ bool Listener::connect_to_target(const std::string& ip, int port) { throw std::runtime_error("attempted a Listener::connect_to_target when already connected!"); } - if (socket_fd >= 0) { - close_socket(socket_fd); + if (listen_socket >= 0) { + close_socket(listen_socket); } // construct socket - socket_fd = open_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; } - if (set_socket_timeout(socket_fd, 100000) < 0) { - printf("[Listener] setsockopt failed\n"); - close_socket(socket_fd); - socket_fd = -1; + if (set_socket_timeout(listen_socket, 100000) < 0) { + close_socket(listen_socket); + listen_socket = -1; return false; } // set nodelay, which makes small rapid messages faster, but large messages slower - char one = 1; - if (set_socket_option(socket_fd, TCP_SOCKET_LEVEL, TCP_NODELAY, &one, sizeof(one))) { - printf("[Listener] failed to TCP_NODELAY\n"); - close_socket(socket_fd); - socket_fd = -1; + int one = 1; + if (set_socket_option(listen_socket, TCP_SOCKET_LEVEL, TCP_NODELAY, &one, sizeof(one))) { + close_socket(listen_socket); + listen_socket = -1; return false; } @@ -93,17 +91,17 @@ 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(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 = connect(listen_socket, (sockaddr*)&server_address, sizeof(server_address)); if (rv < 0) { printf("[Listener] Failed to connect\n"); - close_socket(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } @@ -113,7 +111,7 @@ bool Listener::connect_to_target(const std::string& ip, int port) { int prog = 0; bool ok = true; while (prog < 8) { - auto r = read_from_socket(socket_fd, (char*)version_buffer + prog, 8 - prog); + auto r = read_from_socket(listen_socket, (char*)version_buffer + prog, 8 - prog); if (r < 0) { ok = false; break; @@ -127,8 +125,8 @@ bool Listener::connect_to_target(const std::string& ip, int port) { } if (!ok) { printf("[Listener] Failed to get version number\n"); - close_socket(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } @@ -141,8 +139,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(socket_fd); - socket_fd = -1; + close_socket(listen_socket); + listen_socket = -1; return false; } } @@ -158,7 +156,7 @@ void Listener::receive_func() { int rcvd_desired = sizeof(ListenerMessageHeader); char buff[sizeof(ListenerMessageHeader)]; while (rcvd < rcvd_desired) { - auto got = read_from_socket(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 @@ -191,7 +189,7 @@ void Listener::receive_func() { while (rcvd < hdr->deci2_header.len) { if (!m_connected) return; - int got = read_from_socket(socket_fd, ack_recv_buff + ack_recv_prog, + 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; @@ -214,7 +212,8 @@ void Listener::receive_func() { return; } - int got = read_from_socket(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; diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 5ae49e2f5f..8d8fd25982 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -29,7 +29,7 @@ class Listener { char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? bool receive_thread_running = false; //! is the receive thread unjoined? - int socket_fd = -1; //! socket + int listen_socket = -1; //! socket bool got_ack = false; bool waiting_for_ack = false; diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index a2ffd96e8c..182d3dbd49 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -55,9 +55,10 @@ TEST(Listener, DeciThenListener) { Listener l; EXPECT_FALSE(s.check_for_listener()); EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); + bool connected = l.connect_to_target(); + EXPECT_TRUE(connected); // TODO - some sort of backoff and retry would be better - while (!s.check_for_listener()) { + while (connected && !s.check_for_listener()) { } EXPECT_TRUE(s.check_for_listener()); @@ -85,9 +86,10 @@ TEST(Listener, ListenerThenDeci) { Deci2Server s(always_false); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); - EXPECT_TRUE(l.connect_to_target()); + bool connected = l.connect_to_target(); + EXPECT_TRUE(connected); // TODO - some sort of backoff and retry would be better - while (!s.check_for_listener()) { + while (connected && !s.check_for_listener()) { } } } From c9e99b8ebe42f582852d7dfbab420186492a43a4 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 19:15:38 -0400 Subject: [PATCH 18/34] add a separate cmake configuration for code coverage builds --- .github/workflows/workflow.yaml | 6 +++--- CMakeLists.txt | 6 +++++- test.sh | 3 +-- test/CMakeLists.txt | 2 +- test_code_coverage.sh | 9 +++++++++ 5 files changed, 19 insertions(+), 7 deletions(-) create mode 100755 test_code_coverage.sh diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index d510b598dd..59ceb6bb53 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -34,14 +34,14 @@ jobs: - 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index 2836a1fc54..905e3426bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,11 +33,15 @@ IF (WIN32) 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) +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 diff --git a/test.sh b/test.sh index 482651ed02..573dafc226 100755 --- a/test.sh +++ b/test.sh @@ -5,5 +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 -cd $DIR/build/ -make goalc-test_coverage +$DIR/build/test/goalc-test --gtest_color=yes "$@" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 989eb211a4..b10147fc59 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,7 @@ ELSE() target_link_libraries(goalc-test goos util listener runtime compiler type_system gtest) ENDIF() -if(CMAKE_COMPILER_IS_GNUCXX) +if(CMAKE_COMPILER_IS_GNUCXX AND CODE_COVERAGE) include(CodeCoverage) append_coverage_compiler_flags() setup_target_for_coverage_lcov(NAME goalc-test_coverage diff --git a/test_code_coverage.sh b/test_code_coverage.sh new file mode 100755 index 0000000000..482651ed02 --- /dev/null +++ b/test_code_coverage.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Directory of this script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt +cd $DIR/build/ +make goalc-test_coverage From d35a8a3867942402661938cef710657aca295b5c Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 19:43:03 -0400 Subject: [PATCH 19/34] usleep -> this_thread::sleep_for --- goalc/listener/Listener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 2081babadb..c3d8be0a4b 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -376,7 +376,7 @@ bool Listener::wait_for_ack() { for (int i = 0; i < 2000; i++) { if (got_ack) return true; - usleep(1000); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); } waiting_for_ack = false; From 413bd66ce79be762ddcf6d6cb6a251347605b5c9 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 19:46:40 -0400 Subject: [PATCH 20/34] fix windows min/max macro issue --- goalc/listener/Listener.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index c3d8be0a4b..a8aec4bc62 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -14,6 +14,10 @@ #define WIN32_LEAN_AND_MEAN #include #include + +// remove the evil windows min/max macros! +#undef min +#undef max #endif // TODO - i think im not including the dependency right..? From f55c2d305d810e494688bef11e6d82d29f3f2d34 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 19:49:58 -0400 Subject: [PATCH 21/34] another fix --- goalc/compiler/Compiler.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 29c5c4d95f..1edede33bd 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -3,7 +3,8 @@ #include "common/link_types.h" #include "IR.h" #include "goalc/regalloc/allocate.h" -#include "unistd.h" +#include +#include using namespace goos; @@ -167,7 +168,7 @@ std::vector Compiler::run_test(const std::string& source_code) { if (!m_listener.is_connected()) { for (int i = 0; i < 1000; i++) { m_listener.connect_to_target(); - usleep(10000); + std::this_thread::sleep_for(std::chrono::microseconds(10000)); if (m_listener.is_connected()) { break; } From 5612b9e1de36de4c09d697366c94df0ceb1574fa Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 19:57:58 -0400 Subject: [PATCH 22/34] fix again --- goalc/compiler/compilation/CompilerControl.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index ae85bc07e1..6d09bf9067 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -63,7 +63,13 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re timing.emplace_back("read", reader_timer.getMs()); Timer compile_timer; - std::string obj_file_name = basename(filename.c_str()); + 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()); From 551d9897328e93ed2e4aed3463906a374692ab82 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:01:46 -0400 Subject: [PATCH 23/34] so close --- test/test_compiler_and_runtime.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp index 8192b3a464..172b13bac1 100644 --- a/test/test_compiler_and_runtime.cpp +++ b/test/test_compiler_and_runtime.cpp @@ -1,4 +1,5 @@ #include +#include #include "gtest/gtest.h" #include "game/runtime.h" @@ -15,7 +16,7 @@ TEST(CompilerAndRuntime, StartRuntime) { listener::Listener listener; while (!listener.is_connected()) { listener.connect_to_target(); - usleep(1000); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); } listener.send_reset(true); From 6de430b4efd6c1cb4597edaf6363a82a5bc20999 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:10:03 -0400 Subject: [PATCH 24/34] temporary hacks in fakeiso to see if it can work in windows --- game/overlord/fake_iso.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index 97f85fa18b..7668ee79ed 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -97,10 +97,13 @@ int FS_Init(u8* buffer) { fseek(fp, 0, SEEK_END); size_t len = ftell(fp); rewind(fp); - char* fakeiso = (char*)malloc(len); + char* fakeiso = (char*)malloc(len + 1); if (fread(fakeiso, len, 1, fp) != 1) { +#ifdef __linux__ assert(false); +#endif } + fakeiso[len] = '\0'; // loop over lines char* ptr = fakeiso; @@ -137,6 +140,7 @@ int FS_Init(u8* buffer) { ptr++; i++; } + e->file_path[i] = 0; fake_iso_entry_count++; } From 4825b0d1e59680f15156a033560252287bf32740 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:14:37 -0400 Subject: [PATCH 25/34] add a slash on windows --- game/overlord/fake_iso.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index 7668ee79ed..45603a03c6 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -198,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; } From f73b6de2546c6c071c1acf90e934bc18fe33a70c Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:29:48 -0400 Subject: [PATCH 26/34] try setting a timeout on the accepted socket --- game/system/Deci2Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 4d7f466aab..ca7f1b0f63 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -263,6 +263,7 @@ void Deci2Server::accept_thread_func() { // close new_sock = accept(server_socket, (sockaddr*)&addr, &l); if (new_sock >= 0) { + set_socket_timeout(new_sock, 100000); u32 versions[2] = {versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR}; write_to_socket(new_sock, (char*)&versions, 8); // todo, check result? server_connected = true; From fb62936a94796e58ae4bc1e0f64c30a7ba532846 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:37:03 -0400 Subject: [PATCH 27/34] add debug prints --- game/system/Deci2Server.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index ca7f1b0f63..e7b7207fb9 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -186,10 +186,13 @@ void Deci2Server::send_proto_ready(Deci2Driver* drivers, int* driver_count) { void Deci2Server::run() { int desired_size = (int)sizeof(Deci2Header); int got = 0; + printf("Deci2Server::run\n"); while (got < desired_size) { assert(got + desired_size < BUFFER_SIZE); + printf("r1\n"); auto x = read_from_socket(new_sock, buffer + got, desired_size - got); + printf("r1 done\n"); if (want_exit()) { return; } @@ -240,7 +243,9 @@ void Deci2Server::run() { // receive from network if (hdr->rsvd < hdr->len) { + printf("r2\n"); auto x = read_from_socket(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd); + printf("r2 done\n"); if (want_exit()) { return; } @@ -251,6 +256,7 @@ void Deci2Server::run() { (driver.handler)(DECI2_READDONE, 0, driver.opt); unlock(); + printf("run done\n"); } /*! From e5631ea5c8c70dffbc1709ec91f1a78bbbc2f79f Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:46:09 -0400 Subject: [PATCH 28/34] debug print --- common/cross_sockets/xsocket.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index a7f2827070..eb6e5ce667 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -66,7 +66,8 @@ int set_socket_timeout(int socket, long microSeconds) { } return ret; #elif _WIN32 - unsigned long timeout = microSeconds / 1000; // milliseconds + DWORD timeout = microSeconds / 1000; // milliseconds + printf("setting timeout to %d ms\n", timeout); return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #endif } From a47504b812c176490e1048b078ee03df5d5095ab Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:49:37 -0400 Subject: [PATCH 29/34] only take address once --- common/cross_sockets/xsocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index eb6e5ce667..7dff6415e3 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -40,7 +40,7 @@ void close_socket(int sock) { } int set_socket_option(int socket, int level, int optname, const void* optval, int optlen) { - int ret = setsockopt(socket, level, optname, (char*)&optval, optlen); + int ret = setsockopt(socket, level, optname, (const char*)optval, optlen); if (ret < 0) { printf("Failed to setsockopt(%d, %d, %d, _, _) - Error: %s\n", socket, level, optname, strerror(errno)); From c04ab1a6ff05da514f81847ed4fbbe2bc43c9994 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:55:48 -0400 Subject: [PATCH 30/34] try different print format for windows --- common/cross_sockets/xsocket.cpp | 1 - game/kernel/kboot.cpp | 4 ++++ game/system/Deci2Server.cpp | 6 ------ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index 7dff6415e3..f4c89df7e8 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -67,7 +67,6 @@ int set_socket_timeout(int socket, long microSeconds) { return ret; #elif _WIN32 DWORD timeout = microSeconds / 1000; // milliseconds - printf("setting timeout to %d ms\n", timeout); return set_socket_option(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #endif } diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index d005026d31..791f9d5045 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -150,7 +150,11 @@ void KernelCheckAndDispatch() { printf("\n"); auto result = call_goal(Ptr(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem); +#ifdef __linux__ cprintf("%ld\n", result); +#else + cprintf("%lld\n", result) +#endif ListenerFunction->value = s7.offset; } } diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index e7b7207fb9..ca7f1b0f63 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -186,13 +186,10 @@ void Deci2Server::send_proto_ready(Deci2Driver* drivers, int* driver_count) { void Deci2Server::run() { int desired_size = (int)sizeof(Deci2Header); int got = 0; - printf("Deci2Server::run\n"); while (got < desired_size) { assert(got + desired_size < BUFFER_SIZE); - printf("r1\n"); auto x = read_from_socket(new_sock, buffer + got, desired_size - got); - printf("r1 done\n"); if (want_exit()) { return; } @@ -243,9 +240,7 @@ void Deci2Server::run() { // receive from network if (hdr->rsvd < hdr->len) { - printf("r2\n"); auto x = read_from_socket(new_sock, buffer + hdr->rsvd, hdr->len - hdr->rsvd); - printf("r2 done\n"); if (want_exit()) { return; } @@ -256,7 +251,6 @@ void Deci2Server::run() { (driver.handler)(DECI2_READDONE, 0, driver.opt); unlock(); - printf("run done\n"); } /*! From 2e32bf792323c00396559090e068e9262c6d9384 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 20:58:03 -0400 Subject: [PATCH 31/34] semicolon --- game/kernel/kboot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 791f9d5045..1b54215ada 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -153,7 +153,7 @@ void KernelCheckAndDispatch() { #ifdef __linux__ cprintf("%ld\n", result); #else - cprintf("%lld\n", result) + cprintf("%lld\n", result); #endif ListenerFunction->value = s7.offset; } From 24e79f66bc6f25bb79cef9b8ab751c5f3e987d63 Mon Sep 17 00:00:00 2001 From: water Date: Tue, 8 Sep 2020 21:26:09 -0400 Subject: [PATCH 32/34] try increasing timeouts and decreasing test iterations to make test shorter --- goalc/listener/Listener.cpp | 2 +- test/test_listener_deci2.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index a8aec4bc62..454dcd46f3 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -83,7 +83,7 @@ bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) { return false; } - if (set_socket_timeout(listen_socket, 100000) < 0) { + if (set_socket_timeout(listen_socket, 500000) < 0) { close_socket(listen_socket); listen_socket = -1; return false; diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 182d3dbd49..158a7afb7a 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -46,7 +46,7 @@ TEST(Listener, DeciCheckNoListener) { } TEST(Listener, DeciThenListener) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Deci2Server s(always_false); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); @@ -66,7 +66,7 @@ TEST(Listener, DeciThenListener) { } TEST(Listener, DeciThenListener2) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Deci2Server s(always_false); EXPECT_TRUE(s.init()); EXPECT_FALSE(s.check_for_listener()); @@ -80,7 +80,7 @@ TEST(Listener, DeciThenListener2) { } TEST(Listener, ListenerThenDeci) { - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 3; i++) { Listener l; EXPECT_FALSE(l.connect_to_target()); Deci2Server s(always_false); From 83b6db9f334f86c75ba15b64d0077dd3f1737d39 Mon Sep 17 00:00:00 2001 From: water Date: Wed, 9 Sep 2020 17:00:46 -0400 Subject: [PATCH 33/34] update to c++17 --- CMakeLists.txt | 2 +- game/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc3e6a860..4ab9843521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ 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. diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index e51dde9e25..d73e4968fe 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -1,5 +1,5 @@ # We define our own compilation flags here. -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) # Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. From 01883da47eabc768079559d9109f044940cbe5ee Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Wed, 9 Sep 2020 20:14:13 -0400 Subject: [PATCH 34/34] Fix Listener Socket Timeout on Windows (#28) * try checking for timeouts differently on windows * hopefully this test fails on windows * hopefully this test passes on windows * remove debug prints * remove commented otu code --- common/cross_sockets/xsocket.cpp | 9 +++++++++ common/cross_sockets/xsocket.h | 1 + goalc/listener/Listener.cpp | 13 +++++-------- test/test_listener_deci2.cpp | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/common/cross_sockets/xsocket.cpp b/common/cross_sockets/xsocket.cpp index f4c89df7e8..763cf596f4 100644 --- a/common/cross_sockets/xsocket.cpp +++ b/common/cross_sockets/xsocket.cpp @@ -86,3 +86,12 @@ int read_from_socket(int socket, char* buf, int len) { return recv(socket, buf, len, 0); #endif } + +bool socket_timed_out() { +#ifdef __linux + return errno == EAGAIN; +#elif _WIN32 + auto err = WSAGetLastError(); + return err == WSAETIMEDOUT; +#endif +} \ No newline at end of file diff --git a/common/cross_sockets/xsocket.h b/common/cross_sockets/xsocket.h index eb8e09b38c..d0e4bb1c16 100644 --- a/common/cross_sockets/xsocket.h +++ b/common/cross_sockets/xsocket.h @@ -18,3 +18,4 @@ int set_socket_option(int socket, int level, int optname, const void* optval, in int set_socket_timeout(int socket, long microSeconds); int write_to_socket(int socket, const char* buf, int len); int read_from_socket(int socket, char* buf, int len); +bool socket_timed_out(); \ No newline at end of file diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 454dcd46f3..1fdb4bbe3d 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -123,7 +123,7 @@ bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) { listen_socket = -1; return false; } else { - printf("[Listener] Socket connected established! (took %d tries)\n", i); + 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 @@ -133,11 +133,8 @@ bool Listener::connect_to_target(int n_tries, const std::string& ip, int port) { bool ok = true; while (prog < 8) { auto r = read_from_socket(listen_socket, (char*)version_buffer + prog, 8 - prog); - if (r < 0) { - ok = false; - break; - } - prog += r; + std::this_thread::sleep_for(std::chrono::microseconds(100000)); + prog += r > 0 ? r : 0; read_tries++; if (read_tries > 50) { ok = false; @@ -181,7 +178,7 @@ void Listener::receive_func() { 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; } @@ -238,7 +235,7 @@ void Listener::receive_func() { 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; } } diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 158a7afb7a..ebc5aedeab 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -45,6 +45,24 @@ 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 < 3; i++) { Deci2Server s(always_false);