From 45f74f078ae6e4a1cfa0f8a9522103f42efda62e Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:12:51 -0500 Subject: [PATCH] [Compiler] Implement and/or in the compiler instead of a macro (#231) * fix sc * doc update * another doc update --- doc/changelog.md | 2 + doc/goal_doc.md | 2 +- goal_src/goal-lib.gc | 41 ----------- goalc/compiler/Compiler.h | 1 + goalc/compiler/IR.h | 32 ++++----- goalc/compiler/compilation/Atoms.cpp | 2 + goalc/compiler/compilation/ControlFlow.cpp | 70 +++++++++++++++++++ goalc/compiler/compilation/Type.cpp | 5 +- .../with_game/test-short-circuit.gc | 33 +++++++++ test/goalc/test_with_game.cpp | 5 ++ 10 files changed, 133 insertions(+), 60 deletions(-) create mode 100644 test/goalc/source_templates/with_game/test-short-circuit.gc diff --git a/doc/changelog.md b/doc/changelog.md index be432889c..36ad58af5 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -111,3 +111,5 @@ - Split `method` into `method-of-type` and `method-of-object` to avoid ambiguity - Fixed bug where `(-> obj type)` caused a compiler error when `obj` had compile time type of `array` (the fancy boxed array) - Fixed use-after-free if the top-level form fails to compile and you continue trying to compile stuff. +- `and` and `or` are more efficient and the type of the result is more specific: `LCA(symbol, cases...)` +- `print-type` now fully compiles the argument and returns the result instead of `none` \ No newline at end of file diff --git a/doc/goal_doc.md b/doc/goal_doc.md index 9f2065319..fb733ca92 100644 --- a/doc/goal_doc.md +++ b/doc/goal_doc.md @@ -1094,7 +1094,7 @@ Print the type of some GOAL expression at compile time. ```lisp (print-type form) ``` -This is mainly used to debug the compiler or figure out why some code is failing a type check. The thing inside is actually executed at runtime. Example: +This is mainly used to debug the compiler or figure out why some code is failing a type check. The thing inside is compiled fully and used as the result of `print-type`. Example: ```lisp (print-type "apples") ;; [TYPE] string (print-type (+ 12 1.2)) ;; [TYPE] int diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index 7d2635bf8..5381c4da4 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -304,47 +304,6 @@ ;; TODO - these work but aren't very efficient. -(defmacro and (&rest args) - (with-gensyms (result end) - `(begin - (let ((,result (the object #f))) - ,@(apply (lambda (x) - `(begin - (set! ,result ,x) - (if (eq? ,result #f) - (goto ,end) - ) - ) - ) - args - ) - (label ,end) - ,result - ) - ) - ) - ) - -(defmacro or (&rest args) - (with-gensyms (result end) - `(begin - (let ((,result (the object #f))) - ,@(apply (lambda (x) - `(begin - (set! ,result ,x) - (if (not (eq? ,result #f)) - (goto ,end) - ) - ) - ) - args - ) - (label ,end) - ,result - ) - ) - ) - ) ;;;;;;;;;;;;;;;;;;; ;; Math Macros diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 93c0968ea..fff7c12a6 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -322,6 +322,7 @@ class Compiler { 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); + Val* compile_and_or(const goos::Object& form, const goos::Object& rest, Env* env); // Define Val* compile_define(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index 1f369062d..bdbbd3dcf 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -116,22 +116,6 @@ class IR_RegSet : public IR { 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; -}; - class IR_FunctionCall : public IR { public: IR_FunctionCall(const RegVal* func, const RegVal* ret, std::vector args); @@ -257,6 +241,22 @@ struct Condition { std::string print() const; }; +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; +}; + class IR_ConditionalBranch : public IR { public: IR_ConditionalBranch(const Condition& condition, Label _label); diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index f807cb5d1..c756cce1b 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -56,6 +56,8 @@ static const std::unordered_map< // CONTROL FLOW {"cond", &Compiler::compile_cond}, {"when-goto", &Compiler::compile_when_goto}, + {"and", &Compiler::compile_and_or}, + {"or", &Compiler::compile_and_or}, // DEFINITION {"define", &Compiler::compile_define}, diff --git a/goalc/compiler/compilation/ControlFlow.cpp b/goalc/compiler/compilation/ControlFlow.cpp index d01170df6..123eee1fa 100644 --- a/goalc/compiler/compilation/ControlFlow.cpp +++ b/goalc/compiler/compilation/ControlFlow.cpp @@ -4,6 +4,7 @@ */ #include "goalc/compiler/Compiler.h" +#include "common/goos/ParseHelpers.h" /*! * Convert an expression into a GoalCondition for use in a conditional branch. @@ -252,5 +253,74 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest, // PATCH END end_label->idx = fenv->code().size(); + return result; +} + +Val* Compiler::compile_and_or(const goos::Object& form, const goos::Object& rest, Env* env) { + std::string op_name = form.as_pair()->car.as_symbol()->name; + bool is_and = false; + if (op_name == "and") { + is_and = true; + } else if (op_name == "or") { + is_and = false; + } else { + throw_compiler_error(form, "compile_and_or got an invalid operation {}", op_name); + } + + if (rest.is_empty_list()) { + throw_compiler_error(form, "and/or form must have at least one element"); + } + + auto result = env->make_gpr(m_ts.make_typespec("object")); // temp type for now. + auto fenv = get_parent_env_of_type(env); + auto end_label = fenv->alloc_unnamed_label(); + end_label->func = fenv; + end_label->idx = -4; // placeholder + + std::vector case_result_types; + case_result_types.push_back(TypeSpec("symbol")); // can always return #f. + + std::vector branch_irs; + auto n_elts = goos::list_length(rest); + int i = 0; + for_each_in_list(rest, [&](const goos::Object& o) { + // get the result of this case, put it in the main result and remember the type + auto temp = compile_error_guard(o, env)->to_gpr(env); + case_result_types.push_back(temp->type()); + env->emit_ir(result, temp); + + // no need check if we are the last element. + if (i != n_elts - 1) { + // now, check. + Condition gc; + gc.is_signed = false; + gc.is_float = false; + gc.a = result; + gc.b = compile_get_sym_obj("#f", env)->to_gpr(env); // todo, optimize + if (is_and) { + // for and we abort if we get a false: + gc.kind = ConditionKind::EQUAL; + } else { + // for or, we abort when we get truthy + gc.kind = ConditionKind::NOT_EQUAL; + } + // jump to end + auto branch = std::make_unique(gc, Label()); + branch_irs.push_back(branch.get()); + env->emit(std::move(branch)); + } + i++; + }); + + // now patch branches + end_label->idx = fenv->code().size(); + for (auto* br : branch_irs) { + br->label = *end_label; + br->mark_as_resolved(); + } + + // and set the result type + result->set_type(m_ts.lowest_common_ancestor(case_result_types)); + return result; } \ No newline at end of file diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index b85f34d31..185075da4 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -655,8 +655,9 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}}, {}); - fmt::print("[TYPE] {}\n", compile(args.unnamed.at(0), env)->type().print()); - return get_none(); + auto result = compile(args.unnamed.at(0), env)->to_reg(env); + fmt::print("[TYPE] {}\n", result->type().print()); + return result; } /*! diff --git a/test/goalc/source_templates/with_game/test-short-circuit.gc b/test/goalc/source_templates/with_game/test-short-circuit.gc new file mode 100644 index 000000000..2b2472e15 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-short-circuit.gc @@ -0,0 +1,33 @@ +(start-test "short-circuit") + +(defun dont-call-me () + (segfault) + ) + +(expect-true (= 1 (and 1))) +(expect-true (= #f (and #f))) +(expect-true (= 1 (and 2 1))) +(expect-true (= #f (and 2 #f 1))) +(expect-true (= #f (and #f 1 1))) +(expect-true (= #f (and 2 1 #f))) + +(expect-true (= 1 (or 1))) +(expect-true (= #f (or #f))) +(expect-true (= 2 (or 2 1))) +(expect-true (= 2 (or 2 #f 1))) +(expect-true (= 3 (or #f 3 1))) +(expect-true (= 2 (or 2 1 #f))) +(expect-true (= 2 (or #f #f #f 2 3 #f))) + +(or #f #f 1 (segfault) 2) + +(and 1 #f 1 (segfault) 2) + +(let ((x (the symbol #f))) + ;; type system should allow this because and will return a symbol + (set! x (and #t #f)) + (set! x (or #t #f)) + ;; (set! x (or 1 #f)) ;; not allowed. + ) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index f2665a1fe..03a100905 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -370,6 +370,11 @@ TEST_F(WithGameTests, LocalVars) { {"y is \"test\", x is 12, z is 3.2000\n0\n"}); } +TEST_F(WithGameTests, ShortCircuit) { + runner.run_static_test(env, testCategory, "test-short-circuit.gc", + get_test_pass_string("short-circuit", 13)); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines();