mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 06:09:57 +00:00
[Compiler] Implement and/or in the compiler instead of a macro (#231)
* fix sc * doc update * another doc update
This commit is contained in:
parent
425cc6794c
commit
45f74f078a
@ -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`
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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},
|
||||
|
@ -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<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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
33
test/goalc/source_templates/with_game/test-short-circuit.gc
Normal file
33
test/goalc/source_templates/with_game/test-short-circuit.gc
Normal 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)
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user