From 9ec9b5a22a03d126795e36c3aa288be5d7ea03f2 Mon Sep 17 00:00:00 2001 From: water Date: Sun, 13 Sep 2020 17:34:02 -0400 Subject: [PATCH] add conditional stuff --- game/overlord/iso.cpp | 5 +- goal_src/goal-lib.gc | 58 ++++++ goal_src/kernel-defs.gc | 2 +- goal_src/kernel/gcommon.gc | 72 ++++++- goal_src/test/test-build-game.gc | 6 +- goal_src/test/test-defsmacro-defgmacro.gc | 13 ++ goal_src/test/test-desfun.gc | 10 + goal_src/test/test-div-1.gc | 1 + goal_src/test/test-div-2.gc | 3 + goal_src/test/test-factorial-loop.gc | 11 + goal_src/test/test-factorial-recursive.gc | 12 ++ goal_src/test/test-mlet.gc | 7 + goal_src/test/test-protect.gc | 15 ++ goal_src/test/test-set-symbol.gc | 18 ++ goal_src/test/test-three-reg-add.gc | 3 + goal_src/test/test-three-reg-mult.gc | 6 + goal_src/test/test-three-reg-sub.gc | 5 + goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.h | 9 + goalc/compiler/Env.cpp | 17 +- goalc/compiler/Env.h | 29 ++- goalc/compiler/IR.cpp | 176 +++++++++++++++- goalc/compiler/IR.h | 29 +++ goalc/compiler/Util.cpp | 4 + goalc/compiler/Val.cpp | 16 +- goalc/compiler/compilation/Atoms.cpp | 34 ++-- goalc/compiler/compilation/ControlFlow.cpp | 225 +++++++++++++++++++++ goalc/compiler/compilation/Define.cpp | 39 ++++ goalc/compiler/compilation/Macro.cpp | 18 ++ goalc/compiler/compilation/Math.cpp | 27 ++- goalc/main.cpp | 2 +- test/test_compiler_and_runtime.cpp | 14 ++ 32 files changed, 843 insertions(+), 44 deletions(-) create mode 100644 goal_src/test/test-defsmacro-defgmacro.gc create mode 100644 goal_src/test/test-desfun.gc create mode 100644 goal_src/test/test-div-1.gc create mode 100644 goal_src/test/test-div-2.gc create mode 100644 goal_src/test/test-factorial-loop.gc create mode 100644 goal_src/test/test-factorial-recursive.gc create mode 100644 goal_src/test/test-mlet.gc create mode 100644 goal_src/test/test-protect.gc create mode 100644 goal_src/test/test-set-symbol.gc create mode 100644 goal_src/test/test-three-reg-add.gc create mode 100644 goal_src/test/test-three-reg-mult.gc create mode 100644 goal_src/test/test-three-reg-sub.gc create mode 100644 goalc/compiler/compilation/ControlFlow.cpp diff --git a/game/overlord/iso.cpp b/game/overlord/iso.cpp index b374e3388..17424022b 100644 --- a/game/overlord/iso.cpp +++ b/game/overlord/iso.cpp @@ -597,8 +597,9 @@ u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) { // once we're done, send the header to the EE, and start reading object data if (cmd->bytes_processed == sizeof(ObjectHeader)) { - printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent to 0x%p)\n", - cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer); + // printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent + // to 0x%p)\n", + // cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer); DMA_SendToEE(&cmd->objHeader, sizeof(ObjectHeader), cmd->ee_destination_buffer); DMA_Sync(); cmd->ee_destination_buffer += sizeof(ObjectHeader); diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index 77ae4e31f..aaf565df3 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -33,6 +33,14 @@ ) ) +(defmacro blg () + `(begin + (build-game) + (dgo-load "kernel" global #xf #x200000) + (dgo-load "game" global #xf #x200000) + ) + ) + (defmacro e () `(:exit) ) @@ -109,4 +117,54 @@ ;; otherwise don't ignore it. `(define ,name (lambda :name ,name ,bindings ,@body)) ) + ) + +(defmacro while (test &rest body) + (with-gensyms (reloop test-exit) + `(begin + (goto ,test-exit) + (label ,reloop) + ,@body + (label ,test-exit) + (when-goto ,test ,reloop) + #f + ) + ) + ) + +;; Backup some values, and restore after executing body. +;; Non-dynamic (nonlocal jumps out of body will skip restore) +(defmacro protect (defs &rest body) + (if (null? defs) + ;; nothing to backup, just insert body (base case) + `(begin ,@body) + + ;; a unique name for the thing we are backing up + (with-gensyms (backup) + ;; store the original value of the first def in backup + `(let ((,backup ,(first defs))) + ;; backup any other things which need backing up + (protect ,(cdr defs) + ;; execute the body + ,@body + ) + ;; restore the first thing + (set! ,(first defs) ,backup) + ) + ) + ) + ) + +(defmacro +! (place amount) + `(set! ,place (+ ,place ,amount)) + ) + +;; todo, handle too many arguments correct +(defmacro if (condition true-case &rest others) + (if (null? others) + `(cond (,condition ,true-case)) + `(cond (,condition ,true-case) + (else ,(first others)) + ) + ) ) \ No newline at end of file diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index 3c97a5608..862d2c7ff 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -77,7 +77,7 @@ (define-extern new-dynamic-structure (function kheap type int structure)) (define-extern method-set! (function type int function none)) ;; may actually return function. (define-extern link (function pointer string int kheap int pointer)) -(define-extern dgo-load (function string kheap int int)) +(define-extern dgo-load (function string kheap int int none)) (define-extern link-begin (function pointer string int kheap int int)) (define-extern link-resume (function int)) ;; mc-run diff --git a/goal_src/kernel/gcommon.gc b/goal_src/kernel/gcommon.gc index 057dfb5d3..74b27d1fe 100644 --- a/goal_src/kernel/gcommon.gc +++ b/goal_src/kernel/gcommon.gc @@ -18,7 +18,77 @@ ;; there is an optional "docstring" that can go at the beginning of a function "Function which returns its input. The first function of the game!" - ;; the last thing in the function body is the return value. + ;; the last thing in the function body is the return value. This is like "return x;" in C + ;; the return type of the function is figured out automatically by the compiler + ;; you don't have to specify it manually. x ) + +(defun 1/ ((x float)) + "Reciprocal floating point" + + ;; this function computes 1.0 / x. GOAL allows strange function names like "1/". + + ;; Declaring this an inline function is like a C inline function, however code is + ;; still generated so it can be used a function object. GOAL inline functions have type + ;; checking, so they are preferable to macros when possible, to get better error messages. + (declare (inline)) + + ;; the division form will pick the math type (float, int) based on the type of the first + ;; argument. In this case, "1." is a floating point constant, so this becomes a floating point division. + (/ 1. x) + ) + +(defun + ((x int) (y int)) + "Compute the sum of two integers" + + ;; this wraps the compiler's built-in handling of "add two integers" in a GOAL function. + ;; now "+" can be used as a function object, but is limited to adding two integers when used like this. + ;; The compiler is smart enough to not use this function unless "+" is being used as a function object. + ;; ex: (+ a b c), (+ a b) ; won't use this function, uses built-in addition + ;; (set-combination-function! my-thing +) ; + becomes a function pointer in this case + (+ x y) + ) + +(defun - ((x int) (y int)) + "Compute the difference of two integers" + (- x y) + ) + +(defun * ((x int) (y int)) + "Compute the product of two integers" + ;; TODO - verify that this matches the PS2 exactly. + ;; Uses mult (three operand form) in MIPS + (* x y) + ) + +(defun / ((x int) (y int)) + "Compute the quotient of two integers" + ;; TODO - verify this matches the PS2 exactly + (/ x y) + ) + +;; todo ash +;; todo mod +;; todo rem + +(defun abs ((a int)) + "Take the absolute value of an integer" + + ;; short function, good candidate for inlining + (declare (inline)) + + ;; The original implementation was inline assembly, to take advantage of branch delay slots: + ;; (or v0 a0 r0) ;; move input to output unchanged, for positive case + ;; (bltzl v0 end) ;; if negative, execute the branch delay slot below... + ;; (dsubu v0 r0 v0) ;; negate + ;; (label end) + + + (if (> a 0) ;; condition is "a > 0" + a ;; true case, return a + (- a) ;; false case, return -a. (- a) is like (- 0 a) + ) + ) + diff --git a/goal_src/test/test-build-game.gc b/goal_src/test/test-build-game.gc index 83f28a46f..fe473f2e1 100644 --- a/goal_src/test/test-build-game.gc +++ b/goal_src/test/test-build-game.gc @@ -1,6 +1,4 @@ (build-game) -;(define-extern global kheap) -;(define-extern dgo-load (function string kheap int int int)) -;(dgo-load "kernel" global #xf #x200000) ; todo, remove once kernel loads itself. -;(dgo-load "game" global #xf #x200000) +(dgo-load "kernel" global #xf #x200000) ; todo, remove once kernel loads itself. +(dgo-load "game" global #xf #x200000) 1 \ No newline at end of file diff --git a/goal_src/test/test-defsmacro-defgmacro.gc b/goal_src/test/test-defsmacro-defgmacro.gc new file mode 100644 index 000000000..8917115af --- /dev/null +++ b/goal_src/test/test-defsmacro-defgmacro.gc @@ -0,0 +1,13 @@ +(defsmacro test-macro (x) + ;; goos macro + `(+ ,x 2) + ) + +(defmacro test-macro (x) + ;; goal macro which calls a goos macro of the same name + (let ((goos-expansion (test-macro x))) + `(+ ,goos-expansion 3) + ) + ) + +(test-macro 15) \ No newline at end of file diff --git a/goal_src/test/test-desfun.gc b/goal_src/test/test-desfun.gc new file mode 100644 index 000000000..5e8f8f100 --- /dev/null +++ b/goal_src/test/test-desfun.gc @@ -0,0 +1,10 @@ +(desfun square (x) + (* x x) + ) + + +(defmacro call-square (x) + (square x) + ) + +(call-square 2) \ No newline at end of file diff --git a/goal_src/test/test-div-1.gc b/goal_src/test/test-div-1.gc new file mode 100644 index 000000000..d8b1fc6cb --- /dev/null +++ b/goal_src/test/test-div-1.gc @@ -0,0 +1 @@ +(+ 1 2 (/ (* 2 5) (+ 1 2))) \ No newline at end of file diff --git a/goal_src/test/test-div-2.gc b/goal_src/test/test-div-2.gc new file mode 100644 index 000000000..bb6606249 --- /dev/null +++ b/goal_src/test/test-div-2.gc @@ -0,0 +1,3 @@ +(let ((x 30)) + (+ (/ x 10) 4) + ) \ No newline at end of file diff --git a/goal_src/test/test-factorial-loop.gc b/goal_src/test/test-factorial-loop.gc new file mode 100644 index 000000000..5eac59b43 --- /dev/null +++ b/goal_src/test/test-factorial-loop.gc @@ -0,0 +1,11 @@ +(defun factorial-iterative ((x integer)) + (let ((result 1)) + (while (!= x 1) + (set! result (* result x)) + (set! x (- x 1)) + ) + result + ) + ) + +(factorial-iterative 10) \ No newline at end of file diff --git a/goal_src/test/test-factorial-recursive.gc b/goal_src/test/test-factorial-recursive.gc new file mode 100644 index 000000000..390981766 --- /dev/null +++ b/goal_src/test/test-factorial-recursive.gc @@ -0,0 +1,12 @@ +;; for now, recursive functions need to forward declare so they have their +;; return type. +;(defun-extern factorial-recursive ((x integer)) integer) +(define-extern factorial-recursive (function integer integer)) + +(defun factorial-recursive ((x integer)) + (cond ((= x 1) x) + (else (* x (factorial-recursive (- x 1)))) + ) + ) + +(factorial-recursive 10) \ No newline at end of file diff --git a/goal_src/test/test-mlet.gc b/goal_src/test/test-mlet.gc new file mode 100644 index 000000000..0b034d8cd --- /dev/null +++ b/goal_src/test/test-mlet.gc @@ -0,0 +1,7 @@ +(defglobalconstant c1 3) + +(+ c1 (mlet ((c1 4)) + c1 + ) + c1 + ) diff --git a/goal_src/test/test-protect.gc b/goal_src/test/test-protect.gc new file mode 100644 index 000000000..7c7025e24 --- /dev/null +++ b/goal_src/test/test-protect.gc @@ -0,0 +1,15 @@ +(let ((var1 10) + (var2 20) + (sum 0) + ) + + (protect (var1 var2) + (set! var1 1) + (set! var2 2) + (+! sum var1) + (+! sum var2) + ) + (+! sum var1) + (+! sum var2) + sum + ) diff --git a/goal_src/test/test-set-symbol.gc b/goal_src/test/test-set-symbol.gc new file mode 100644 index 000000000..910d3eef6 --- /dev/null +++ b/goal_src/test/test-set-symbol.gc @@ -0,0 +1,18 @@ + +(defun burp ((thing integer)) + (* thing 3) + ) + +(define thing 2) + +(set! thing 3) + + +(+ (let ((thing 4)) + (set! thing (+ thing 1)) + thing + ) + (burp thing) + (set! thing 4) + thing + ) diff --git a/goal_src/test/test-three-reg-add.gc b/goal_src/test/test-three-reg-add.gc new file mode 100644 index 000000000..16d3aa526 --- /dev/null +++ b/goal_src/test/test-three-reg-add.gc @@ -0,0 +1,3 @@ +(let ((x 3)) + (+ (+ x 1) x) + ) \ No newline at end of file diff --git a/goal_src/test/test-three-reg-mult.gc b/goal_src/test/test-three-reg-mult.gc new file mode 100644 index 000000000..5dc9a1ce4 --- /dev/null +++ b/goal_src/test/test-three-reg-mult.gc @@ -0,0 +1,6 @@ + +(let ((x 3)) + (* x 4) + (* x) + x + ) diff --git a/goal_src/test/test-three-reg-sub.gc b/goal_src/test/test-three-reg-sub.gc new file mode 100644 index 000000000..994d0619c --- /dev/null +++ b/goal_src/test/test-three-reg-sub.gc @@ -0,0 +1,5 @@ +(let ((x 3)) + (- x 1) + (- x) + x + ) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 0c9ba1f0f..3c309bec0 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(compiler compiler/compilation/Math.cpp compiler/compilation/Define.cpp compiler/compilation/Function.cpp + compiler/compilation/ControlFlow.cpp compiler/Util.cpp logger/Logger.cpp regalloc/IRegister.cpp diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 2dbfb5386..2241a52e3 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -99,6 +99,7 @@ class Compiler { Val* number_to_float(Val* in, Env* env); Val* number_to_binteger(Val* in, Env* env); Val* to_math_type(Val* in, MathMode mode, Env* env); + bool is_none(Val* in); public: // Atoms @@ -123,14 +124,22 @@ class Compiler { Val* compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env); + // ControlFlow + Condition compile_condition(const goos::Object& condition, Env* env, bool invert); + Val* compile_condition_as_bool(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_when_goto(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_cond(const goos::Object& form, const goos::Object& rest, Env* env); + // Define Val* compile_define(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_define_extern(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_set(const goos::Object& form, const goos::Object& rest, Env* env); // Macro Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_defglobalconstant(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env); // Math Val* compile_add(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/Env.cpp b/goalc/compiler/Env.cpp index 07ebf17c3..1e2b5ebd0 100644 --- a/goalc/compiler/Env.cpp +++ b/goalc/compiler/Env.cpp @@ -31,7 +31,7 @@ void Env::constrain_reg(IRegConstraint constraint) { /*! * Lookup the given symbol object as a lexical variable. */ -Val* Env::lexical_lookup(goos::Object sym) { +RegVal* Env::lexical_lookup(goos::Object sym) { return m_parent->lexical_lookup(std::move(sym)); } @@ -93,7 +93,7 @@ void GlobalEnv::constrain_reg(IRegConstraint constraint) { /*! * Lookup the given symbol object as a lexical variable. */ -Val* GlobalEnv::lexical_lookup(goos::Object sym) { +RegVal* GlobalEnv::lexical_lookup(goos::Object sym) { (void)sym; return nullptr; } @@ -218,6 +218,15 @@ void FunctionEnv::resolve_gotos() { } gt.ir->resolve(&kv_label->second); } + + for (auto& gt : unresolved_cond_gotos) { + auto kv_label = m_labels.find(gt.label); + if (kv_label == m_labels.end()) { + throw std::runtime_error("invalid when-goto destination " + gt.label); + } + gt.ir->label = kv_label->second; + gt.ir->mark_as_resolved(); + } } RegVal* FunctionEnv::make_ireg(TypeSpec ts, emitter::RegKind kind) { @@ -238,7 +247,7 @@ std::unordered_map& LabelEnv::get_label_map() { return m_labels; } -Val* FunctionEnv::lexical_lookup(goos::Object sym) { +RegVal* FunctionEnv::lexical_lookup(goos::Object sym) { if (!sym.is_symbol()) { throw std::runtime_error("invalid symbol in lexical_lookup"); } @@ -259,7 +268,7 @@ std::string LexicalEnv::print() { return "lexical"; } -Val* LexicalEnv::lexical_lookup(goos::Object sym) { +RegVal* LexicalEnv::lexical_lookup(goos::Object sym) { if (!sym.is_symbol()) { throw std::runtime_error("invalid symbol in lexical_lookup"); } diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index a9fc91c03..7f522bb3a 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -31,7 +31,7 @@ class Env { virtual void emit(std::unique_ptr ir); virtual RegVal* make_ireg(TypeSpec ts, emitter::RegKind kind); virtual void constrain_reg(IRegConstraint constraint); // todo, remove! - virtual Val* lexical_lookup(goos::Object sym); + virtual RegVal* 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); @@ -54,7 +54,7 @@ class GlobalEnv : public Env { 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; + RegVal* lexical_lookup(goos::Object sym) override; BlockEnv* find_block(const std::string& name) override; ~GlobalEnv() = default; @@ -128,7 +128,14 @@ class DeclareEnv : public Env { class IR_GotoLabel; struct UnresolvedGoto { - IR_GotoLabel* ir; + IR_GotoLabel* ir = nullptr; + std::string label; +}; + +class IR_ConditionalBranch; + +struct UnresolvedConditionalGoto { + IR_ConditionalBranch* ir = nullptr; std::string label; }; @@ -146,13 +153,18 @@ class FunctionEnv : public DeclareEnv { const std::vector& constraints() { return m_constraints; } void constrain(const IRegConstraint& c) { m_constraints.push_back(c); } void set_allocations(const AllocationResult& result) { m_regalloc_result = result; } - Val* lexical_lookup(goos::Object sym) override; + RegVal* lexical_lookup(goos::Object sym) override; const AllocationResult& alloc_result() { return m_regalloc_result; } bool needs_aligned_stack() const { return m_aligned_stack_required; } void require_aligned_stack() { m_aligned_stack_required = true; } + Label* alloc_unnamed_label() { + m_unnamed_labels.emplace_back(std::make_unique