From ee4eb9f12823b0b4c8ad4a653dbdff1f13492dad Mon Sep 17 00:00:00 2001 From: water Date: Mon, 7 Sep 2020 13:28:16 -0400 Subject: [PATCH] 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 000000000..148bdb50b --- /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 2b41efe59..c43173753 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 9f6f6de06..b8605b36f 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 fd53697f1..3ac75533e 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 4752b90b9..6a030104f 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 d2c41a96c..bec0f1be1 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 af0e1fad0..09f71dab9 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 000000000..27e30228f --- /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 000000000..2dfc9036f --- /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 000000000..3ac5f6a80 --- /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 000000000..8a7c3981c --- /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 63cdb110e..1a26b2ec1 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 d9d062e9b..a72875317 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 16722ea81..9f49720f3 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 8a3c7baad..c81671c6e 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 71e17bf7f..33afba90b 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 be5e6bd52..b06845e0b 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 ed40906c9..435055de1 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 c0c4f2e72..06c3e2030 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 9ee9b86f2..1313aa642 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 7fd4039d5..aa66b84e2 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 2d83f48ff..ae85bc07e 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 000000000..336d711a7 --- /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 275207c19..a5d6b6aa2 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 b18e1468c..cf338f4b8 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 285f607fb..7e1c41131 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 d58f46670..3d58a5c36 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 f971dc35b..961a3cb60 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 38c453c6d..b26b61994 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 609a1fbb6..fdca90d0c 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 000000000..4ac44ab25 --- /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 000000000..597202033 --- /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 d6b35e173..095ad933a 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 aae7340ee..9fa780204 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 a03b01177..fffb74b45 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();