[Compiler] Implement and/or in the compiler instead of a macro (#231)

* fix sc

* doc update

* another doc update
This commit is contained in:
water111 2021-02-03 16:12:51 -05:00 committed by GitHub
parent 425cc6794c
commit 45f74f078a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 60 deletions

View file

@ -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`

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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<RegVal*> 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);

View file

@ -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},

View file

@ -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.
@ -254,3 +255,72 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
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<FunctionEnv>(env);
auto end_label = fenv->alloc_unnamed_label();
end_label->func = fenv;
end_label->idx = -4; // placeholder
std::vector<TypeSpec> case_result_types;
case_result_types.push_back(TypeSpec("symbol")); // can always return #f.
std::vector<IR_ConditionalBranch*> 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<IR_RegSet>(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<IR_ConditionalBranch>(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;
}

View file

@ -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;
}
/*!

View file

@ -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)

View file

@ -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();